如何修复提交时的 ERROR_GOOGLEKEY 与 ERROR_PAGEURL

简短回答: ERROR_GOOGLEKEY 表示你发送的 googlekey 值被拒绝了,而 ERROR_PAGEURL 表示你发送的 pageurl 值被拒绝了。两者都来自 /in.php 在提交阶段的返回,因此没有创建任何任务,也就没有验证码 ID 可以轮询。十有八九,原因出在参数名上:reCAPTCHA 要的是 googlekey,Turnstile 要的是 sitekey,发错了就正好得到这个结果。下面讲怎么区分它们,以及各自怎么修。
这两个错误码到底在检查什么
| 代码 | 文档中的含义 | 涉及哪个参数 |
|---|---|---|
ERROR_GOOGLEKEY | 无效的 googlekey 参数 | 你从页面上读到的 sitekey |
ERROR_PAGEURL | 无效的 pageurl 参数 | 小组件所在页面的 URL |
它们是校验失败,而不是识别失败。系统还没有做任何尝试,队列里也什么都没有。
发生在提交阶段,而不是轮询阶段
分清这一点能省下大量调试时间。API 分两个阶段,每个阶段都会产生自己的一类错误。
# Stage 1: submit. These codes appear HERE. curl -X POST http://127.0.0.1:8080/in.php \ -d "key=YOUR_API_KEY" -d "method=userrecaptcha" \ -d "sitekey=YOUR_SITEKEY" \ -d "pageurl=https://example.com/page-with-recaptcha" ERROR_GOOGLEKEY # no ID came back, so there is nothing to poll
对比一下正常的提交,它会返回 OK|2122988149 ,之后即便失败,也是失败在 /res.php上。轮询阶段的失败意味着参数已经被接受,只是识别本身没成功,那是完全不同的排查方向。这种情况请看 ERROR_CAPTCHA_UNSOLVABLE ;如果是密钥本身在这两项检查之前就被拒绝,答案在 ERROR_KEY_DOES_NOT_EXIST 这一篇里。
值为什么会被拒绝
几乎所有情况都出自三个原因,按常见程度排列如下。
原因一:googlekey 还是 sitekey
这个兼容 2captcha 的 API 对每个 method 使用不同的参数名,两者不能互换。
| 方式 | 密钥参数 | 名字写错会得到 |
|---|---|---|
userrecaptcha | googlekey | ERROR_GOOGLEKEY |
turnstile | sitekey | ERROR_BAD_PARAMETERS |
geetest | gt 加上 challenge | ERROR_BAD_PARAMETERS |
陷阱在于,大家平时口头都把这个值叫作 sitekey,而且 HTML 属性名本身就叫 data-sitekey ,reCAPTCHA 和 Turnstile 都一样。只有 reCAPTCHA 的提交参数是以 Google 命名的。如果你复制了一个能用的 Turnstile 调用,只改了 method 设置为 userrecaptcha,那这就是你的 bug。
SDK 把这个差异彻底藏了起来。 solver.recaptcha(sitekey, url) 和 solver.turnstile(sitekey, url) 接受同一个第一参数,并替你映射成正确的线上参数名,这本身就是使用 SDK 的一个好理由。
原因二:密钥过期、被截断或为空
如果参数名没问题,下一个嫌疑对象就是这个值本身。
- 你把它硬编码了。 网站会轮换 sitekey。上个月还能用的常量,今天可能已经作废。
- 它来自缓存的页面源码。 浏览器里的“查看源代码”给你的,可能是比小组件实际渲染所用版本更旧的一份副本。
- 你抓错了属性。 该值位于
data-sitekey,而不是id,name,也不是 iframe 的title. - shell 变量带上了一个换行符。 命令替换会保留末尾的空白字符,而空白不属于合法密钥的一部分。
- 它是空的。 未设置的变量会展开成空,请求发出去就变成了
googlekey=.
从实时 DOM 里读取它,不要相信写死的常量:
// Run in the page, or via your automation tool's evaluate().
// reCAPTCHA and Turnstile both expose it as data-sitekey.
const recaptchaKey = document.querySelector('.g-recaptcha')?.dataset.sitekey;
const turnstileKey = document.querySelector('.cf-turnstile')?.dataset.sitekey;
// Fallback: reCAPTCHA also carries it as k= in the widget iframe URL.
const iframe = document.querySelector('iframe[src*="/recaptcha/"]');
const fromSrc = iframe ? new URL(iframe.src).searchParams.get('k') : null;
console.log(recaptchaKey || fromSrc);加载的 k= 这个回退方案在这类站点上很重要:小组件由 JavaScript 渲染,DOM 里从来不会留下 .g-recaptcha 元素。
原因三:什么样的 pageurl 算无效
ERROR_PAGEURL 是个更窄的问题,通常也很机械。
- 没有协议头。
example.com/login不是 URL。要发送的是https://example.com/login. - 相对路径。
/login是你的爬虫手头正好有的东西,不是 API 需要的东西。 - GET 截断。 如果页面 URL 自身带有查询字符串,而你用 GET 提交又没有编码,第一个 & 之后的一切都会被当成你这次 API 调用的参数来解析。请改用 POST,或者用
--data-urlencode. - 空白字符或引号。 从配置文件里读出来的 URL,可能带着从来没被去掉的引号。
这个 URL 不需要能从运行 CapSkip 的机器上访问到,也不必是你最终 POST 表单的那个 URL。它必须是嵌入小组件的那个页面,因为 token 正是绑定到它上面的。
从 SDK 里看是什么样
SDK 会在数据离开你的进程之前先检查掉明显的问题,其余的原样放行。两种不同的异常,两种不同的修法。
# pip install capskip
from capskip import CapSkip, ApiException, ValidationException
solver = CapSkip(host="127.0.0.1", port=8080)
try:
result = solver.recaptcha(
sitekey=sitekey_from_dom,
url="https://example.com/page-with-recaptcha",
)
except ValidationException:
# Caught locally: empty sitekey, malformed URL, missing argument.
print("fix the values before sending")
except ApiException as e:
# Came back from the API: GOOGLEKEY or PAGEURL rejected.
print(f"rejected at submit: {e}")还可提供一个 ValidationException 意味着你根本没有到达 API。而 ApiException 带着这些错误码返回,意味着你到达了 API,但值被拒绝了。C# 里思路相同,只是由基类型来做这件事:
using CapSkip;
var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
try
{
var result = await solver.RecaptchaAsync(sitekey, pageUrl);
}
catch (ApiException ex)
{
// ERROR_GOOGLEKEY or ERROR_PAGEURL arrives here.
Console.WriteLine(ex.Message);
}
catch (CapSkipError ex)
{
// Everything else the SDK can throw.
Console.WriteLine(ex.Message);
}两分钟检查清单
- 把你实际发送的请求体打印出来。不是变量,是请求体本身。
- 对照 method 核对参数名:
googlekey对应 reCAPTCHA,sitekey用于 Turnstile。 - 确认这个值非空,且末尾没有换行符。
- 从实时页面重新读一次 sitekey,再和你发送的那个对比。
- 确认
pageurl的开头是http://或https://. - 如果页面 URL 带查询字符串,就把调用改成 POST。
第一步解决的问题比其余五步加起来还多,因为差异通常就出在你以为自己发送的内容和真正上线的内容之间。
相邻的错误码分别代表什么
| 代码 | 含义 |
|---|---|
ERROR_WRONG_USER_KEY | API 密钥缺失或为空,这项检查在上述两者之前进行 |
ERROR_WRONG_METHOD | 加载的 method 或 action 值不是 API 认识的取值 |
ERROR_BAD_PARAMETERS | 该 method 所需的某个必需参数完全没有传 |
ERROR_CAPTCHA_UNSOLVABLE | 提交成功了,识别没成功。属于轮询阶段,不是提交阶段 |
CAPCHA_NOT_READY | 这不是错误。继续轮询同一个 ID |
每个错误码及其确切文案都列在 API 文档.
常见问题
遇到 ERROR_GOOGLEKEY 之后该重试吗?
不该。同样的值每次都会以同样的方式被拒绝,重试循环只会白白消耗时间,还把真正的问题掩盖起来。先把值改对,再提交一次。重试是轮询阶段失败时的正确反应,而不是提交阶段。
我的 sitekey 是对的,却还是被拒绝,接下来怎么办?
先找看不见的损坏:命令替换带来的尾随换行、配置文件里的引号,或者被固定宽度数据库字段截断的值。把你发送的字符串长度打印出来。标准的 reCAPTCHA sitekey 是 40 个字符,短于这个数就说明在某处被截掉了。
pageurl 必须能被公网访问吗?
不必。它只用来标识小组件属于哪个页面,所以登录之后才能看到的页面完全没问题。它唯一不能是的是占位符:填一个和承载小组件不同的域名,即使提交成功,拿到的 token 也会被网站拒绝。
Turnstile 的识别会返回 ERROR_GOOGLEKEY 吗?
不该出现,因为 turnstile 方法根本不会读取 googlekey 参数。在 Turnstile 调用里看到它,几乎总是说明 method 还是设成了 userrecaptcha ,那是从复制来的请求里带过来的。先把 method 改对,再发送 sitekey 而不是 googlekey.
小结
把 error_googlekey 和 ERROR_PAGEURL 看作拼写错误,而不是失败。没有创建任务,没有发起尝试,也没有什么需要重试。对照 method 核对参数名,从实时 DOM 而不是常量里读取 sitekey,并发送带协议头的页面 URL。每个 method 的完整参数表都在 reCAPTCHA 识别工具 页面。
调试这件事成本很低,因为用的是 无限量验证码识别工具 ,它就运行在本地。被拒绝的提交不烧额度,也不占配额,所以你可以把同一个请求打二十次,直到找出是哪个字符不对。
