Anwendungsfälle

Domain-WHOIS-Abfragen automatisieren: CAPTCHA-Handling

Sobald Sie WHOIS-Portale im großen Stil abfragen, erscheint nach wenigen Domains ein reCAPTCHA v2 – und Ihr Skript bleibt stehen. Der Weg drum herum ist immer derselbe: das CAPTCHA per API lösen, das zurückgegebene Token in dieselbe Sitzung einsetzen und die Abfrage fortsetzen. Dieser Leitfaden zeigt den kompletten Ablauf für einzelne Lookups, Verfügbarkeitsprüfungen und Massenabfragen von Domain-Registrierungsdaten – inklusive Beispielcode in Python und Node.js.

Wo WHOIS-Portale CAPTCHAs auslösen

WHOIS-Dienste schützen Registrierungsdaten vor automatisiertem Auslesen. Nicht jedes Portal setzt denselben Mechanismus ein, und die Schwelle, ab der eine Abfrage blockiert wird, variiert stark. Wer weiß, welcher CAPTCHA-Typ wo auftaucht, plant seine Drosselung und Proxy-Rotation gezielter.

Portaltyp CAPTCHA Auslöseschwelle
ICANN WHOIS reCAPTCHA v2 3–5 Abfragen pro Sitzung
Suchseiten von Registraren reCAPTCHA v2/v3 5–10 Abfragen pro Minute
Regionale NIR (APNIC, RIPE) Bild-CAPTCHA 10–20 Abfragen
Domain-Auktions-WHOIS Cloudflare Turnstile schnelle Domain-Checks
Massen-WHOIS-Tools benutzerdefiniertes CAPTCHA nach dem kostenlosen Kontingent

reCAPTCHA v2 ist der mit Abstand häufigste Fall und deckt den größten Teil der öffentlichen WHOIS-Frontends ab. Genau darauf konzentriert sich der folgende Code.

WHOIS-Abfrage mit automatischer CAPTCHA-Lösung (Python)

Der Ablauf besteht aus vier Schritten: Seite laden, prüfen ob ein reCAPTCHA eingebettet ist, bei Bedarf den sitekey auslesen und über CaptchaAI lösen, dann das Token als g-recaptcha-response im selben Session-Objekt absenden. Weil das Token nur rund 120 Sekunden gültig ist, müssen Lösung und Absenden in derselben Sitzung passieren – sonst weist das Portal es zurück. Die Klasse WhoisLookup kapselt genau diesen Zyklus und ergänzt eine Massenabfrage sowie eine Verfügbarkeitsprüfung.

import requests
import time
import re

class WhoisLookup:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def lookup(self, domain, whois_url):
        """Look up WHOIS data for a domain, solving CAPTCHAs as needed."""
        response = self.session.get(whois_url, params={"domain": domain})

        if self._has_recaptcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, whois_url)
            response = self.session.post(whois_url, data={
                "domain": domain,
                "g-recaptcha-response": token
            })

        return self._parse_whois(response.text)

    def bulk_lookup(self, domains, whois_url, delay=3):
        """Look up WHOIS for multiple domains."""
        results = {}
        for domain in domains:
            try:
                results[domain] = self.lookup(domain, whois_url)
            except Exception as e:
                results[domain] = {"error": str(e)}
            time.sleep(delay)
        return results

    def check_availability(self, domains, whois_url):
        """Check which domains are available for registration."""
        results = self.bulk_lookup(domains, whois_url)
        available = []
        taken = []

        for domain, data in results.items():
            if data.get("error") or data.get("status") == "available":
                available.append(domain)
            else:
                taken.append(domain)

        return {"available": available, "taken": taken}

    def _has_recaptcha(self, html):
        return "g-recaptcha" in html or "recaptcha" in html.lower()

    def _extract_site_key(self, html):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if match:
            return match.group(1)
        raise ValueError("reCAPTCHA site key not found")

    def _solve_recaptcha(self, site_key, page_url):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": page_url,
            "json": 1
        })
        task_id = resp.json()["request"]

        for _ in range(60):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("reCAPTCHA solve timed out")

    def _parse_whois(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        # Look for WHOIS data in pre-formatted blocks or tables
        raw_whois = soup.select_one("pre, .whois-data, #whois-result")
        if raw_whois:
            text = raw_whois.get_text()
            return self._extract_fields(text)

        return {"raw": soup.get_text()[:2000]}

    def _extract_fields(self, text):
        fields = {}
        patterns = {
            "registrar": r"Registrar:\s*(.+)",
            "created": r"Creat(?:ed|ion) Date:\s*(.+)",
            "expires": r"(?:Expir(?:y|ation)|Registry Expiry) Date:\s*(.+)",
            "updated": r"Updated Date:\s*(.+)",
            "status": r"(?:Domain )?Status:\s*(.+)",
            "nameservers": r"Name Server:\s*(.+)",
            "registrant": r"Registrant (?:Name|Organization):\s*(.+)"
        }

        for field, pattern in patterns.items():
            matches = re.findall(pattern, text, re.IGNORECASE)
            if matches:
                fields[field] = matches if len(matches) > 1 else matches[0].strip()

        return fields


# Usage
whois = WhoisLookup("YOUR_API_KEY")

# Single lookup
result = whois.lookup("example.com", "https://whois.example.com/lookup")
print(f"Registrar: {result.get('registrar')}")
print(f"Expires: {result.get('expires')}")

# Bulk availability check
domains = ["startup-name.com", "my-project.io", "cool-app.dev"]
availability = whois.check_availability(domains, "https://whois.example.com/lookup")
print(f"Available: {availability['available']}")

_parse_whois liest die Antwort mit BeautifulSoup aus und normalisiert die üblichen Felder – Registrar, Anlage- und Ablaufdatum, Nameserver, Status. Extrahieren Sie diese Daten immer erst, nachdem das Token in derselben Sitzung angewendet wurde; ein Parser, der die CAPTCHA-Seite statt des Ergebnisses liest, liefert leere Felder.

Domain-Monitoring mit Ablaufwarnung (Node.js)

Für die laufende Überwachung eines Domain-Portfolios eignet sich ein Watchlist-Ansatz besser als Einzelabfragen. Die Klasse DomainMonitor prüft in Intervallen die Ablaufdaten der beobachteten Domains, löst dabei auftretende reCAPTCHAs über dieselbe CaptchaAI-Schleife und meldet alle Domains, die in den nächsten 30 Tagen auslaufen. So bemerken Sie eine anstehende Verlängerung, bevor die Domain in die Redemption Grace Period rutscht.

class DomainMonitor {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.watchList = new Map();
  }

  addDomain(domain, whoisUrl) {
    this.watchList.set(domain, { url: whoisUrl, history: [] });
  }

  async checkExpirations() {
    const expiring = [];

    for (const [domain, config] of this.watchList) {
      try {
        const data = await this.lookup(domain, config.url);
        config.history.push({ ...data, checkedAt: new Date().toISOString() });

        if (data.expires) {
          const daysLeft = Math.ceil(
            (new Date(data.expires) - new Date()) / (1000 * 60 * 60 * 24)
          );
          if (daysLeft <= 30) {
            expiring.push({ domain, daysLeft, expires: data.expires });
          }
        }
      } catch (error) {
        console.error(`Failed to check ${domain}: ${error.message}`);
      }
    }

    return expiring;
  }

  async lookup(domain, whoisUrl) {
    const response = await fetch(`${whoisUrl}?domain=${domain}`);
    const html = await response.text();

    if (html.includes('g-recaptcha')) {
      return this.solveAndLookup(domain, whoisUrl, html);
    }

    return this.parseWhois(html);
  }

  async solveAndLookup(domain, whoisUrl, html) {
    const match = html.match(/data-sitekey="([^"]+)"/);
    if (!match) throw new Error('No reCAPTCHA site key found');

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'userrecaptcha',
        googlekey: match[1],
        pageurl: whoisUrl,
        json: '1'
      })
    });
    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 60; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) {
        const response = await fetch(whoisUrl, {
          method: 'POST',
          body: new URLSearchParams({
            domain,
            'g-recaptcha-response': data.request
          })
        });
        return this.parseWhois(await response.text());
      }
    }
    throw new Error('reCAPTCHA solve timed out');
  }

  parseWhois(html) {
    const extract = (pattern) => {
      const match = html.match(pattern);
      return match ? match[1].trim() : null;
    };

    return {
      registrar: extract(/Registrar:\s*([^\n<]+)/i),
      created: extract(/Creat(?:ed|ion) Date:\s*([^\n<]+)/i),
      expires: extract(/(?:Expir(?:y|ation)|Registry Expiry) Date:\s*([^\n<]+)/i),
      status: extract(/(?:Domain )?Status:\s*([^\n<]+)/i)
    };
  }
}

// Usage
const monitor = new DomainMonitor('YOUR_API_KEY');
monitor.addDomain('example.com', 'https://whois.example.com/lookup');
monitor.addDomain('mysite.io', 'https://whois.example.com/lookup');

const expiring = await monitor.checkExpirations();
expiring.forEach(d => console.log(`${d.domain} expires in ${d.daysLeft} days`));

Legen Sie den checkExpirations-Lauf als geplanten Job an – etwa täglich per Cron auf einem kleinen Hetzner- oder netcup-Server. Die Historie pro Domain bleibt im Map-Objekt erhalten und lässt sich für Berichte oder Alerts weiterverwenden.

Abfragen drosseln und CAPTCHA-Trigger senken

Jedes gelöste CAPTCHA kostet Zeit und ein Guthaben-Kontingent. Am günstigsten fahren Sie, wenn möglichst wenige CAPTCHAs überhaupt erscheinen. Die folgenden vier Hebel senken die Auslöserate spürbar, bevor die Lösung ins Spiel kommt.

Strategie Nutzen
Ergebnisse lokal zwischenspeichern vermeidet wiederholte Abfragen derselben Domain
Verzögerungen von 3–5 Sekunden einbauen senkt die CAPTCHA-Auslöserate
zwischen WHOIS-Portalen wechseln verteilt die Last auf mehrere Anbieter
Sitzung persistent halten behält den CAPTCHA-Freigabestatus bei

Ein lokaler Cache ist dabei der wirksamste Hebel: Registrierungsdaten ändern sich selten, sodass eine Domain oft tage- oder wochenlang aus dem Cache bedient werden kann, statt erneut ein CAPTCHA auszulösen.

Typische Fehler und ihre Behebung

Problem Ursache Lösung
Zu viele CAPTCHAs in kurzer Zeit Abrufrate oder Parallelität ist für die Quelle zu aggressiv Drosseln Sie die Intervalle, halten Sie Sitzungen stabil und prüfen Sie die Qualität Ihrer Proxys
Daten fehlen trotz gelöstem CAPTCHA der Parser liest eine alte oder unvollständige Ansicht aus Extrahieren Sie Daten erst nach erfolgreicher Token-Anwendung in derselben Sitzung
Kosten steigen stärker als erwartet zu viele Wiederholungen oder unnötige Seitenaufrufe lösen zusätzliche Abfragen aus Lösen Sie nur kritische Schritte und protokollieren Sie Wiederholungen pro Quelle
Token wird abgelehnt das Token ist abgelaufen, bevor es abgesendet wurde Lösen und Absenden innerhalb von rund 120 Sekunden in derselben Sitzung

WHOIS im DACH-Raum: DENIC, RIPE und DSGVO

Für .de-Domains ist die DENIC die maßgebliche Registry; ihre öffentliche Auskunft liefert bewusst nur einen reduzierten Datensatz, seit die DSGVO in Kraft ist. Ähnliches gilt für europäische IP-Ranges bei der RIPE NCC in Amsterdam. Rechnen Sie also damit, dass bei europäischen Objekten viele Kontaktfelder redigiert sind – das ist keine Fehlfunktion Ihres Parsers, sondern gewollte Datensparsamkeit.

WHOIS-Datensätze enthalten teils personenbezogene Daten, und IP-Adressen gelten in der EU selbst als solche. Wer WHOIS-Daten automatisiert sammelt und speichert, sollte die eigene Rechtsgrundlage und die Speicherdauer sauber dokumentieren. Das ist eine Frage Ihrer eigenen Sorgfaltspflicht – CaptchaAI löst die CAPTCHA-Abfrage, trifft aber keine Aussage über die Zulässigkeit Ihrer Datenverarbeitung.

Kosten und Skalierung

CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro einzelner Lösung – jeder Plan enthält unbegrenzte Lösungen pro Thread im Abrechnungsmonat. Für WHOIS-Workflows heißt das: Ein Thread verarbeitet Ihre Abfragen nacheinander, mehrere Threads erlauben parallele Lookups über mehrere Portale hinweg. Der Einstieg BASIC (15 $/Monat, 5 Threads) reicht für die meisten Monitoring-Skripte; wer regelmäßig große Domain-Listen prüft, skaliert mit STANDARD (30 $/Monat, 15 Threads) oder ADVANCE (90 $/Monat, 50 Threads). Weil es keine Gebühr pro Lösung und keine Tageslimits gibt, bleiben die Kosten auch bei Massenabfragen planbar – begrenzt wird der Durchsatz nur durch die Lösungszeit pro Typ und Ihre Thread-Zahl.

Häufige Fragen

Welche CAPTCHA-Typen erscheinen auf WHOIS-Portalen?

Am häufigsten reCAPTCHA v2, dazu Bild-CAPTCHAs auf regionalen Registries und Cloudflare Turnstile auf schnellen Auktionsportalen. CaptchaAI löst alle drei Typen; hCaptcha und FunCaptcha werden nicht unterstützt, tauchen auf WHOIS-Frontends aber praktisch nicht auf.

Warum wird mein reCAPTCHA-Token abgelehnt?

Meist ist es abgelaufen. Ein reCAPTCHA-Token ist nur rund 120 Sekunden gültig und muss in derselben Sitzung abgesendet werden, in der die Seite geladen wurde. Lösen Sie das CAPTCHA also unmittelbar vor der Übermittlung und verwenden Sie dasselbe Session-Objekt.

Kann ich WHOIS-Abfragen parallel ausführen?

Ja. Da CaptchaAI pro gleichzeitigem Thread abrechnet, bestimmt Ihre Thread-Zahl, wie viele Lookups zeitgleich laufen. Verteilen Sie parallele Abfragen zusätzlich über mehrere Portale und Proxys, um die Auslöseschwelle einzelner Quellen nicht zu überschreiten.

Sind WHOIS-Daten durch die DSGVO eingeschränkt?

Ja. Seit 2018 redigieren viele Registries – darunter die DENIC – personenbezogene Felder in der öffentlichen Auskunft. Ein „No match" oder fehlende Registrant-Daten bedeuten daher oft eine bewusste Datensparsamkeit, nicht einen Parser-Fehler. Prüfen Sie in solchen Fällen ein alternatives Portal.

Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.