Use Cases

域 WHOIS 查找自动化的验证码处理

WHOIS 门户查询到第 3~5 次通常就会弹出验证码——这是 ICANN 官方查询页、注册商查询页面乃至 APNIC、RIPE 等区域级互联网注册管理机构的通用防护策略,不是针对某个 IP 的临时封锁。批量核实域名可用性、验证所有权、定期检查到期时间,只要请求量上去了,reCAPTCHA v2、图片验证码或 Cloudflare Turnstile 迟早会挡在中间。把 CaptchaAI 的识别能力接入查询脚本,遇到验证码就自动识别继续跑。

验证码什么时候会挡住 WHOIS 查询

  • ICANN WHOIS:reCAPTCHA v2,每个会话 3–5 次查询后触发
  • 注册商自建查询页:reCAPTCHA v2/v3,每分钟 5–10 次查询后触发
  • 区域级 NIR(APNIC、RIPE):图片验证码,10–20 次查询后触发
  • 域名拍卖平台 WHOIS:Cloudflare Turnstile,连续查域时触发
  • 批量 WHOIS 工具:自定义验证码,超出免费额度后触发

先把稳定性和报错处理讲清楚

提升查询稳定性的几个技巧

优化技巧 效果
本地缓存查询结果 避免重复查询同一域名
请求间隔设置为 3–5 秒 降低验证码触发概率
在多个 WHOIS 门户之间轮换 分散请求负载
保持会话状态 减少重复验证码挑战

常见报错排查

问题 原因 处理方式
查询 3 次左右就弹验证码 门户按 IP/会话限流 增加间隔,或用自有服务器基础设施分摊
WHOIS 返回“无匹配” 隐私/RDAP 数据编辑 换一个 WHOIS 门户重试
reCAPTCHA token 被拒绝 提交前 token 已过期 识别完成后 2 分钟内提交
IP 被封 单 IP 每日查询超限 轮换自有服务器基础设施出口

批量采集和存储 WHOIS 数据时,留意查询对象所在司法辖区的数据合规要求,比如国内的《数据安全法》与《个人信息保护法》——只处理你有权处理的数据。

Python 批量查询:识别 reCAPTCHA v2 后继续跑

给新项目选域名时,你可能要一次性核对几十个 .com/.io/.dev 候选词的注册状态,手动开 WHOIS 页面太慢。下面这段代码检测到 reCAPTCHA 后自动提取 sitekey,调用 CaptchaAI 识别,把 token 提交回表单继续拿数据。

小贴士:pip install 慢的话加个国内镜像,比如 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple beautifulsoup4 requests

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

JavaScript 域名到期监控:定时任务 + 验证码识别

手上有一批域名资产时,到期监控比一次性可用性查询更实用——续费窗口错过,域名可能被别人抢注。下面这段 Node.js 代码维护监控列表,定期查询,遇到 reCAPTCHA 自动识别后继续,到期日剩 30 天以内输出提醒。

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

常见问题

国内网络访问 reCAPTCHA 脚本较慢,会影响批量查询吗?

会。reCAPTCHA v2 加载 Google 托管的脚本,部分国内网络环境下访问不稳定,可能拖慢识别流程。建议设置足够的超时与重试,选连通性稳定的出口环境跑脚本。

批量查询时怎样避免被 WHOIS 门户封锁 IP?

查询间隔控制在 3–5 秒以上,结合本地缓存避免重复查询。查询量大时用自有服务器基础设施分散出口,并在多个门户间轮换。

WHOIS 门户除了 reCAPTCHA v2,还会遇到哪些验证码?

常见的还有图片验证码(多见于 APNIC、RIPE)和 Cloudflare Turnstile(常见于域名拍卖平台)。CaptchaAI 对这几类都有对应的识别方法。

端口 43 的 WHOIS 协议要验证码吗?

不需要,但受 RDAP 隐私政策限制,返回字段比网页门户少,查询频率控制也更严。网页门户虽然会挡验证码,通过后往往能看到更完整的字段。

能不能自动监控域名到期日期?

可以。给关注的域名建监控列表,按天或按周查询,遇到验证码让 CaptchaAI 自动识别,到期日低于设定阈值就触发提醒。

相关文章

下一步

想把域名批量查询、可用性检查和到期监控串成一条不会被验证码打断的流水线?注册 CaptchaAI 拿到 API Key,把上面的示例接进你自己的查询脚本。

该文章已禁用评论。