Comparisons

标准版与企业版 reCAPTCHA v2 求解器差异

任务返回了 token,站点却提示验证没通过——多半不是识别质量问题,而是版本判断错了。企业版页面加载 enterprise.js,提交任务必须带 enterprise=1;标准版页面多加这个参数,同样可能拿到用不了的 token。下面按接入顺序走:判断版本、看请求差异、提交 token。


结论先行:识别侧只差一个参数

两个版本的挑战机制完全一样:同样的“我不是机器人”复选框,同样的图片网格。企业版多出的原因码、自定义规则和 Google Cloud 项目管理都在站点后端校验环节,不会传到求解侧。集成只有两步:

  1. 判断页面加载的是 api.js 还是 enterprise.js
  2. 是企业版就加 enterprise=1,其余代码不动。

怎么判断页面用的是哪个版本

依据只有一条:页面加载了哪个 JavaScript 文件。

看 script 标签

<!-- Standard v2 -->
<script src="https://www.google.com/recaptcha/api.js"></script>

<!-- Enterprise v2 -->
<script src="https://www.google.com/recaptcha/enterprise.js"></script>

Python 自动判断

import requests
from bs4 import BeautifulSoup

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

    enterprise_script = soup.find("script", src=lambda s: s and "enterprise.js" in s)
    standard_script = soup.find("script", src=lambda s: s and "recaptcha/api.js" in s)

    widget = soup.find(class_="g-recaptcha")
    sitekey = widget["data-sitekey"] if widget else None

    if enterprise_script:
        return {"version": "enterprise_v2", "sitekey": sitekey}
    elif standard_script:
        return {"version": "standard_v2", "sitekey": sitekey}
    return None

info = detect_recaptcha_version("https://staging.example.com/qa-login")
print(info)

Node.js 自动判断

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

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

  const hasEnterprise = $('script[src*="enterprise.js"]').length > 0;
  const hasStandard = $('script[src*="recaptcha/api.js"]').length > 0;
  const sitekey = $(".g-recaptcha").attr("data-sitekey");

  if (hasEnterprise) return { version: "enterprise_v2", sitekey };
  if (hasStandard) return { version: "standard_v2", sitekey };
  return null;
}

控制台快速确认

// Quick check in DevTools
if (document.querySelector('script[src*="enterprise.js"]')) {
  console.log("Enterprise v2");
} else if (document.querySelector('script[src*="recaptcha/api.js"]')) {
  console.log("Standard v2");
}

检测只请求目标站点自身的 HTML,不依赖 Google 域名;但渲染 widget 联调时要加载 Google 域名上的脚本,国内网络下不一定稳定,别把网络问题误判成版本错误。


提交识别任务:两个版本的请求差异

标准版 v2

import requests
import time

# Submit task
resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url
})
task_id = resp.text.split("|")[1]

# Poll for token
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

提交成功返回 OK|<task_id>,之后每 5 秒轮询 res.php

企业版 v2

只多一个 enterprise=1,轮询逻辑不用改:

import requests
import time

# Submit task — only difference is enterprise=1
resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "enterprise": 1  # Required for Enterprise
})
task_id = resp.text.split("|")[1]

# Polling is identical
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

自动判断版本的通用封装

更实用的做法是把检测和提交合并,一份代码覆盖两种页面,站点升级也不必改配置:

import requests
import time
from bs4 import BeautifulSoup

class RecaptchaV2Solver:
    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")
        is_enterprise = bool(soup.find("script", src=lambda s: s and "enterprise.js" in s))
        widget = soup.find(class_="g-recaptcha")
        sitekey = widget["data-sitekey"] if widget else None

        if not sitekey:
            raise Exception("No reCAPTCHA sitekey found on page")

        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url
        }
        if is_enterprise:
            params["enterprise"] = 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],
                    "is_enterprise": is_enterprise,
                    "sitekey": sitekey
                }
            if result.text != "CAPCHA_NOT_READY":
                raise Exception(f"Solve failed: {result.text}")

        raise Exception("Solve timed out")


solver = RecaptchaV2Solver("YOUR_API_KEY")
result = solver.detect_and_solve("https://staging.example.com/qa-login")
print(f"Enterprise: {result['is_enterprise']}, Token: {result['token'][:40]}...")

把 token 写回页面:两个版本一致

写入 g-recaptcha-response 字段;页面有 data-callback 就再手动触发一次回调。

# Selenium injection — works for both standard and enterprise
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# If the page uses a callback function
callback = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
if callback:
    driver.execute_script(f'{callback}("{token}");')
// Puppeteer injection — works for both
await page.evaluate((token) => {
  document.getElementById("g-recaptcha-response").value = token;
  // Find and call callback if present
  const widget = document.querySelector(".g-recaptcha");
  const cb = widget?.getAttribute("data-callback");
  if (cb && typeof window[cb] === "function") {
    window[cb](token);
  }
}, token);

reCAPTCHA token 有效期约 2 分钟,表单提交要紧跟识别完成。


reCAPTCHA v2 标准版与企业版能力对照表

对比项 标准版 v2 企业版 v2
复选框与图片挑战 3×3 / 4×4 网格 完全相同
JS 文件 api.js enterprise.js
执行函数 grecaptcha.execute() grecaptcha.enterprise.execute()
校验接口 siteverify(免费) recaptchaenterprise.googleapis.com(付费)
原因码 有(AUTOMATION 等)
自定义规则 有(按 action 设阈值)
Google Cloud 控制台
密码泄露检测
CaptchaAI 参数 enterprise=1
典型识别耗时 10–30 秒 10–30 秒

最常见的四个坑

问题 表现 处理方式
标准版页面加了 enterprise=1 可能返回无效 token 先确认页面有 enterprise.js
企业版页面漏了 enterprise=1 token 被站点后端拒绝 检测到 enterprise.js 就带上
sitekey 取错 返回 ERROR_WRONG_GOOGLEKEY .g-recaptchadata-sitekey
把 v2 企业版当成 v3 企业版 提交参数整套用错 v2 有复选框,v3 只返回分数

出海业务的典型场景

国内站点多用 GeeTest(极验)、网易易盾、腾讯防水墙,reCAPTCHA 主要出现在自家出海站点和海外 SaaS 后台。常见故障是:前端换成企业版 sitekey 和 enterprise.js,回归脚本没跟着改,日志里识别全是 OK|,表单却整片失败。成本上不必单独算账:CaptchaAI 按并发线程计费,套餐内不限次数、不按类型加价,BASIC 套餐 $15/月、5 线程。


常见问题

页面同时出现 api.jsenterprise.js 怎么办?

按企业版处理,以表单实际渲染的那个 widget 为准,在控制台确认 grecaptcha.enterprise 是否存在即可。

标准版和企业版的 sitekey 能互换吗?

不能,企业版的 key 在 Google Cloud 项目里单独创建,用错会返回 ERROR_WRONG_GOOGLEKEY。稳妥做法是每次从 DOM 现读 data-sitekey

漏掉 enterprise=1 会报错吗?

通常不会,这也是最麻烦的地方。任务照常返回 OK|<token>,失败发生在站点后端校验,判断参数对错要看站点的最终响应。

v2 企业版和 v3 企业版怎么区分?

看有没有可见控件:v2 企业版是复选框加图片网格,v3 企业版无界面、只在后台打分,提交参数也不同。


延伸阅读

该文章已禁用评论。