写自动化脚本对接 reCAPTCHA 识别接口时,卡壳的往往不是识别本身,而是拿不到正确的参数。CaptchaAI 只要求两个必填值——googlekey(sitekey)和 pageurl——但 v3、隐形 reCAPTCHA 或 Enterprise 还会额外要求 action、data-s 或 enterprise 标志,这些值不一定写在 HTML 属性里,也可能藏在脚本标签、iframe,或要等 JavaScript 执行完才出现。
四种提取思路速览
按页面渲染方式挑一种,命中不了就往下试:
- HTML 属性——sitekey 写在
data-sitekey,静态抓取即可命中。 - 脚本标签——v3、Enterprise 把 sitekey 编码进加载脚本的 URL。
- iframe src——widget 单独渲染在 iframe,从
src里抠。 - 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 官网。
相关指南
- 浏览器控制台里定位 sitekey 与参数
- reCAPTCHA v2 回调函数的工作机制
- 识别隐形 reCAPTCHA 的触发时机