Use Cases

Обработка CAPTCHA для автоматизации поиска WHOIS домена

Запрос к WHOIS почти всегда упирается в проверку: уже после третьего-пятого обращения к порталу регистратора выскакивает reCAPTCHA v2, графическая CAPTCHA или мягкий rate limit. Разработчики, которые парсят WHOIS для проверки доступности доменов, мониторинга портфеля или due diligence, теряют время на ручной ввод капчи вместо автоматизации. Ниже — какие проверки встречаются на каких порталах, рабочий код на Python и JavaScript и приёмы, которые снижают частоту срабатывания CAPTCHA ещё до того, как её приходится решать.

Какие CAPTCHA встречаются на WHOIS-порталах

Тип проверки и порог срабатывания зависят от портала: у ICANN он мягче, у региональных NIR и агрегаторов доменных аукционов — жёстче.

Тип портала CAPTCHA Порог срабатывания
ICANN WHOIS reCAPTCHA v2 3–5 запросов за сессию
Страницы регистраторов reCAPTCHA v2/v3 5–10 запросов в минуту
Региональные NIR (APNIC, RIPE) Графическая CAPTCHA 10–20 запросов
Аукционы доменов Cloudflare Turnstile При каждой проверке
Массовые WHOIS-инструменты Кастомная CAPTCHA После лимита free-тарифа

Как снизить частоту появления CAPTCHA при WHOIS-запросах

Прежде чем автоматически решать каждую CAPTCHA через API, стоит снизить саму частоту её появления — это экономит потоки CaptchaAI и ускоряет массовые проверки.

  • Кешируйте результаты локально, чтобы не запрашивать повторно один и тот же домен.
  • Ставьте задержку 3–5 секунд между запросами — CAPTCHA появляется реже.
  • Чередуйте WHOIS-порталы, чтобы распределить нагрузку между источниками.
  • Сохраняйте сессию (cookies) — тогда проверка CAPTCHA не сбрасывается на каждом заходе.

WHOIS-запрос с автоматическим решением CAPTCHA (Python)

Класс ниже проверяет ICANN WHOIS и решает reCAPTCHA v2 через CaptchaAI. При развёртывании в Европе или Казахстане/Центральной Азии заложите запас времени в опросе res.php — на нестабильном канале токен успевает «протухнуть» до отправки формы.

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']}")

Метод _solve_recaptcha отправляет задачу в in.php, затем опрашивает res.php раз в 3 секунды до 60 попыток — около трёх минут запаса, которого обычно хватает даже при нестабильном соединении. check_availability группирует результаты bulk_lookup в списки доступных и занятых доменов — удобно для скрипта, который проверяет портфель из нескольких сотен имён за один прогон.

Мониторинг сроков истечения домена (JavaScript)

Для портфеля из десятков доменов проще поставить проверку на расписание. Модуль ниже опрашивает домены и решает CAPTCHA автоматически, если она появляется.

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`));

DomainMonitor хранит историю проверок в watchList и на каждом опросе пересчитывает daysLeft до истечения регистрации. Если запускать checkExpirations() по cron раз в сутки, скрипт сам подставит g-recaptcha-response там, где портал требует проверку, и вернёт только те домены, которые истекают в ближайшие 30 дней.

Типичные проблемы и их решение

Если автоматизация спотыкается — вот на что смотреть в первую очередь.

Проблема Причина Решение
CAPTCHA уже после 3 запросов Жёсткий rate limit портала Увеличьте задержку, подключите прокси
WHOIS отвечает «No match» Данные скрыты приватностью/RDAP Попробуйте другой портал
Токен reCAPTCHA отклонён Токен истёк до отправки формы Отправляйте токен за 2 минуты
IP заблокирован Превышен дневной лимит Ротируйте прокси, снизьте частоту

Инфраструктура и регион: на что обратить внимание

Команды, которые разворачивают мониторинг WHOIS в европейских дата-центрах или в Казахстане/Центральной Азии, чаще сталкиваются с ростом времени приёма-передачи (RTT) до порталов регистраторов — из-за этого токен reCAPTCHA может «протухнуть» ещё до отправки формы. Добавьте запас 20–30 секунд к таймауту опроса res.php и держите прокси в регионе, близком к целевому порталу — это особенно заметно на мобильных и нестабильных каналах, где round-trip до дальнего дата-центра сам по себе занимает секунды. При сборе данных о регистранте помните: поля WHOIS часто содержат персональные данные, поэтому собирайте только то, что нужно для конкретной задачи (подробнее — в разделе с частыми вопросами ниже).

Для команд, которые ведут учёт в разных валютах, отдельный плюс — тарификация CaptchaAI фиксированной суммой в USD за поток в месяц, а не за каждый решённый запрос. Это упрощает прогноз бюджета мониторинга на десятки и сотни доменов вперёд.

Частые вопросы

Сколько потоков CaptchaAI нужно для параллельной проверки доменов?

Для разовых проверок хватает BASIC ($15/мес, 5 потоков). Для параллельного мониторинга сотен доменов — STANDARD ($30/мес, 15 потоков) или ADVANCE ($90/мес, 50 потоков); при росте портфеля тариф можно поменять без переписывания кода.

Можно ли отслеживать сроки продления доменов автоматически?

Да. Поставьте ежедневный опрос WHOIS по расписанию (cron) — CaptchaAI решает CAPTCHA на каждом заходе, а DomainMonitor из примера выше сам отфильтрует домены, которые истекают в ближайшие 30 дней.

Нужно ли учитывать закон о персональных данных при сборе WHOIS-данных?

Да: поля регистранта часто содержат персональные данные. Собирайте только то, что нужно для задачи — особенно с оглядкой на 152-ФЗ для аудитории из РФ и на GDPR для трансграничных сценариев. Это рекомендация по due diligence, а не юридическая консультация.

Дальнейшие шаги

Чтобы собрать WHOIS-мониторинг с нуля, начните с быстрого старта, затем разберите механику решения нужного типа CAPTCHA:

Комментарии для этой статьи отключены.