Domain intelligence is valuable for security research, brand protection, and competitive analysis. A WHOIS and DNS history tracker lets you monitor ownership changes, detect domain hijacking, and map infrastructure shifts over time.
In this guide, I'll build a complete domain intelligence tool that tracks WHOIS records and DNS changes using Python.
Why Track Domain History?
- Security teams need to detect when domains are transferred to suspicious registrars
- Brand protection requires monitoring for domain squatting and typosquatting
- Competitive intelligence reveals when competitors acquire new domains or change hosting
- Threat hunting uses DNS history to trace attacker infrastructure
Project Setup
pip install python-whois dnspython requests schedule
Building the WHOIS Tracker
import whois
import dns.resolver
import json
import hashlib
from datetime import datetime
from pathlib import Path
class DomainTracker:
def __init__(self, storage_dir: str = "./domain_data"):
self.storage_dir = Path(storage_dir)
self.storage_dir.mkdir(exist_ok=True)
def get_whois(self, domain: str) -> dict:
"""Fetch current WHOIS data for a domain."""
try:
w = whois.whois(domain)
return {
"domain": domain,
"registrar": w.registrar,
"creation_date": str(w.creation_date),
"expiration_date": str(w.expiration_date),
"name_servers": sorted(w.name_servers or []),
"registrant_country": w.country,
"dnssec": w.dnssec,
"updated_date": str(w.updated_date),
"fetched_at": datetime.utcnow().isoformat()
}
except Exception as e:
return {"domain": domain, "error": str(e)}
def get_dns_records(self, domain: str) -> dict:
"""Fetch all DNS record types for a domain."""
records = {"domain": domain, "fetched_at": datetime.utcnow().isoformat()}
record_types = ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA"]
for rtype in record_types:
try:
answers = dns.resolver.resolve(domain, rtype)
records[rtype] = sorted([str(r) for r in answers])
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN,
dns.resolver.NoNameservers):
records[rtype] = []
return records
Change Detection Engine
The core value is detecting changes over time:
def compute_fingerprint(self, data: dict) -> str:
"""Create a hash fingerprint of record state."""
canonical = json.dumps(data, sort_keys=True, default=str)
return hashlib.sha256(canonical.encode()).hexdigest()[:16]
def detect_changes(self, domain: str) -> list[dict]:
"""Compare current state against last known state."""
history_file = self.storage_dir / f"{domain}.json"
current_whois = self.get_whois(domain)
current_dns = self.get_dns_records(domain)
current_state = {
"whois": current_whois,
"dns": current_dns,
"fingerprint": self.compute_fingerprint(
{"whois": current_whois, "dns": current_dns}
)
}
changes = []
if history_file.exists():
history = json.loads(history_file.read_text())
last_state = history["snapshots"][-1]
if current_state["fingerprint"] != last_state["fingerprint"]:
diff = self._compute_diff(last_state, current_state)
changes = diff
history["snapshots"].append(current_state)
history["change_log"].extend(changes)
else:
history = {
"domain": domain,
"first_seen": datetime.utcnow().isoformat(),
"snapshots": [current_state],
"change_log": []
}
history_file.write_text(json.dumps(history, indent=2, default=str))
return changes
def _compute_diff(self, old: dict, new: dict) -> list[dict]:
"""Find specific differences between two states."""
changes = []
timestamp = datetime.utcnow().isoformat()
# Compare WHOIS fields
for field in ["registrar", "name_servers", "expiration_date"]:
old_val = old["whois"].get(field)
new_val = new["whois"].get(field)
if old_val != new_val:
changes.append({
"timestamp": timestamp,
"type": "whois_change",
"field": field,
"old_value": old_val,
"new_value": new_val
})
# Compare DNS records
for rtype in ["A", "AAAA", "MX", "NS", "TXT", "CNAME"]:
old_records = set(old["dns"].get(rtype, []))
new_records = set(new["dns"].get(rtype, []))
added = new_records - old_records
removed = old_records - new_records
if added or removed:
changes.append({
"timestamp": timestamp,
"type": "dns_change",
"record_type": rtype,
"added": list(added),
"removed": list(removed)
})
return changes
Monitoring Multiple Domains
import schedule
import time
def monitor_domains(domains: list[str], interval_hours: int = 6):
tracker = DomainTracker()
def check_all():
for domain in domains:
changes = tracker.detect_changes(domain)
if changes:
print(f"\n[ALERT] Changes detected for {domain}:")
for change in changes:
print(f" - {change['type']}: {json.dumps(change, indent=4)}")
else:
print(f"[OK] {domain}: no changes")
time.sleep(2) # Rate limit
check_all() # Initial run
schedule.every(interval_hours).hours.do(check_all)
while True:
schedule.run_pending()
time.sleep(60)
# Track competitor domains
domains_to_watch = [
"competitor1.com",
"competitor2.io",
"suspicious-domain.xyz"
]
monitor_domains(domains_to_watch, interval_hours=12)
Adding Historical Lookups via Web Scraping
For historical WHOIS data not available through the python-whois library, you can scrape public archives. ScraperAPI handles the proxy rotation needed for these lookups.
import requests
def fetch_historical_whois(domain: str, api_key: str) -> str:
"""Fetch historical WHOIS data via web archive."""
url = f"https://web.archive.org/web/2024*/whois.domaintools.com/{domain}"
params = {"api_key": api_key, "url": url}
response = requests.get("https://api.scraperapi.com", params=params)
return response.text
For high-volume DNS resolution with residential IPs, ThorData provides geo-distributed proxies that help avoid DNS-level blocking.
Use Cases in Production
- Brand monitoring: Track your brand domain and common typosquats daily
- M&A due diligence: Audit all domains owned by an acquisition target
- Threat intelligence: Map C2 infrastructure changes in real-time
- SEO monitoring: Detect when competitors change hosting or CDN providers
Monitor your scraping pipeline health with ScrapeOps dashboards to ensure you never miss a DNS change.
Wrapping Up
Domain intelligence is an underserved niche. Most tools charge $100+/month for what you can build with Python and a few libraries. The key is consistent monitoring — changes that slip by unnoticed for days can mean lost domains or security breaches.
Top comments (0)