如何修复提交时的 ERROR_GOOGLEKEY 与 ERROR_PAGEURL

error_googlekey - How to Fix ERROR_GOOGLEKEY and ERROR_PAGEURL on Submit

简短回答: 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 使用不同的参数名,两者不能互换。

方式密钥参数名字写错会得到
userrecaptchagooglekeyERROR_GOOGLEKEY
turnstilesitekeyERROR_BAD_PARAMETERS
geetestgt 加上 challengeERROR_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);
}

两分钟检查清单

  1. 把你实际发送的请求体打印出来。不是变量,是请求体本身。
  2. 对照 method 核对参数名: googlekey 对应 reCAPTCHA, sitekey 用于 Turnstile。
  3. 确认这个值非空,且末尾没有换行符。
  4. 从实时页面重新读一次 sitekey,再和你发送的那个对比。
  5. 确认 pageurl 的开头是 http://https://.
  6. 如果页面 URL 带查询字符串,就把调用改成 POST。

第一步解决的问题比其余五步加起来还多,因为差异通常就出在你以为自己发送的内容和真正上线的内容之间。

相邻的错误码分别代表什么

代码含义
ERROR_WRONG_USER_KEYAPI 密钥缺失或为空,这项检查在上述两者之前进行
ERROR_WRONG_METHOD加载的 methodaction 值不是 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_googlekeyERROR_PAGEURL 看作拼写错误,而不是失败。没有创建任务,没有发起尝试,也没有什么需要重试。对照 method 核对参数名,从实时 DOM 而不是常量里读取 sitekey,并发送带协议头的页面 URL。每个 method 的完整参数表都在 reCAPTCHA 识别工具 页面。

调试这件事成本很低,因为用的是 无限量验证码识别工具 ,它就运行在本地。被拒绝的提交不烧额度,也不占配额,所以你可以把同一个请求打二十次,直到找出是哪个字符不对。