提交了 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 之后怎么处理它。
- 提交给 CaptchaAI:
method=userrecaptcha+ sitekey + pageurl——两种实现完全相同。 - 轮询结果:
action=get+ captcha ID——两种实现完全相同。 - 拿到 token:token 格式完全相同。
- 注入 token:标准 v2 写入
g-recaptcha-response字段;回调 v2 要用 token 调用回调函数。 - 提交表单:标准 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()
代码逐步做了什么:
- 用
method=userrecaptcha把 sitekey 和 pageurl 提交到in.php——和标准 v2 一模一样。 - 每 5 秒轮询一次
res.php,直到 token 就绪。 - 从页面 DOM 里检测回调函数名。
- 用
execute_script把解出的 token 传给回调函数并调用它。 - 剩下的交给网站自己的 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");
容易踩的坑
- 设置了
g-recaptcha-response,没调用回调——页面无视这个 token,表单永远提交不出去。怎么修: 找到回调函数名,用 token 直接调用它。 - 回调函数名写错了——报 JavaScript 错误:函数未定义。怎么修: 回头再核对一遍
data-callback、grecaptcha.render()或内部配置。 - 回调挂在别的 client 索引上——多小部件页面里调用错了 reCAPTCHA 实例。怎么修: 挨个检查
___grecaptcha_cfg.clients[1]、clients[2]等。 - 页面还没就绪就调用回调——函数在当前页面上下文里还不存在。怎么修: 调用前先等
DOMContentLoaded或networkidle。 - 用了压缩混淆后的函数名——源码里的回调名已经面目全非。怎么修: 直接在运行时浏览器控制台里找真实的函数引用。
- 把回调版 v2 和 invisible v2 搞混——部分 invisible 实现同样走回调。怎么修: 看页面有没有
data-size="invisible",有的话参考如何用 API 解决 reCAPTCHA Invisible。
报错排查
token 拿到了,但页面就是没反应
最常见的原因:你设置了 g-recaptcha-response,但没调用回调。去看小部件的 grecaptcha.render() 里有没有 data-callback 或 callback 参数——有的话,那个函数必须被显式调用。这个检查同样适用于“标准 v2 能跑、回调页面却不行”的情况——大概率就是撞上了回调实现,按上面的检测方法确认后切到回调调用方式。
ReferenceError: SubmitToken is not defined
要么回调函数还没加载,要么名字记错了。按顺序排查:
- 重新核对
data-callback或内部配置里的函数名。 - 等页面完全加载完再调用。
- 压缩过的站点可能把函数挂在某个变量上——在控制台里查一下
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 回调
- 拿到 API Key —captchaai.com/api.php
- 确认回调函数名 — 依次查
data-callback、grecaptcha.render()或内部配置 - 照抄上面的 Python 或 Node.js 代码 — 把占位符换成你的 key、sitekey、pageurl 和回调函数名
- 跑起来 — token 通常 60 秒内到手,调用回调,页面自己完成剩下的处理
- 还是卡住? 先查常见 reCAPTCHA v2 解决错误,或读完整的CaptchaAI API 文档
相关文章
- 用 Python 解决 reCAPTCHA Invisible
- reCAPTCHA v2 与 Turnstile 同站处理
- reCAPTCHA Enterprise Assessment API 深度解析