一些网站使用 回调函数 来实现 reCAPTCHA v2,而不是标准的 g-recaptcha-response 隐藏字段。当您解决验证码并将token 提交隐藏字段时,什么也没有发生 - 页面会忽略它。这是因为该网站希望您使用令牌调用 JavaScript 函数。
本指南向您展示如何检测基于回调的 reCAPTCHA v2,通过 CaptchaAI API 解决它,并正确调用回调。 API 调用与标准 reCAPTCHA v2 相同 — 只是token 提交步骤发生了变化。
reCAPTCHA v2 新手? 从如何使用API解决reCAPTCHA v2对于标准流程,然后返回此处了解回调变体。
开始之前您需要什么
| 要求 | 细节 |
|---|---|
| CaptchaAI API 密钥 | 从以下位置获取一份captchaai.com/api.php。 32 个字符的字符串。 |
| 目标页面网址 | reCAPTCHA v2 小部件加载的完整 URL。 |
| reCAPTCHA v2 站点密钥 | 与小部件实例关联的公钥。 |
| 浏览器自动化工具 | Selenium、Puppeteer 或 Playwright — 您需要执行 JavaScript 来调用回调。 |
| 回调函数名称 | 站点期望接收令牌的 JavaScript 函数。 |
如何识别回调实现
标准 reCAPTCHA v2 将解决的标记写入隐藏的 g-recaptcha-response 文本区域。回调实现会跳过这一点并直接调用 JavaScript 函数。以下是如何辨别差异的方法。
方法一:检查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 调用相同。唯一的区别在于您收到令牌后如何处理它。
| 步 | 标准v2 | 回调 v2 |
|---|---|---|
| 1.提交至CaptchaAI | method=userrecaptcha + 站点密钥 + 页面网址 |
相同的 |
| 2. 投票结果 | action=get + 验证码 |
相同的 |
| 3. 接收代币 | 相同的令牌格式 | 相同的 |
| 4. 注入代币 | 设置 g-recaptcha-response 字段值 |
使用token调用回调函数 |
| 5. 提交表格 | 触发表单提交 | 通常是自动的——回调处理它 |
关键: 不要在基于回调的实现上设置
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,直到令牌准备就绪。 - 从页面 DOM 检测回调函数名称。
- 使用
execute_script使用已解析的令牌调用回调函数。 - 网站自己的 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实现
API调用与PHP中相同。回调调用需要浏览器上下文,因此本示例涵盖服务器端解决方案。使用无头浏览器工具(例如 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 中获取令牌后,使用浏览器自动化工具(例如 php-webdriver)执行:
SubmitToken("TOKEN_FROM_CAPTCHAAI");
常见错误
| # | 错误 | 会发生什么 | 处理方式 |
|---|---|---|---|
| 1 | 设置g-recaptcha-response而不是调用回调 |
页面忽略令牌 - 表单永远不会提交 | 找到回调名称并使用令牌调用它 |
| 2 | 回调函数名错误 | JavaScript 错误:函数未定义 | 重新检查 data-callback、grecaptcha.render() 或内部配置 |
| 3 | 回调在不同的客户端索引上 | 针对多小部件页面的错误 reCAPTCHA 实例 | 检查 ___grecaptcha_cfg.clients[1]、clients[2] 等。 |
| 4 | 在页面准备好之前调用回调 | 页面上下文中尚未定义函数 | 调用前等待 DOMContentLoaded 或 networkidle |
| 5 | 使用模糊的 /minified 名称 | 源中的回调名称已损坏 | 使用运行时浏览器控制台查找实际的函数引用 |
| 6 | 将回调 v2 与不可见 v2 混合 | 一些不可见的实现也使用回调 | 检查 data-size="invisible" 是否存在 - 如果存在,请参见如何使用API解决reCAPTCHA Invisible |
故障排除
令牌已解决但页面没有反应
最常见的原因:您设置了 g-recaptcha-response 而不是调用回调。检查小部件的 grecaptcha.render() 中是否有 data-callback 或 callback。如果是这样,您必须调用该函数。
ReferenceError: SubmitToken is not defined
回调函数尚未加载或名称错误。尝试:
- 通过检查
data-callback或内部配置来确认名称。 - 在调用之前等待页面完全加载。
- 在缩小的站点上,该函数可能会分配给一个变量 - 在控制台中检查
window.SubmitToken。
令牌适用于标准 v2,但在此页面上失败
您可能面临回调实现。遵循上方的检测步骤确认后,切换到回调调用。
ERROR_BAD_TOKEN_OR_PAGEURL
sitekey/pageurl 对无效。这是一个 API 错误,与回调与标准无关。从页面中重新提取这两个值。
页面有多个 reCAPTCHA 小部件
每个小部件可能有自己的回调。检查每个 g-recaptcha div 或检查 ___grecaptcha_cfg.clients 是否有所有已注册的实例。将小部件与您要定位的表单相匹配。
ERROR_CAPTCHA_UNSOLVABLE
该挑战无法解决。使用新请求重试。这不是特定于回调的。
有关完整的错误参考,请参阅常见reCAPTCHA v2解决错误。
为什么 CaptchaAI 适用于此
| 因素 | 细节 |
|---|---|
| 相同的 API 调用 | Submit/poll 流程与标准 reCAPTCHA v2 相同 - 不需要额外的参数 |
| 成功率 | reCAPTCHA v2 为 99.5%+(回调和标准使用相同的求解器) |
| 求解速度 | 60秒以下 |
| 令牌兼容性 | 返回的令牌适用于 g-recaptcha-response 注入和回调调用 |
| 定价 | 基于线程的计划起价为 15/month,可无限解决问题 |
无论站点如何实现 reCAPTCHA v2,返回的令牌 CaptchaAI 都是相同的。区别完全在于客户端代码 - 如何将令牌传递到页面。
完整的可运行示例
需要一个包含环境设置、轮询、重试和错误处理的完整工作项目吗?
常问问题
什么是 reCAPTCHA v2 回调?
回调是网站注册以接收解析的 reCAPTCHA 令牌的 JavaScript 函数。 reCAPTCHA 不将令牌写入隐藏的 g-recaptcha-response 字段,而是直接调用该函数。该函数通常会触发表单提交、验证或页面重定向。
API 调用的回调与标准 reCAPTCHA v2 有何不同?
这完全没有什么不同。您使用相同的 sitekey 和 pageurl 发送相同的 method=userrecaptcha 请求。唯一的区别是您在收到令牌后如何处理它 - 您调用回调函数而不是设置字段值。
如何找到回调函数名称?
要检查的三个位置:(1) reCAPTCHA div 上的 data-callback 属性,(2) 页面 JavaScript 中 grecaptcha.render() 调用中的 callback 属性,(3) 浏览器控制台中的 ___grecaptcha_cfg.clients[0] 对象 — 导航树以查找 callback 属性。
我可以在这两种方法中使用令牌吗?
令牌本身可以以任何方式工作。但如果站点期望回调,则设置 g-recaptcha-response 将被忽略。始终将注入方法与站点期望的方法相匹配。
我需要浏览器来调用回调吗?
是的。回调是页面上下文中的 JavaScript 函数。您需要 Selenium、Puppeteer、Playwright 或可以在目标页面上运行 JavaScript 的类似工具。
开始解决reCAPTCHA v2回调
- 获取您的 API 密钥 —captchaai.com/api.php
- 检测回调名称 — 检查
data-callback、grecaptcha.render()或内部配置 - 复制上面的 Python 或 Node.js 代码 — 将占位符替换为您的密钥、sitekey、pageurl 和回调名称
- 运行它 — 令牌在 60 秒内到达,回调触发,页面处理结果
- 卡住了?从以下开始常见reCAPTCHA v2解决错误或阅读全文CaptchaAI API 文档
相关文章
- 解决 Recaptcha 看不见的 Python
- Recaptcha V2 Turnstile同一站点处理
- Recaptcha 企业评估 API 深入探讨