API Tutorials

如何使用 API 解决 reCAPTCHA v2 回调

提交了 token,表单却像没反应——如果目标站点的 reCAPTCHA v2 用的是回调(callback)方式,你大概率就是卡在这一步。标准实现会把解出的 token 写进隐藏的 g-recaptcha-response 字段;回调实现完全不理会这个字段,而是直接调用页面里注册好的 JavaScript 函数。字段该写的你都写了,函数没被调用,页面自然当作验证码从未通过。

好消息是:调用 CaptchaAI API 拿 token 的流程和标准 v2 完全一样,一个参数都不用改。真正要处理的只有最后一步——把 token 正确地交给回调函数,而不是塞进字段里。本文按顺序讲清楚:怎么判断页面是不是回调实现、CaptchaAI API 怎么调,以及 Python、Node.js、PHP 三种语言里怎么触发回调。

给出海团队提个醒:reCAPTCHA 本身要加载 Google 域名下的资源,国内网络访问不一定稳定,不少团队在验证码之外还会先卡在这一层;如果你的回调逻辑里还挂着支付前置校验或日志上报,出问题时更要分两步排查——先确认 reCAPTCHA 组件能不能正常加载,再确认回调函数有没有被真正调用。

刚接触 reCAPTCHA v2? 先看如何使用 API 解决 reCAPTCHA v2搞懂标准流程,再回来看这篇回调专项。


动手前先准备好这 5 样东西

  • CaptchaAI API Key:在 captchaai.com/api.php 申请,32 位字符串。
  • 目标页面 URL:reCAPTCHA v2 小部件实际加载的完整地址。
  • reCAPTCHA v2 sitekey:绑定该小部件实例的公钥。
  • 浏览器自动化工具:Selenium、Puppeteer 或 Playwright 都行,你需要能在页面上下文里执行 JavaScript 才能调用回调。
  • 回调函数名:站点用来接收 token 的那个 JavaScript 函数名。

怎么判断页面用的是不是回调实现

标准 reCAPTCHA v2 会把解出的 token 写进隐藏的 g-recaptcha-response 文本域;回调实现完全跳过这一步,直接调用 JavaScript 函数。下面三种方法可以帮你分辨。

方法 1:查 data-callback 属性

打开页面源码,找到 reCAPTCHA 小部件的 div:

<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
     data-callback="SubmitToken">
</div>

只要 data-callback 存在,这个站点就是回调实现——属性值(这里是 SubmitToken)就是你要调用的函数名。

方法 2:查 grecaptcha.render() 调用

在页面 JavaScript 里搜 grecaptcha.render

grecaptcha.render('recaptcha-container', {
  sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
  callback: userVerified
});

callback 属性就是函数名——这个例子里是 userVerified

方法 3:翻内部 reCAPTCHA 配置

在目标页面打开浏览器控制台,执行:

___grecaptcha_cfg.clients[0]
  • 顺着对象树找 callback 属性:具体路径每个站点都不一样,可能是 clients[0].aa.l.callback,也可能是别的层级,取决于 reCAPTCHA 版本和代码压缩方式。
  • 页面上有多个 reCAPTCHA 实例的话,clients[1]clients[2] 都要挨个查。

一次性检测脚本

嫌手动翻麻烦?在浏览器控制台跑这段代码,自动把回调名列出来:

// Check data-callback attributes
document.querySelectorAll('[data-callback]').forEach(el => {
  console.log('data-callback:', el.getAttribute('data-callback'));
});

// Check internal config
if (typeof ___grecaptcha_cfg !== 'undefined') {
  Object.keys(___grecaptcha_cfg.clients).forEach(key => {
    const client = ___grecaptcha_cfg.clients[key];
    console.log(`Client ${key}:`, JSON.stringify(client, null, 2));
  });
}

回调和标准 v2 到底差在哪一步

对 CaptchaAI 的 API 调用完全一样。唯一的差别是你拿到 token 之后怎么处理它。

  1. 提交给 CaptchaAImethod=userrecaptcha + sitekey + pageurl——两种实现完全相同。
  2. 轮询结果action=get + captcha ID——两种实现完全相同。
  3. 拿到 token:token 格式完全相同。
  4. 注入 token:标准 v2 写入 g-recaptcha-response 字段;回调 v2 要用 token 调用回调函数
  5. 提交表单:标准 v2 手动触发表单提交;回调 v2 通常自动——回调函数会处理。

划重点: 回调实现下不要去设置 g-recaptcha-response。页面根本不看这个字段,它在等回调函数被调用。只写字段不调用回调,效果和验证码从没解过一样。

整体流程一图看懂

Page → extract sitekey + pageurl + callback name
                    ↓
      POST to in.php (method=userrecaptcha)
                    ↓
           receive captcha ID
                    ↓
         wait 15–20 seconds
                    ↓
      GET res.php (action=get, id=…)
          ↓                    ↓
   CAPCHA_NOT_READY       status=1 → token
    (wait 5s, retry)            ↓
                     invoke callback(token)
                              ↓
               site processes token automatically

Python 实现(Selenium 版)

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

API_KEY = "YOUR_CAPTCHAAI_API_KEY"
SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
PAGE_URL = "https://staging.example.com/qa-login"
CALLBACK_NAME = "SubmitToken"  # The callback function name from the page

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


def solve_recaptcha_v2(api_key, sitekey, pageurl):
    """Submit a reCAPTCHA v2 task and return the solved token."""

    # Step 1: Submit the captcha
    submit_resp = requests.post(
        SUBMIT_URL,
        data={
            "key": api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": 1,
        },
        timeout=30,
    )
    submit_resp.raise_for_status()
    submit_data = submit_resp.json()

    if submit_data.get("status") != 1:
        raise RuntimeError(f"Submit failed: {submit_data}")

    captcha_id = submit_data["request"]
    print(f"Task created — captcha ID: {captcha_id}")

    # Step 2: Wait before first poll
    time.sleep(15)

    # Step 3: Poll for result
    for _ in range(60):
        result_resp = requests.get(
            RESULT_URL,
            params={
                "key": api_key,
                "action": "get",
                "id": captcha_id,
                "json": 1,
            },
            timeout=30,
        )
        result_resp.raise_for_status()
        result_data = result_resp.json()

        if result_data.get("request") == "CAPCHA_NOT_READY":
            time.sleep(5)
            continue

        if result_data.get("status") == 1:
            return result_data["request"]

        raise RuntimeError(f"Polling error: {result_data}")

    raise TimeoutError("reCAPTCHA v2 solve timed out")


def detect_callback_name(driver):
    """Detect the reCAPTCHA callback function name from the page."""

    # Try data-callback attribute first
    callback = driver.execute_script("""
        const el = document.querySelector('[data-callback]');
        if (el) return el.getAttribute('data-callback');
        return null;
    """)
    if callback:
        return callback

    # Try internal reCAPTCHA config
    callback = driver.execute_script("""
        if (typeof ___grecaptcha_cfg === 'undefined') return null;
        const clients = ___grecaptcha_cfg.clients;
        for (const key of Object.keys(clients)) {
            const client = clients[key];
            // Walk the object tree to find a callback function
            const json = JSON.stringify(client);
            const match = json.match(/"callback":"(\\w+)"/);
            if (match) return match[1];
        }
        return null;
    """)
    return callback


# Main workflow
driver = webdriver.Chrome()
driver.get(PAGE_URL)

# Detect the callback name (or use the known name)
detected = detect_callback_name(driver)
callback_name = detected or CALLBACK_NAME
print(f"Using callback: {callback_name}")

# Solve the CAPTCHA
token = solve_recaptcha_v2(API_KEY, SITEKEY, PAGE_URL)
print(f"Solved token: {token[:80]}...")

# Invoke the callback with the token
driver.execute_script(f"{callback_name}(arguments[0]);", token)
print("Callback invoked — site should process the token automatically")

# Wait for the page to process
time.sleep(3)
driver.quit()

代码逐步做了什么:

  1. method=userrecaptcha 把 sitekey 和 pageurl 提交到 in.php——和标准 v2 一模一样。
  2. 每 5 秒轮询一次 res.php,直到 token 就绪。
  3. 从页面 DOM 里检测回调函数名。
  4. execute_script 把解出的 token 传给回调函数并调用它。
  5. 剩下的交给网站自己的 JavaScript——表单提交、校验、页面跳转都会自动发生。

Node.js 实现(Puppeteer 版)

const puppeteer = require("puppeteer");

const API_KEY = "YOUR_CAPTCHAAI_API_KEY";
const SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
const PAGE_URL = "https://staging.example.com/qa-login";
const CALLBACK_NAME = "SubmitToken";

const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function solveRecaptchaV2(apiKey, sitekey, pageurl) {
  // Step 1: Submit the captcha
  const submitResp = await fetch(SUBMIT_URL, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      key: apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageurl,
      json: "1",
    }),
  });

  const submitData = await submitResp.json();
  if (submitData.status !== 1) {
    throw new Error(`Submit failed: ${JSON.stringify(submitData)}`);
  }

  const captchaId = submitData.request;
  console.log(`Task created — captcha ID: ${captchaId}`);

  // Step 2: Wait before first poll
  await sleep(15_000);

  // Step 3: Poll for result
  for (let i = 0; i < 60; i++) {
    const resultResp = await fetch(
      `${RESULT_URL}?${new URLSearchParams({
        key: apiKey,
        action: "get",
        id: captchaId,
        json: "1",
      })}`
    );

    const resultData = await resultResp.json();

    if (resultData.request === "CAPCHA_NOT_READY") {
      await sleep(5_000);
      continue;
    }

    if (resultData.status === 1) {
      return resultData.request;
    }

    throw new Error(`Polling error: ${JSON.stringify(resultData)}`);
  }

  throw new Error("reCAPTCHA v2 solve timed out");
}

async function detectCallbackName(page) {
  return page.evaluate(() => {
    // Try data-callback attribute
    const el = document.querySelector("[data-callback]");
    if (el) return el.getAttribute("data-callback");

    // Try internal config
    if (typeof ___grecaptcha_cfg !== "undefined") {
      const clients = ___grecaptcha_cfg.clients;
      for (const key of Object.keys(clients)) {
        const json = JSON.stringify(clients[key]);
        const match = json.match(/"callback":"(\w+)"/);
        if (match) return match[1];
      }
    }

    return null;
  });
}

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto(PAGE_URL, { waitUntil: "networkidle2" });

  // Detect callback
  const detected = await detectCallbackName(page);
  const callbackName = detected || CALLBACK_NAME;
  console.log(`Using callback: ${callbackName}`);

  // Solve the CAPTCHA
  const token = await solveRecaptchaV2(API_KEY, SITEKEY, PAGE_URL);
  console.log(`Solved token: ${token.slice(0, 80)}...`);

  // Invoke the callback
  await page.evaluate(
    (name, tkn) => {
      window[name](tkn);
    },
    callbackName,
    token
  );
  console.log("Callback invoked — site should process the token automatically");

  await sleep(3_000);
  await browser.close();
})();

PHP 实现

PHP 这边的 API 调用逻辑完全一样。回调调用必须在浏览器上下文里发生,所以下面这段只覆盖服务端拿 token 的部分——注入这一步交给无头浏览器工具(比如 PHP WebDriver)去做。

<?php
$apiKey  = "YOUR_CAPTCHAAI_API_KEY";
$sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
$pageurl = "https://staging.example.com/qa-login";

// Step 1: Submit
$submit = file_get_contents("https://ocr.captchaai.com/in.php?" . http_build_query([
    "key"       => $apiKey,
    "method"    => "userrecaptcha",
    "googlekey" => $sitekey,
    "pageurl"   => $pageurl,
    "json"      => 1,
]));

$submitData = json_decode($submit, true);
if ($submitData["status"] !== 1) {
    die("Submit failed: " . $submit);
}

$captchaId = $submitData["request"];
echo "Task created — captcha ID: $captchaId\n";

// Step 2: Wait and poll
sleep(15);

for ($i = 0; $i < 60; $i++) {
    $result = file_get_contents("https://ocr.captchaai.com/res.php?" . http_build_query([
        "key"    => $apiKey,
        "action" => "get",
        "id"     => $captchaId,
        "json"   => 1,
    ]));

    $resultData = json_decode($result, true);

    if ($resultData["request"] === "CAPCHA_NOT_READY") {
        sleep(5);
        continue;
    }

    if ($resultData["status"] === 1) {
        $token = $resultData["request"];
        echo "Solved token: " . substr($token, 0, 80) . "...\n";
        // Pass $token to your browser automation to invoke the callback
        break;
    }

    die("Polling error: " . $result);
}

PHP 端拿到 token 之后,用浏览器自动化工具(比如 php-webdriver)执行:

SubmitToken("TOKEN_FROM_CAPTCHAAI");

容易踩的坑

  1. 设置了 g-recaptcha-response,没调用回调——页面无视这个 token,表单永远提交不出去。怎么修: 找到回调函数名,用 token 直接调用它。
  2. 回调函数名写错了——报 JavaScript 错误:函数未定义。怎么修: 回头再核对一遍 data-callbackgrecaptcha.render() 或内部配置。
  3. 回调挂在别的 client 索引上——多小部件页面里调用错了 reCAPTCHA 实例。怎么修: 挨个检查 ___grecaptcha_cfg.clients[1]clients[2] 等。
  4. 页面还没就绪就调用回调——函数在当前页面上下文里还不存在。怎么修: 调用前先等 DOMContentLoadednetworkidle
  5. 用了压缩混淆后的函数名——源码里的回调名已经面目全非。怎么修: 直接在运行时浏览器控制台里找真实的函数引用。
  6. 把回调版 v2 和 invisible v2 搞混——部分 invisible 实现同样走回调。怎么修: 看页面有没有 data-size="invisible",有的话参考如何用 API 解决 reCAPTCHA Invisible

报错排查

token 拿到了,但页面就是没反应

最常见的原因:你设置了 g-recaptcha-response,但没调用回调。去看小部件的 grecaptcha.render() 里有没有 data-callbackcallback 参数——有的话,那个函数必须被显式调用。这个检查同样适用于“标准 v2 能跑、回调页面却不行”的情况——大概率就是撞上了回调实现,按上面的检测方法确认后切到回调调用方式。

ReferenceError: SubmitToken is not defined

要么回调函数还没加载,要么名字记错了。按顺序排查:

  1. 重新核对 data-callback 或内部配置里的函数名。
  2. 等页面完全加载完再调用。
  3. 压缩过的站点可能把函数挂在某个变量上——在控制台里查一下 window.SubmitToken

ERROR_BAD_TOKEN_OR_PAGEURL

sitekey/pageurl 这对参数不对,这是纯 API 层面的错误,跟回调还是标准无关,去页面重新提取一遍这两个值——页面上有多个 reCAPTCHA 小部件时,务必确认提取的是你要处理的那一个(对应表单可参考“容易踩的坑”第 3 条)。

ERROR_CAPTCHA_UNSOLVABLE

这次挑战没解出来,换个新请求重试即可,跟回调没关系。完整错误码列表见常见 reCAPTCHA v2 解决错误

为什么回调场景交给 CaptchaAI 更省心

  • API 调用零改动:submit/poll 流程和标准 reCAPTCHA v2 完全一样,不需要多传任何参数。
  • 成功率:reCAPTCHA v2 在支持类型中保持高成功率(回调和标准走的是同一套求解器)。
  • 求解速度:60 秒以内。
  • token 兼容两种注入方式:同一个 token,既能写进 g-recaptcha-response,也能直接传给回调函数。
  • 定价:基于线程的套餐,起价 BASIC $15/月(5 线程,单线程内解决次数不限)。

无论站点用哪种方式实现 reCAPTCHA v2,CaptchaAI 返回的 token 都是同一个——真正有差异的只是客户端代码,你怎么把这个 token 交给页面。

想要可以直接跑起来的完整项目?

包含环境配置、轮询逻辑、重试和错误处理的完整示例项目:GitHub 完整可运行示例 →


常见问题

CaptchaAI 能识别回调版的 reCAPTCHA v2 吗?

能。回调实现和标准实现共用同一套 API 和求解器,reCAPTCHA v2(含回调变体)属于 GA 支持类型,在支持类型中保持高成功率,通常 60 秒以内拿到 token。两者唯一的差异在客户端——你怎么把 token 交给页面。

轮询要等多久,回调什么时候会被触发?

  • 轮询节奏: 提交任务后先等 15 秒左右再开始轮询,之后每 5 秒查一次 res.php,大多数情况下 60 秒以内能拿到 token。
  • 回调触发方式: 不是系统自动触发的——token 到手之后,需要你在自己的自动化脚本里主动调用回调函数(参考本文的 Python/Node.js 实现)。

回调函数名一般在哪几个地方能找到?

  • reCAPTCHA div 上的 data-callback 属性。
  • 页面 JavaScript 里 grecaptcha.render() 调用中的 callback 参数。
  • 浏览器控制台里的 ___grecaptcha_cfg.clients 对象,顺着树找 callback 字段——函数名被压缩混淆过的站点,直接在控制台查运行时对象最保险。

reCAPTCHA v2 隐形(invisible)用的也是回调吗?

有一部分 invisible 实现确实走回调。判断方法很简单:看页面有没有 data-size="invisible"。有的话,除了本文的三种检测方法,也可以参考如何用 API 解决 reCAPTCHA Invisible

调用回调一定要有浏览器环境吗?

是的。回调是页面上下文里的 JavaScript 函数,脱离浏览器没法直接调用。纯服务端代码(比如本文的 PHP 示例)只能拿到 token,注入这一步还是要靠 Selenium、Puppeteer、Playwright 这类能在页面里跑 JavaScript 的工具来完成。


现在就动手处理 reCAPTCHA v2 回调

  1. 拿到 API Keycaptchaai.com/api.php
  2. 确认回调函数名 — 依次查 data-callbackgrecaptcha.render() 或内部配置
  3. 照抄上面的 Python 或 Node.js 代码 — 把占位符换成你的 key、sitekey、pageurl 和回调函数名
  4. 跑起来 — token 通常 60 秒内到手,调用回调,页面自己完成剩下的处理
  5. 还是卡住? 先查常见 reCAPTCHA v2 解决错误,或读完整的CaptchaAI API 文档

相关文章

该文章已禁用评论。