深度解析

reCAPTCHA v2 回调机制:回调如何工作以及如何触发它们

当用户完成 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 密钥:验证码网站


相关指南

该文章已禁用评论。