Comparisons

reCAPTCHA v2 与隐形 reCAPTCHA 解释

先说结论:这不是两个产品,而是同一个 reCAPTCHA v2 的两种前端配置。

  • sitekey 通用——同一个 key 两种都能配,看 key 分不出来。
  • token 字段相同——都写进 g-recaptcha-response
  • 请求只差一个参数——隐形变体多带 invisible=1

分叉在最后一步:复选框写完隐藏字段一般就能提交,隐形变体几乎一定要再调一次回调。下面按判变体、写参数、提交 token 展开,示例均针对自有或已授权的 QA、staging 环境。

隐形 reCAPTCHA 与复选框变体的差异对照

对比项 v2 复选框 v2 隐形
可见控件 有,“我不是机器人”
触发方式 点击复选框 按钮点击、表单提交或页面加载
图片挑战 内嵌展示 右下角弹出
sitekey 格式 相同 相同
token 字段 g-recaptcha-response 同左
回调函数 可选 几乎必需
CaptchaAI 请求 method=userrecaptcha 同上,另加 invisible=1
token 有效期 120 秒 120 秒
识别耗时(参考值) 10–30 秒 10–25 秒

会让脚本跑不通的只有两行:请求参数和回调。

什么场景下会遇到隐形 reCAPTCHA

国内站点多用 GeeTest(极验)、网易易盾、腾讯防水墙,reCAPTCHA 基本只出现在出海业务上。踩到隐形变体,多半是出海表单在 CI 里跑回归,而前端为了转化率换掉了复选框。

  • 先确认控件渲染出来了:reCAPTCHA 依赖 Google 托管脚本,内地网络下加载并不稳定。
  • 变体要动态判:桌面端复选框、移动端隐形是常见配置。
  • 只覆盖你有权限的站点与数据——网络安全法与 PIPL 下的前提。

从 HTML 结构分辨两种写法

v2 复选框

<!-- Standard checkbox widget -->
<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
     data-callback="onSubmit">
</div>

<!-- Widget renders as: -->
<!-- [✓] I'm not a robot     reCAPTCHA logo -->
  • 有可见控件,用户点一下才触发。
  • Google 起疑时,九宫格挑战在控件下方展开。
  • 容器上没有 data-size,只有 sitekey 和可选的回调名。

v2 隐形

<!-- Pattern 1: Invisible widget on a button -->
<button class="g-recaptcha"
        data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
        data-callback="onSubmit"
        data-size="invisible">
  Submit
</button>

<!-- Pattern 2: Invisible div (programmatic trigger) -->
<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
     data-size="invisible"
     data-callback="onSubmit">
</div>

<!-- Pattern 3: Programmatic render -->
<script>
  grecaptcha.render('submit-btn', {
    sitekey: '6Le-wvkSAAAAAPBMRTvw...',
    callback: onSubmit,
    size: 'invisible'
  });
</script>
  • 按钮式data-size="invisible" 直接挂在按钮上。
  • 隐形 div:靠代码调 grecaptcha.execute() 触发。
  • 程序化渲染:配置藏在 grecaptcha.render() 里,静态 HTML 搜不到,最容易漏判。

用脚本自动判断页面上的变体

批量巡检得让脚本自己判,顺序不能反,否则第三种写法会被误判成复选框:

  1. 先找带 data-size="invisible" 的元素,命中即隐形。
  2. 没命中再翻 <script>,找 render 配置里的 invisible。
  3. 都落空,才按普通 .g-recaptcha 当复选框。

Python 检测:

import requests
from bs4 import BeautifulSoup
import re

def detect_recaptcha_variant(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Check for invisible indicators
    invisible_widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
    if invisible_widget:
        return {
            "variant": "invisible",
            "sitekey": invisible_widget.get("data-sitekey"),
            "callback": invisible_widget.get("data-callback")
        }

    # Check for programmatic invisible in scripts
    for script in soup.find_all("script"):
        if script.string and "invisible" in str(script.string):
            key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
            if key_match:
                return {
                    "variant": "invisible-programmatic",
                    "sitekey": key_match.group(1),
                    "callback": "check grecaptcha.render() call"
                }

    # Check for standard checkbox
    checkbox_widget = soup.find(class_="g-recaptcha")
    if checkbox_widget:
        return {
            "variant": "checkbox",
            "sitekey": checkbox_widget.get("data-sitekey"),
            "callback": checkbox_widget.get("data-callback")
        }

    return None

result = detect_recaptcha_variant("https://staging.example.com/qa-login")
print(result)

Node.js 检测:

const axios = require("axios");
const cheerio = require("cheerio");

async function detectRecaptchaVariant(url) {
  const { data } = await axios.get(url);
  const $ = cheerio.load(data);

  // Check for invisible
  const invisible = $(".g-recaptcha[data-size='invisible']");
  if (invisible.length) {
    return {
      variant: "invisible",
      sitekey: invisible.attr("data-sitekey"),
      callback: invisible.attr("data-callback"),
    };
  }

  // Check scripts for programmatic invisible
  const scripts = $("script")
    .map((_, el) => $(el).html())
    .get()
    .join("\n");
  if (scripts.includes("invisible")) {
    const keyMatch = scripts.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
    if (keyMatch) {
      return {
        variant: "invisible-programmatic",
        sitekey: keyMatch[1],
        callback: "check render call",
      };
    }
  }

  // Check for standard checkbox
  const checkbox = $(".g-recaptcha");
  if (checkbox.length) {
    return {
      variant: "checkbox",
      sitekey: checkbox.attr("data-sitekey"),
      callback: checkbox.attr("data-callback"),
    };
  }

  return null;
}

控制台快速确认:

const el = document.querySelector('[data-size="invisible"]');
console.log(el ? "Invisible reCAPTCHA" : "Checkbox reCAPTCHA");

用 CaptchaAI API 识别两种变体

两种变体都走 method=userrecaptcha:提交到 in.php,再轮询 res.php。区别只有一处——隐形要带 invisible=1,否则 token 会被服务端拒掉。

v2 复选框

import requests
import time

resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": "6Le-wvkSAAAA...",
    "pageurl": "https://example.com/form"
})
task_id = resp.text.split("|")[1]

for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY", "action": "get", "id": task_id
    })
    if result.text.startswith("OK|"):
        token = result.text.split("|")[1]
        break

v2 隐形

import requests
import time

resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": "6Le-wvkSAAAA...",
    "pageurl": "https://example.com/form",
    "invisible": 1  # Only parameter difference
})
task_id = resp.text.split("|")[1]

for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY", "action": "get", "id": task_id
    })
    if result.text.startswith("OK|"):
        token = result.text.split("|")[1]
        break
  • 轮询 5 秒一次就够,压到 1 秒只会白占线程。
  • token 有效期 120 秒,识别挪到临近提交处再发起。

CaptchaAI 按并发线程计费,套餐内识别次数不限:单机脚本用 BASIC($15/月,5 线程)足够,多站点并行巡检一般落到 ADVANCE($90/月,50 线程)。

token 提交:隐形 reCAPTCHA 必须多做一步

写 token 的动作两边一样,都是写进 g-recaptcha-response 隐藏字段,差别出在写完之后。

v2 复选框:写隐藏字段通常就够

# Selenium — inject into hidden field
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# If the page uses a callback, also call it
callback = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
if callback:
    driver.execute_script(f'{callback}("{token}");')

v2 隐形:回调几乎必调

# Selenium — inject AND call the callback
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# CRITICAL: Invisible reCAPTCHA almost always requires calling the callback
callback_name = driver.find_element(
    "css selector", ".g-recaptcha[data-size='invisible']"
).get_attribute("data-callback")

driver.execute_script(f'{callback_name}("{token}");')
// Puppeteer — invisible callback injection
await page.evaluate((tok) => {
  // Set the hidden field
  document.getElementById("g-recaptcha-response").value = tok;

  // Find and call the callback function
  const widget = document.querySelector("[data-size='invisible']");
  const cbName = widget?.getAttribute("data-callback");
  if (cbName && typeof window[cbName] === "function") {
    window[cbName](tok);
  }
}, token);

关键差异: 复选框那次点击已经把回调走过一遍,脚本补个 token 往往就能提交。隐形变体没有这次点击,回调必须显式调用,否则表单看着填好了也提交不过。

一套兼容两种变体的识别脚本

生产脚本别把变体写死。下面这个类先检测,再决定加不加 invisible=1

  • 返回值带 variantcallback,提交那步照着走。
  • CAPCHA_NOT_READY 外的返回一律抛错,不要静默重试。
import requests
import time
from bs4 import BeautifulSoup

class RecaptchaV2UniversalSolver:
    def __init__(self, api_key):
        self.api_key = api_key

    def detect_and_solve(self, page_url, page_html=None):
        if not page_html:
            page_html = requests.get(page_url).text

        soup = BeautifulSoup(page_html, "html.parser")

        # Detect variant
        invisible = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
        widget = invisible or soup.find(class_="g-recaptcha")

        if not widget:
            raise Exception("No reCAPTCHA widget found")

        sitekey = widget.get("data-sitekey")
        is_invisible = invisible is not None
        callback = widget.get("data-callback")

        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url
        }
        if is_invisible:
            params["invisible"] = 1

        resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
        if not resp.text.startswith("OK|"):
            raise Exception(f"Submit failed: {resp.text}")

        task_id = resp.text.split("|")[1]
        for _ in range(60):
            time.sleep(5)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get", "id": task_id
            })
            if result.text.startswith("OK|"):
                return {
                    "token": result.text.split("|")[1],
                    "variant": "invisible" if is_invisible else "checkbox",
                    "callback": callback,
                    "sitekey": sitekey
                }
            if result.text != "CAPCHA_NOT_READY":
                raise Exception(f"Solve error: {result.text}")

        raise Exception("Timed out")

# Usage
solver = RecaptchaV2UniversalSolver("YOUR_API_KEY")
result = solver.detect_and_solve("https://staging.example.com/qa-login")
print(f"Variant: {result['variant']}, Callback: {result['callback']}")

排查对照表

常见现象对照:

现象 复选框处理方式 隐形处理方式
token 不被接受 确认写入 g-recaptcha-response 写入后再调 data-callback 函数
找不到控件 .g-recaptcha 类名 data-size 或 render 调用
提交了却校验失败 检查是否还要求回调 先定位回调名,再调用

常见问题

隐形 reCAPTCHA 和 reCAPTCHA v3 是一回事吗?

不是。隐形属于 v2,只返回通过与否的 token;v3 是打分模型,还会多给一个 0.0–1.0 的分数。两者都没有可见控件,参数却不同。

同一个 sitekey 能同时用于两种变体吗?

可以。变体是站点侧的配置,不体现在 sitekey 里:

  • 桌面端复选框、移动端隐形是常见组合。
  • 只看 sitekey 判变体一定翻车,每次运行都动态检测。

token 提交后页面没反应,卡在哪?

九成卡在回调。先读一次 g-recaptcha-response 确认写进去了,再确认 data-callback 的函数名在 window 上可调用。

为什么国内站点很少见到 reCAPTCHA?

国内业务更常用 GeeTest(极验)、网易易盾这类本土方案。CaptchaAI 支持 reCAPTCHA v2(含隐形)与 v3、GeeTest v3;GeeTest v4 只是即将支持,hCaptcha 与 FunCaptcha 不支持。

相关指南

该文章已禁用评论。