如何在轮询结果时修复 CAPCHA_NOT_READY

capcha_not_ready - How to Fix CAPCHA_NOT_READY When Polling for a Result

简短回答: CAPCHA_NOT_READY 并不是错误。它是 API 在告诉你验证码仍在识别中,而你查询得太早了。你的请求没有任何需要修复的地方。你只需要正确的轮询节奏,以及一个知道何时停止的循环。本指南将涵盖这两点,还有那个会把正常脚本变得令人困惑的一次性读取行为。

CAPCHA_NOT_READY 实际含义

当你向 /in.php 提交验证码时,你得到的是一个 ID,而不是答案。识别在后台进行。然后你用该 ID 轮询 /res.php ,直到答案就绪。

在此之前,每次轮询都返回相同的字符串:

# Poll for the result. Note action=get and the id from /in.php.
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=CAPTCHA_ID"

# Still working:
CAPCHA_NOT_READY

# Done:
OK|03AGdBq26Sxo...

是的,它拼写为 CAPCHA,而不是 验证码。这个拼写错误自 2captcha API 诞生之初就存在。CapSkip 与该 API 即插即用兼容,因此故意保留了这个拼写错误。如果纠正它,每一个检查该确切字符串的现有客户端库都会失效。请在你的代码中精确匹配它。

按正确的节奏轮询

大多数人频繁遇到这个问题,是因为他们在提交后立即轮询。不同验证码类型所需时间差别很大,因此不应对所有类型在同一时刻进行首次检查。

类型首次检查前等待然后每隔
图片 / 文字1 秒5 秒
reCAPTCHA v215 到 20 秒5 秒
reCAPTCHA v310 到 15 秒5 秒
极验约 5 秒5 秒
Cloudflare Turnstile约 5 秒5 秒

比每 5 秒更快地轮询并不会让识别更快,只会浪费请求。

一个会终止的轮询循环

Shell 版本,清晰地展示其结构:

# Submit, capture the id, give it a head start, then poll.
ID=$(curl -s "http://127.0.0.1:8080/in.php?key=YOUR_API_KEY&method=userrecaptcha&googlekey=YOUR_SITEKEY&pageurl=https://example.com" | cut -d'|' -f2)

sleep 15
while :; do
  RES=$(curl -s "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=$ID")
  [ "$RES" = "CAPCHA_NOT_READY" ] || break
  sleep 5
done
echo "$RES"

那个循环有一个值得指出的缺陷:如果上游出问题,它会永远运行。在真实代码中,请给它设上限。

# pip install requests
import time
import requests

BASE = "http://127.0.0.1:8080"

def solve_recaptcha(sitekey, page_url, api_key="YOUR_API_KEY", timeout=180):
    task = requests.get(BASE + "/in.php", params={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    }).json()
    task_id = task["request"]

    time.sleep(15)                      # reCAPTCHA needs a head start
    deadline = time.monotonic() + timeout

    while time.monotonic() < deadline:
        res = requests.get(BASE + "/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
        }).text.strip()

        if res.startswith("OK|"):
            return res.split("|", 1)[1]

        # Anything that is not the pending string is terminal.
        if res != "CAPCHA_NOT_READY":
            raise RuntimeError(res or "empty response: already read, or bad id")

        time.sleep(5)

    raise TimeoutError("gave up after %ss" % timeout)

三点让它更安全:一个截止时间使其不会挂起;把任何非 pending 字符串都视为终止状态,使真正的错误立即显现;以及只读取一次答案。

或者完全跳过循环

如果你使用的是官方 SDK 之一,上述问题都与你无关。轮询在调用内部完成,你会直接拿回 token,因此 CAPCHA_NOT_READY 永远不会到达你的代码。

# pip install capskip
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# Submit and poll happen inside this one call.
result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
)

print(result["code"])   # token, ready to inject
// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
const result = await solver.recaptcha('YOUR_SITEKEY', 'https://example.com/page-with-recaptcha');

console.log(result.code);
// composer require capskip/capskip
use CapSkip\CapSkip;

$solver = new CapSkip(['host' => '127.0.0.1', 'port' => 8080]);
$result = $solver->recaptcha('YOUR_SITEKEY', 'https://example.com/page-with-recaptcha');

echo $result['code'];
// dotnet add package CapSkip
using CapSkip;

var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
var result = await solver.RecaptchaAsync("YOUR_SITEKEY", "https://example.com/page-with-recaptcha");

Console.WriteLine(result.Code);

四种语言的完整方法签名都在 CAPTCHA 识别 SDK 页面上。对于 Go、Java、Ruby 或任何没有官方包的语言,上面的原始循环仍是你需要的。

只读一次的陷阱

这是那种会浪费你一个下午的问题。 每个结果只能读取一次。 成功读取后再次轮询,你得到的是空响应,而不是你已经拿到的 token。

因此,空响应体并不意味着“仍在处理”。它意味着两件事之一:

  • 你已经取回了这个结果并把它丢弃了
  • 该 ID 不存在,通常是解析时弄错或截断的 ID OK|ID

一拿到 token 就立即存储它。不要为了“确认”而重新轮询。

当它确实成为问题时

如果指定了 CAPCHA_NOT_READY 始终无法解决时,pending 字符串是症状而非原因。请按顺序检查以下各项:

响应含义修复
ERROR_CAPTCHA_UNSOLVABLE已尝试识别但失败核实 sitekey 和 pageurl 是否为实时有效的,然后重新提交
ERROR_WRONG_ID_FORMAT该 ID 不是有效整数你在解析 OK|ID 时出错了。按竖线分割,取第 2 个字段
ERROR_GOOGLEKEYsitekey 在提交时被拒绝从实时页面重新读取它,而不是从缓存源
空响应体已读取,或未知 ID在首次读取时存储结果
pending 超过 3 分钟识别引擎未运行或不可达确认服务在所配置的端口上处于运行状态。参见 设置指南

完整的错误字符串列表见 API 文档.

常见问题

CAPCHA_NOT_READY 是我应该记录的错误吗?

不是作为错误。它是正常的进行中状态,每次识别你都会看到它数次。如果要记录,请以 debug 级别记录,否则你会把真正的失败淹没在噪声里。

更快地轮询会更早返回答案吗?

不会。识别时间与你询问的频率无关。每 5 秒是文档规定的间隔,更快只是浪费请求。

为什么拼写为 CAPCHA 而不是 CAPTCHA?

这是原始 2captcha API 中的一个旧拼写错误,已成为约定的一部分。CapSkip 与该 API 即插即用兼容,因此原样保留该字符串。修正拼写会破坏每一个检查它的客户端。

我能以 JSON 形式获取 pending 状态吗?

可以。在请求中添加 json=1 ,响应就会以对象形式返回,包含 statusrequest 字段,而非纯文本。pending 字符串本身不变。

小结

在首次检查前给识别一点提前量,每 5 秒重试一次,把除 CAPCHA_NOT_READY 以外的任何内容都视为终止,用截止时间给循环设上限,并且只读取一次结果。这就是全部套路。

如果你根本不想编写这个循环,CapSkip 是一款 验证码识别工具 在本地运行,并提供 Python、Node.js、PHP 和 .NET 的 SDK,为你处理轮询。