reCAPTCHA v2 集成里最让人摸不着头脑的报错,往往不是 API 报错,而是 API 明明返回了有效 token,页面却还是拒绝提交。这类问题可以分三层排查:提交阶段的参数错误、轮询阶段的异常返回,以及最隐蔽的一种——目标页面拒绝了已经生效的 token。本文按这三层逐一拆解常见故障,并给出对应的修复方法。如果你还没跑通基本流程,建议先看reCAPTCHA v2 API 识别教程,跑通一次成功请求再回来对照排查。
已经拿到错误码?先查这张速查表
如果你手上已经有具体的错误码或症状,直接对照下表定位,不用从头看完整篇:
| 症状 | 先查什么 |
|---|---|
ERROR_GOOGLEKEY 或 ERROR_WRONG_GOOGLEKEY |
sitekey 是不是从 data-sitekey 原样复制的? |
ERROR_PAGEURL |
有没有传完整页面 URL? |
ERROR_BAD_TOKEN_OR_PAGEURL |
组件是不是在 iframe 里?改用 iframe URL。 |
CAPCHA_NOT_READY 持续超过 3 分钟 |
难题正常现象,把超时时间调到 180 秒。 |
ERROR_CAPTCHA_UNSOLVABLE |
提交新任务;反复出现就核对 sitekey + pageurl。 |
| token 有效但页面无反应 | 查 data-callback,手动调用回调函数。 |
| token 返回了但表单还是失败 | token 可能已过期(超过 2 分钟),加快提交节奏。 |
| 偶发性失败 | 加上重试逻辑,每次用新的任务 ID。 |
没找到对应症状?往下看完整排查流程。
报错前先查这 4 个高频原因(覆盖 80% 场景)
在逐个翻错误码之前,先扫一眼这张表——这 4 个原因能解释绝大多数故障,后面章节会逐一展开。
| 高频原因 | 典型症状 | 对应错误码 |
|---|---|---|
googlekey 填错或漏填 |
提交请求后立刻被拒绝,任务连排队都没进 | ERROR_GOOGLEKEY、ERROR_WRONG_GOOGLEKEY |
pageurl 与实际加载地址不符 |
组件嵌在跨域 iframe 里时最常见 | ERROR_PAGEURL、ERROR_BAD_TOKEN_OR_PAGEURL |
| 页面需要回调,你却只填了隐藏字段 | 提交本身不报错,但表单原地不动 | 属于目标页面拒绝,API 不会报错 |
| token 过期或被重复使用 | 拿到 token 后间隔太久才提交,或复用了同一个 token | 属于目标页面拒绝,API 不会报错 |
googlekey(也就是 sitekey)来自 reCAPTCHA 组件的 data-sitekey 属性,或者锚点 URL 里的 k 参数:
# Look for data-sitekey in the page HTML
# <div class="g-recaptcha" data-sitekey="6Le-wvkSVVABCPBMRTvw0Q4Muexq1bi0DJwx_mJ-"></div>
# Or find it in the anchor URL
# https://www.google.com/recaptcha/api2/anchor?k=6Le-wvkSVVABCPBMRTvw0Q4Muexq1bi0DJwx_mJ-
后三个原因——pageurl、回调、token 过期——都属于“API 觉得没问题,页面却拒绝”这一类,下文有专门一节逐条拆解,这里先记住结论,方便对照错误码表快速定位。
提交阶段报错:in.php 返回的错误码
把验证码任务提交到 https://ocr.captchaai.com/in.php 时,可能会遇到下面这些错误。
| 错误码 | 原因 | 处理方式 |
|---|---|---|
ERROR_WRONG_USER_KEY |
API Key 格式不对(不是 32 位字符) | 到 captchaai.com/api.php 核对你的 API Key |
ERROR_KEY_DOES_NOT_EXIST |
系统里查不到这个 API Key | 确认复制的是完整密钥,前后没有多余空格 |
ERROR_ZERO_BALANCE |
账户余额为 0 | 充值,或检查当前占用的线程数 |
ERROR_PAGEURL |
缺少 pageurl 参数 |
补上 reCAPTCHA 组件所在页面的完整 URL |
ERROR_GOOGLEKEY |
googlekey 格式错误或为空 |
从页面里重新提取正确的 sitekey |
ERROR_WRONG_GOOGLEKEY |
请求里完全没带 googlekey |
把 googlekey 加进 API 请求 |
ERROR_BAD_TOKEN_OR_PAGEURL |
googlekey 和 pageurl 对不上 |
检查组件是否在 iframe 里,改用 iframe 的 URL |
ERROR_BAD_PARAMETERS |
必填参数缺失或格式不对 | 对照 API 文档 检查必填字段 |
下面是带完整错误处理的请求示例,Python 和 JavaScript 各一份:
import requests
def submit_recaptcha_v2(api_key, sitekey, page_url):
response = requests.get("https://ocr.captchaai.com/in.php", params={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": 1
})
data = response.json()
if data.get("status") == 1:
return data["request"] # task ID
error = data.get("request", "UNKNOWN_ERROR")
if error == "ERROR_WRONG_USER_KEY":
raise ValueError("API key format is invalid. Must be 32 characters.")
elif error == "ERROR_ZERO_BALANCE":
raise RuntimeError("Account balance is zero. Top up at captchaai.com")
elif error == "ERROR_PAGEURL":
raise ValueError("pageurl parameter is missing from request")
elif error in ("ERROR_GOOGLEKEY", "ERROR_WRONG_GOOGLEKEY"):
raise ValueError(f"Invalid sitekey. Verify the data-sitekey value on the page.")
elif error == "ERROR_BAD_TOKEN_OR_PAGEURL":
raise ValueError("Sitekey/pageurl mismatch. Check if widget is in an iframe.")
else:
raise RuntimeError(f"API error: {error}")
# Usage
task_id = submit_recaptcha_v2("YOUR_API_KEY", "6Le-wvkSAAAAAN...", "https://staging.example.com/qa-login")
print(f"Task submitted: {task_id}")
async function submitRecaptchaV2(apiKey, sitekey, pageUrl) {
const params = new URLSearchParams({
key: apiKey,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageUrl,
json: 1,
});
const res = await fetch(`https://ocr.captchaai.com/in.php?${params}`);
const data = await res.json();
if (data.status === 1) return data.request;
const error = data.request || "UNKNOWN_ERROR";
const fixes = {
ERROR_WRONG_USER_KEY: "API key format is invalid. Must be 32 characters.",
ERROR_ZERO_BALANCE: "Account balance is zero. Top up at captchaai.com",
ERROR_PAGEURL: "pageurl parameter is missing from request",
ERROR_GOOGLEKEY: "Invalid sitekey. Check the data-sitekey attribute.",
ERROR_BAD_TOKEN_OR_PAGEURL: "Sitekey/pageurl mismatch. Check iframe context.",
};
throw new Error(fixes[error] || `API error: ${error}`);
}
// Usage
const taskId = await submitRecaptchaV2("YOUR_API_KEY", "6Le-wvkSAAAAAN...", "https://staging.example.com/qa-login");
console.log(`Task submitted: ${taskId}`);
轮询阶段报错:res.php 返回的错误码
轮询 https://ocr.captchaai.com/res.php 获取结果时,可能会遇到下面这些错误。
| 错误码 | 原因 | 处理方式 |
|---|---|---|
CAPCHA_NOT_READY |
还在处理中 | 等 5 秒再轮询一次,这是正常现象 |
ERROR_CAPTCHA_UNSOLVABLE |
无法识别 | 换一批新参数重新提交任务 |
ERROR_WRONG_ID_FORMAT |
任务 ID 格式不对 | 核对 in.php 返回的 ID |
ERROR_WRONG_CAPTCHA_ID |
任务 ID 不存在 | 确认保存的是正确的任务 ID |
ERROR_EMPTY_ACTION |
缺少 action=get 参数 |
轮询请求里补上 action=get |
下面是带错误处理的轮询逻辑,同样是 Python 和 JavaScript 两份:
import time
import requests
def poll_result(api_key, task_id, timeout=120):
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
response = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key,
"action": "get",
"id": task_id,
"json": 1
})
data = response.json()
if data.get("status") == 1:
return data["request"] # solved token
error = data.get("request", "")
if error == "CAPCHA_NOT_READY":
continue # normal — keep waiting
elif error == "ERROR_CAPTCHA_UNSOLVABLE":
raise RuntimeError("CAPTCHA unsolvable. Submit a new task with fresh params.")
elif error in ("ERROR_WRONG_ID_FORMAT", "ERROR_WRONG_CAPTCHA_ID"):
raise ValueError(f"Invalid task ID: {task_id}")
else:
raise RuntimeError(f"Polling error: {error}")
raise TimeoutError(f"Solve timed out after {timeout}s")
# Usage
token = poll_result("YOUR_API_KEY", task_id)
print(f"Token: {token[:50]}...")
async function pollResult(apiKey, taskId, timeout = 120000) {
const start = Date.now();
while (Date.now() - start < timeout) {
await new Promise((r) => setTimeout(r, 5000));
const params = new URLSearchParams({
key: apiKey,
action: "get",
id: taskId,
json: 1,
});
const res = await fetch(`https://ocr.captchaai.com/res.php?${params}`);
const data = await res.json();
if (data.status === 1) return data.request;
if (data.request === "CAPCHA_NOT_READY") continue;
if (data.request === "ERROR_CAPTCHA_UNSOLVABLE")
throw new Error("Unsolvable. Submit a new task.");
throw new Error(`Polling error: ${data.request}`);
}
throw new Error(`Solve timed out after ${timeout / 1000}s`);
}
API 返回了 token,页面却依然拒绝
这是最难排查的一类问题:API 认为任务已经成功,返回了有效 token,但目标网站还是不认账。
token 塞错了字段
有的页面从 g-recaptcha-response 文本框里读取 token,有的用 grecaptcha.getResponse(),还有的页面只认回调。选错注入方式,表单提交会静默失败,不会抛出任何报错。
处理方式: 先检查页面到底期待哪种路径:
# Method 1: Hidden field injection
driver.execute_script(
'document.getElementById("g-recaptcha-response").innerHTML = arguments[0];',
token
)
# Method 2: Callback execution (check data-callback attribute)
driver.execute_script(f'onCaptchaSuccess("{token}");')
# Method 3: Direct form field + submit
driver.execute_script(
'document.querySelector("[name=g-recaptcha-response]").value = arguments[0];',
token
)
driver.find_element("css selector", "form").submit()
回调函数没被调用
如果组件带有 data-callback="onSuccess",或者 grecaptcha.render() 里配置了 callback 属性,单纯填隐藏字段不会有任何效果——你必须主动调用这个回调函数。
处理方式: 找到并调用回调:
// In browser console or Puppeteer/Playwright
// Check for data-callback
const widget = document.querySelector('.g-recaptcha');
const callbackName = widget?.getAttribute('data-callback');
if (callbackName && window[callbackName]) {
window[callbackName](token);
}
token 已经过期
拿到 token 和提交表单之间如果超过 2 分钟,Google 会直接拒绝。这种情况在偏慢的自动化流水线里很常见。
处理方式: 拿到 token 后立即提交表单;如果整条流水线本身偏慢,把求解请求放到靠近提交的那一步,而不是放在流程最开始。
组件加载在 iframe 里
如果 reCAPTCHA 是从另一个域名加载进 iframe 的,pageurl 必须用 iframe 自己的源地址,而不是父页面地址。频繁出现 ERROR_BAD_TOKEN_OR_PAGEURL,基本就是这个原因。
处理方式: 检查页面结构,找到包含 reCAPTCHA 的 iframe,把它的
src作为pageurl。
常见问题
reCAPTCHA v2 在国内网络环境下总是超时,是什么原因?
reCAPTCHA 组件依赖 Google 域名加载的前端脚本,国内网络访问该域名的稳定性本身就不如国际网络环境,这是常见的超时来源之一。排查顺序建议是:先在能正常加载 Google 服务的网络环境下确认 googlekey、pageurl 参数没有问题,排除掉参数错误的可能,再评估当前网络链路是否稳定。国内站点更常见的是 GeeTest(极验)而不是 reCAPTCHA,如果你同时对接两种验证码,识别逻辑不能直接复用。
CaptchaAI 的线程数和 reCAPTCHA v2 的识别速度有关系吗?
线程数决定你能同时并发处理多少个任务,不是决定单个任务的识别耗时。如果批量任务下频繁遇到排队变慢,先确认当前套餐的并发线程上限是否够用,而不是一味调大超时时间;单个 reCAPTCHA v2 任务本身的识别耗时通常在 15–60 秒区间波动。
CAPCHA_NOT_READY 是什么意思,需要处理吗?
这不是报错,只是说明识别还在进行中,不需要额外处理。等 5 秒后再轮询一次 res.php 即可。reCAPTCHA v2 的识别耗时通常在 15–60 秒,如果持续超过 3 分钟还没返回结果,再考虑是不是参数本身有问题。
token 返回了,表单却还是提交失败,该按什么顺序排查?
按顺序检查三点:① 页面是不是需要回调?看组件上有没有 data-callback;② pageurl 是否精确匹配组件实际加载的地址,尤其是组件在 iframe 里的情况;③ 从拿到 token 到提交表单,是否已经超过了 2 分钟的有效期。这三个原因覆盖了绝大多数“token 有效但页面拒绝”的场景。
把这套排查流程用起来
- 核对输入参数 — 从
data-sitekey提取googlekey,用组件实际加载的页面地址作为pageurl(留意 iframe) - 确认注入方式 — 判断页面到底要隐藏字段、回调,还是两者都要
- 拿到就提交 — token 有效期只有 2 分钟,越快提交越稳
- 补全错误处理 — 直接套用上面的代码示例,覆盖每一种错误类型
开始用 CaptchaAI 求解器 识别 reCAPTCHA v2,API Key 在 captchaai.com/api.php 获取。
相关指南
- reCAPTCHA v2 API 识别教程 — 从零跑通一次完整请求的分步教程
- reCAPTCHA v2 回调场景处理指南 — 专门讲回调触发的坑
- reCAPTCHA 图片网格挑战详解 — 网格挑战的识别原理
- CaptchaAI 错误码完整参考 — 所有错误码一次查全