Tutorials

从页面源中提取 reCAPTCHA 参数

写自动化脚本对接 reCAPTCHA 识别接口时,卡壳的往往不是识别本身,而是拿不到正确的参数。CaptchaAI 只要求两个必填值——googlekey(sitekey)和 pageurl——但 v3、隐形 reCAPTCHA 或 Enterprise 还会额外要求 actiondata-senterprise 标志,这些值不一定写在 HTML 属性里,也可能藏在脚本标签、iframe,或要等 JavaScript 执行完才出现。

四种提取思路速览

按页面渲染方式挑一种,命中不了就往下试:

  1. HTML 属性——sitekey 写在 data-sitekey,静态抓取即可命中。
  2. 脚本标签——v3、Enterprise 把 sitekey 编码进加载脚本的 URL。
  3. iframe src——widget 单独渲染在 iframe,从 src 里抠。
  4. JavaScript 动态渲染——完全靠 grecaptcha.render() 挂载,只能解析传参对象。

reCAPTCHA 各版本所需参数一览

提交前先对照参数表:

参数 v2 标准 v2 隐形 v3 Enterprise
googlekey(sitekey) 必需 必需 必需 必需
pageurl 必需 必需 必需 必需
invisible 1
action 必需 有时
data-s 有时 有时
enterprise 1

国内团队注意: reCAPTCHA 依赖 Google 托管的脚本,大陆网络访问不够稳定。静态抓取不受影响,但方法四那类要等 JavaScript 执行完的逻辑可能因脚本加载失败而拿不到结果——先排除这个变量,再查验证码逻辑本身。


方法一:从 HTML 属性直接读取

data-sitekey 属性

多数 reCAPTCHA v2 页面把 sitekey 写进 <div class="g-recaptcha"> 容器的 data-sitekey 属性,静态请求页面源码就能拿到,不需要启动浏览器:

import re
import requests

url = "https://staging.example.com/qa-login"
html = requests.get(url).text

# Find data-sitekey
match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', html)
if match:
    sitekey = match.group(1)
    print(f"Sitekey: {sitekey}")

# Check if invisible
invisible_match = re.search(r'data-size=["\']invisible["\']', html)
is_invisible = bool(invisible_match)
print(f"Invisible: {is_invisible}")

# Find callback
callback_match = re.search(r'data-callback=["\'](\w+)["\']', html)
callback = callback_match.group(1) if callback_match else None
print(f"Callback: {callback}")

# Check for data-s (Google-owned sites)
data_s_match = re.search(r'data-s=["\']([^"\']+)["\']', html)
data_s = data_s_match.group(1) if data_s_match else None
print(f"data-s: {data_s}")

如果页面靠 JavaScript 动态挂载 reCAPTCHA,服务端返回的 HTML 里没有 data-sitekey,静态抓取就会落空。这时候换 Puppeteer 之类的无头浏览器渲染出真实 DOM 再读取:

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });

const params = await page.evaluate(() => {
  const widget = document.querySelector('.g-recaptcha');
  if (!widget) return null;

  return {
    sitekey: widget.getAttribute('data-sitekey'),
    size: widget.getAttribute('data-size'),
    callback: widget.getAttribute('data-callback'),
    dataS: widget.getAttribute('data-s'),
    invisible: widget.getAttribute('data-size') === 'invisible',
  };
});

console.log(params);

方法二:从脚本标签解析

reCAPTCHA v3 与 Enterprise 的 sitekey

v3 和 Enterprise 不走 data-sitekey,sitekey 编码在加载脚本的 URL 查询参数里:

# Find sitekey from script src
v3_match = re.search(
    r'recaptcha/(?:api|enterprise)\.js\?.*?render=([A-Za-z0-9_-]+)',
    html
)
if v3_match:
    sitekey = v3_match.group(1)
    print(f"v3 Sitekey: {sitekey}")

# Check enterprise
is_enterprise = 'enterprise.js' in html
print(f"Enterprise: {is_enterprise}")

出现 enterprise.js 说明这是 reCAPTCHA Enterprise,提交时带上 enterprise 标志。

提取 action 参数

action 只有 v3 才必填,且从不出现在 HTML 属性上——它是 grecaptcha.execute() 调用时传入的运行参数,只能从内联脚本或加载的 JS 文件里搜索:

# Search for grecaptcha.execute calls
action_match = re.search(
    r'grecaptcha\.execute\s*\([^,]+,\s*\{[^}]*action\s*:\s*["\']([^"\']+)',
    html
)
if action_match:
    action = action_match.group(1)
    print(f"Action: {action}")

方法三:从 iframe src 提取

定位 iframe 里的 sitekey

有些页面把 reCAPTCHA 渲染在独立 iframe 里,前两种方法都会落空。直接从 iframe 的 src 里抠 sitekey 反而更可靠:

# Find reCAPTCHA iframe
iframe_match = re.search(
    r'<iframe[^>]+src=["\']([^"\']*recaptcha/api2/anchor[^"\']*)["\']',
    html
)
if iframe_match:
    iframe_src = iframe_match.group(1)
    sitekey_match = re.search(r'k=([A-Za-z0-9_-]+)', iframe_src)
    if sitekey_match:
        sitekey = sitekey_match.group(1)
        print(f"Iframe sitekey: {sitekey}")

方法四:JavaScript 动态渲染提取

解析 render() 传参对象

还有一类页面完全依赖 grecaptcha.render() 挂载 reCAPTCHA,HTML 骨架里没有相关标签,只能解析传给 render() 的配置对象:

# Find grecaptcha.render calls
render_match = re.search(
    r'grecaptcha\.render\s*\([^,]*,\s*\{([^}]+)\}',
    html
)
if render_match:
    config = render_match.group(1)
    sk = re.search(r'sitekey\s*:\s*["\']([A-Za-z0-9_-]+)', config)
    cb = re.search(r'callback\s*:\s*["\']?(\w+)', config)
    sz = re.search(r'size\s*:\s*["\'](\w+)', config)
    print(f"Sitekey: {sk.group(1) if sk else 'not found'}")
    print(f"Callback: {cb.group(1) if cb else 'not found'}")
    print(f"Size: {sz.group(1) if sz else 'not found'}")

整合成一个可复用的提取函数

四种方法按优先级串起来,命中即返回:

import re
import requests

def extract_recaptcha_params(url):
    html = requests.get(url, timeout=15).text
    params = {"pageurl": url}

    # Sitekey from data-sitekey
    sk = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)', html)
    if sk:
        params["sitekey"] = sk.group(1)

    # Sitekey from script render parameter (v3)
    if "sitekey" not in params:
        v3 = re.search(r'render=([A-Za-z0-9_-]{20,})', html)
        if v3:
            params["sitekey"] = v3.group(1)

    # Sitekey from iframe
    if "sitekey" not in params:
        iframe = re.search(r'recaptcha.*?k=([A-Za-z0-9_-]+)', html)
        if iframe:
            params["sitekey"] = iframe.group(1)

    # Sitekey from grecaptcha.render
    if "sitekey" not in params:
        render = re.search(r'sitekey\s*:\s*["\']([A-Za-z0-9_-]+)', html)
        if render:
            params["sitekey"] = render.group(1)

    # Version detection
    if re.search(r'data-size=["\']invisible', html):
        params["invisible"] = True
    if 'enterprise.js' in html:
        params["enterprise"] = True

    # Action (v3)
    action = re.search(
        r'action\s*:\s*["\']([^"\']+)',
        html[html.find('grecaptcha.execute'):] if 'grecaptcha.execute' in html else ''
    )
    if action:
        params["action"] = action.group(1)

    # data-s
    ds = re.search(r'data-s=["\']([^"\']+)', html)
    if ds:
        params["data_s"] = ds.group(1)

    # Callback
    cb = re.search(r'data-callback=["\'](\w+)', html)
    if cb:
        params["callback"] = cb.group(1)

    return params

# Usage
params = extract_recaptcha_params("https://staging.example.com/qa-login")
for k, v in params.items():
    print(f"  {k}: {v}")

预期输出:

  pageurl: https://staging.example.com/qa-login
  sitekey: 6Le-SITEKEY-abc123
  invisible: True
  callback: onCaptchaComplete

把提取到的参数提交给 CaptchaAI

按 reCAPTCHA 类型拼装请求体——只把对应类型需要的字段加进去:

data = {
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": params["sitekey"],
    "pageurl": params["pageurl"],
    "json": "1",
}

if params.get("invisible"):
    data["invisible"] = "1"
if params.get("enterprise"):
    data["enterprise"] = "1"
if params.get("action"):
    data["action"] = params["action"]
if params.get("data_s"):
    data["data-s"] = params["data_s"]

resp = requests.post("https://ocr.captchaai.com/in.php", data=data).json()

常见故障排查

问题 原因 处理方式
找不到 sitekey 页面是动态渲染的 换 Puppeteer/Selenium 渲染出真实 DOM 再解析
提取到错误的 sitekey 页面有多个 reCAPTCHA 实例 确认 sitekey 对应的 widget 就是目标表单
找不到 action 定义在外部 JS 文件里 把关联的 JS 文件拉下来一起搜索
data-s 每次都不一样 Google 端重新生成了它 每次解决前重新提取,不要缓存

常见问题

sitekey 和 API key 是一回事吗?

不是,两者性质完全相反:

  • sitekey——分配给网站的公钥,本来就该出现在页面源码里,公开可见没有安全风险。
  • API key——你的 CaptchaAI 账户凭证,只应放在服务端环境变量里,绝不能写进前端代码。

同一页面有多个 reCAPTCHA,该提交哪个 sitekey?

先在 DevTools 确认目标表单对应哪个 widget,取它自己的 data-sitekey。正则批量匹配的第一个结果未必是你要的,页面常同时挂着登录框、反馈表单等多个实例。

sitekey 能跨域名共用吗?

不建议。sitekey 绑定了具体域名,换域名提交大概率失败。测试环境请单独申请 staging 域名对应的 sitekey,不要复用生产站点的。

action 参数必须和页面里的值完全一致吗?

尽量精确匹配——action 会参与 Google 的打分逻辑。确实提取不到时,submit 这类通用动作也能跑通,但分数可能不如精确匹配稳定。


用提取到的参数直接提交给 CaptchaAI

获取你的 API Key:CaptchaAI 官网


相关指南

该文章已禁用评论。