Explainers

如何在网页抓取工作流程中处理 reCAPTCHA v2

如果爬虫在采集海外站点数据时卡在 reCAPTCHA v2 的复选框或图片九宫格上,问题往往不在爬虫逻辑——而是页面在等一次人工验证。国内站点大多用 GeeTest(极验),但目标一换成海外站点,reCAPTCHA v2 基本绕不开。最快的恢复办法:提取 sitekey 和 pageurl,发给 CaptchaAI,拿到 token 后注入回页面即可继续。

本文给出两套可直接运行的代码:Python(Selenium + requests)和 Node.js(Puppeteer)。


从零开始:让抓取脚本跑通 reCAPTCHA v2

  1. captchaai.com/api.php 拿到你的 API Key
  2. 从目标页面里提取 sitekey
  3. 套用下文任意一段代码完成识别和 token 注入
  4. 流量上来之后,用并发识别把吞吐撑起来

常见问题

reCAPTCHA v2 识别会拖慢抓取速度吗?

单次识别一般 15–60 秒,批量抓取时并行提交任务(CaptchaAI 按线程计费)即可保住吞吐。

轮询结果一直卡在 CAPCHA_NOT_READY,是哪里的问题?

多是轮询次数和间隔太小,识别没跑完就退出了;调大后仍不行,检查 pageurl 是否和地址栏一致——这是第二常见原因。

reCAPTCHA v2 Invisible 和普通版本,接入方式一样吗?

一样,都用 userrecaptcha 方法;Invisible 没有可见复选框,靠 data-sitekey 属性定位。

国内网络环境下调试 reCAPTCHA v2 页面,加载总是很慢,正常吗?

正常,reCAPTCHA 依赖 Google 托管脚本,境内访问经常不稳定,与代码无关;生产环境选能稳定连通 Google 服务的网络来跑。

目标站点用的是 reCAPTCHA v2 Enterprise,请求要怎么调整?

加一个 enterprise=1 参数,其余不变,完整参数见如何用 API 识别 reCAPTCHA v2 Enterprise


两个参数决定成败:sitekey 与 pageurl

每个 reCAPTCHA v2 组件都要用到两个值:

  1. googlekey —— 页面 HTML 里的公开 sitekey
  2. pageurl —— 验证码出现的页面地址

发给 CaptchaAI API,等返回 token,再写回页面的 g-recaptcha-response 字段(或调用回调函数),站点验证通过即放行请求。

token 一次性,2 分钟左右过期,不能缓存复用,每个受保护页面都要重新识别。CaptchaAI 在自己的基础设施上完成识别,不需要传抓取用的代理——代理只用于后续的抓取请求。


Python 方案:Selenium + requests + CaptchaAI

下面是一段可以直接跑的示例:用 Selenium 打开页面、提取 sitekey,再用 requests 调用 CaptchaAI 完成识别和轮询。

import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# Step 1: Open the page with Selenium
driver = webdriver.Chrome()
driver.get("https://example.com/protected-page")

# Step 2: Extract the sitekey
sitekey = driver.find_element(By.CSS_SELECTOR, ".g-recaptcha").get_attribute("data-sitekey")
page_url = driver.current_url

# Step 3: Submit to CaptchaAI
response = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "json": 1
}).json()

task_id = response["request"]

# Step 4: Poll for result
token = None
for _ in range(40):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY",
        "action": "get",
        "id": task_id,
        "json": 1
    }).json()

    if result.get("status") == 1:
        token = result["request"]
        break
    if result.get("request") != "CAPCHA_NOT_READY":
        raise RuntimeError(f"Solve failed: {result['request']}")

# Step 5: Inject the token and submit
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").innerHTML = "{token}";'
)

# Check for callback
callback = driver.execute_script(
    'var el = document.querySelector(".g-recaptcha"); '
    'return el ? el.getAttribute("data-callback") : null;'
)
if callback:
    driver.execute_script(f'{callback}("{token}");')
else:
    driver.find_element(By.CSS_SELECTOR, "form").submit()

# Step 6: Scrape the data
print(driver.page_source[:500])
driver.quit()

Node.js 方案:Puppeteer + CaptchaAI

思路和 Python 版一致,只是换成 Puppeteer 打开页面,用原生 fetch 调用 CaptchaAI 接口。

const puppeteer = require("puppeteer");

async function scrapeWithCaptcha(url) {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: "networkidle2" });

  // Extract sitekey
  const sitekey = await page.$eval(".g-recaptcha", (el) => el.dataset.sitekey);

  // Submit to CaptchaAI
  const submitRes = await fetch(
    `https://ocr.captchaai.com/in.php?${new URLSearchParams({
      key: "YOUR_API_KEY",
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: url,
      json: 1,
    })}`
  );
  const { request: taskId } = await submitRes.json();

  // Poll for result
  let token;
  for (let i = 0; i < 40; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const res = await fetch(
      `https://ocr.captchaai.com/res.php?${new URLSearchParams({
        key: "YOUR_API_KEY",
        action: "get",
        id: taskId,
        json: 1,
      })}`
    );
    const data = await res.json();
    if (data.status === 1) {
      token = data.request;
      break;
    }
    if (data.request !== "CAPCHA_NOT_READY")
      throw new Error(`Solve failed: ${data.request}`);
  }

  // Inject token
  await page.evaluate((t) => {
    document.getElementById("g-recaptcha-response").innerHTML = t;
    const cb = document.querySelector(".g-recaptcha")?.dataset.callback;
    if (cb && window[cb]) window[cb](t);
  }, token);

  // Wait for navigation after form submit
  await page.waitForNavigation({ waitUntil: "networkidle2" });
  const content = await page.content();
  await browser.close();
  return content;
}

scrapeWithCaptcha("https://example.com/protected-page").then(console.log);

不用浏览器:纯 HTTP 请求方案

如果目标站点把 reCAPTCHA v2 的 token 当成普通表单字段接收(POST 里带 g-recaptcha-response 就行),可以跳过 Selenium 或 Puppeteer,直接用 requests.Session 走完流程,速度更快、资源占用更低。只有页面必须靠 JavaScript 把 token 写进 DOM 才能提交时,才真正需要浏览器:

import requests
import time

session = requests.Session()
session.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0"

# Load the page to get cookies
session.get("https://example.com/protected-page")

# Solve the CAPTCHA
sitekey = "6Le-wvkSAAAAAN..."  # extracted from page HTML
solve_resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY", "method": "userrecaptcha",
    "googlekey": sitekey, "pageurl": "https://example.com/protected-page",
    "json": 1
}).json()

task_id = solve_resp["request"]
time.sleep(15)

# Poll
for _ in range(30):
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY", "action": "get", "id": task_id, "json": 1
    }).json()
    if result.get("status") == 1:
        token = result["request"]
        break
    time.sleep(5)

# Submit with token
resp = session.post("https://example.com/protected-page", data={
    "g-recaptcha-response": token,
    "other_field": "value"
})
print(resp.text[:500])

无头浏览器容易被提前拦截,该怎么办

有些站点会在 reCAPTCHA v2 出现前就识别出无头浏览器并拦截。脚本还没看到验证码就被拦下时,可以试试:

  • Puppeteer 换成新版 headless: "new" 模式
  • Chromium 启动参数加上 --no-sandbox
  • 换一个真实浏览器会用的 User-Agent
  • 给抓取会话单独配一个稳定代理

这解决的是页面愿不愿意显示 reCAPTCHA,和 CaptchaAI 识别 token 是两回事。


相关指南

该文章已禁用评论。