当用户完成 reCAPTCHA v2 挑战时,Google 会调用网站定义的 JavaScript 回调函数。此回调通常会启用提交按钮、验证表单或发送 AJAX 请求。从 CaptchaAI 注入已解决的令牌后,您需要触发相同的回调 - 否则表单将无法识别验证码已解决。
回调如何工作
该站点在 reCAPTCHA 小部件中定义了一个回调:
<div class="g-recaptcha"
data-sitekey="6Le-SITEKEY"
data-callback="onCaptchaSuccess"
data-expired-callback="onCaptchaExpired">
</div>
<script>
function onCaptchaSuccess(token) {
document.getElementById('submit-btn').disabled = false;
document.getElementById('captcha-token').value = token;
}
</script>
当用户解决验证码时,Google 的 JavaScript 会调用 onCaptchaSuccess(token)。该函数接收标记字符串作为其唯一参数。
寻找回调函数
方法一:检查data-callback属性
// In browser console
const widget = document.querySelector('.g-recaptcha');
const callbackName = widget?.getAttribute('data-callback');
console.log('Callback:', callbackName);
方法 2:检查 grecaptcha 内部结构
某些站点使用带有回调选项的 grecaptcha.render() 而不是 data-callback 属性:
// Search page source for grecaptcha.render
document.querySelectorAll('script:not([src])').forEach(s => {
if (s.textContent.includes('grecaptcha.render')) {
console.log(s.textContent.match(/callback\s*:\s*(\w+)/)?.[1]);
}
});
方法三:拦截回调注册
在页面加载之前在 DevTools 中运行此命令(来源 → 片段):
const origRender = grecaptcha.render;
grecaptcha.render = function(container, params) {
console.log('Render callback:', params.callback);
console.log('Expired callback:', params['expired-callback']);
return origRender.apply(this, arguments);
};
token注入后触发回调
Python(硒)
import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
# Extract sitekey and callback
sitekey = driver.find_element(
By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-sitekey")
callback = driver.find_element(
By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-callback")
# Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": driver.current_url,
"json": "1",
}).json()
task_id = resp["request"]
token = None
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
token = result["request"]
break
# Inject token into textarea
driver.execute_script("""
document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
""", token)
# Trigger the callback
if callback:
driver.execute_script(f"window['{callback}'](arguments[0]);", token)
print(f"Triggered callback: {callback}")
else:
# Fallback: try ___grecaptcha_cfg
driver.execute_script("""
try {
var widgetId = Object.keys(___grecaptcha_cfg.clients)[0];
var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
if (typeof callback === 'function') callback(arguments[0]);
} catch(e) {}
""", token)
print("Triggered callback via ___grecaptcha_cfg")
JavaScript(Puppeteer)
const puppeteer = require('puppeteer');
// After solving and getting the token...
await page.evaluate((token, callbackName) => {
// Set textarea value
const textarea = document.querySelector(
'textarea[name="g-recaptcha-response"]'
);
textarea.value = token;
textarea.style.display = 'block'; // sometimes hidden
// Trigger callback
if (callbackName && typeof window[callbackName] === 'function') {
window[callbackName](token);
console.log(`Called ${callbackName}()`);
} else {
// Fallback: search grecaptcha config
try {
const clients = ___grecaptcha_cfg.clients;
const widgetId = Object.keys(clients)[0];
const cb = clients[widgetId]?.aa?.l?.callback;
if (typeof cb === 'function') cb(token);
} catch (e) {}
}
}, token, callbackName);
没有数据回调的站点
有些网站不使用回调。相反,他们会在提交表单时检查 grecaptcha.getResponse()。对于这些站点,覆盖该函数:
driver.execute_script("""
const token = arguments[0];
document.querySelector('textarea[name="g-recaptcha-response"]').value = token;
// Override getResponse to return the token
if (typeof grecaptcha !== 'undefined') {
grecaptcha.getResponse = function() { return token; };
}
""", token)
# Then submit the form normally
driver.find_element(By.CSS_SELECTOR, "form").submit()
过期回电
某些站点定义 data-expired-callback 以在令牌过期时禁用提交按钮。如果您的令牌即将到期,此回调可能会触发并重新锁定表单。在接近提交时间时解决以避免这种情况。
// Check for expired callback
const expiredCallback = document.querySelector('.g-recaptcha')
?.getAttribute('data-expired-callback');
console.log('Expired callback:', expiredCallback);
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
| 注射后表格仍然失效 | 回调未触发 | 查找并调用回调函数 |
ReferenceError: function not defined |
闭包中定义的回调 | 使用 ___grecaptcha_cfg 后备 |
| 令牌已注入但 AJAX 未发送 | 回调触发AJAX,而不是表单提交 | 检查回调函数做了什么 |
| 令牌已接受,但页面显示错误 | 回调前令牌已过期 | 临近提交时间解决 |
常问问题
如果根本没有回调怎么办?
有些网站仅在提交表单时检查服务器端的令牌。在这种情况下,设置 g-recaptcha-response textarea 值并提交表单就足够了。
回调函数名称可以在页面加载之间更改吗?
是的,在某些网站上。始终从 DOM 中动态提取回调名称,而不是对其进行硬编码。
reCAPTCHA v3是否使用相同的回调机制?
不会。reCAPTCHA v3 使用返回 Promise 的 grecaptcha.execute()。没有可见的小部件或 data-callback 属性。
使用 CaptchaAI 通过正确的回调处理解决 reCAPTCHA v2
获取您的 API 密钥:验证码网站。
相关指南
- reCAPTCHA v2 不可见:触发检测
- 从页面源中提取 reCAPTCHA 参数
- 处理单个页面上的多个验证码