如何用 cURL 和原始 HTTP API 识别验证码

solve captcha with curl - How to Solve CAPTCHA with cURL and the Raw HTTP API

你并不需要 SDK。CapSkip API 与 2captcha 兼容,所以只用两次调用就能通过 curl 识别验证码:先把任务 POST 到 /in.php ,拿回一个 ID,然后轮询 /res.php ,直到答案出现。一切都运行在 127.0.0.1:8080上,因此既没有远程端点,也不会按次计费。本指南给出每种验证码类型的确切参数、JSON 响应结构,以及一段可以直接粘贴进终端的脚本。

你需要什么

  • CapSkip 应用正在运行,且本地服务已启动。端口和密钥设置见 设置指南.
  • curl。macOS、所有 Linux 发行版以及 Windows 10 build 1803 及以上版本都自带它。
  • jq ,用于从 JSON 响应里提取字段。它是可选的,但有了它示例就能写成一行。

密钥校验默认关闭,所以任何非空字符串都可以用作 key。宁可随便发一个,也别什么都不发:密钥为空会返回 ERROR_WRONG_USER_KEY.

依赖清单到此为止,因为整个 API 只有两个端点:

端点作用返回
/in.php提交任务一个数字形式的验证码 ID
/res.php查询该 ID 是否已完成答案,或 CAPCHA_NOT_READY

两个端点都接受 GET 或 POST。POST 是更好的习惯,因为带查询参数的页面 URL 会让 GET 请求在第一个未编码的 & 处被悄悄截断。

第 1 步:提交任务

reCAPTCHA v2 是最短的例子。两个值就能确定这个任务:页面上的 sitekey,以及它所在页面的 URL。

# No install step. curl is already on your machine.
curl -X POST http://127.0.0.1:8080/in.php \
  -d "key=YOUR_API_KEY" \
  -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://example.com/page-with-recaptcha"

OK|2122988149   # the number after the pipe is your captcha ID

注意参数名。reCAPTCHA 用的是 googlekey。Turnstile 用的是 sitekey。发送 sitekey 设置为 userrecaptcha 是收到 ERROR_GOOGLEKEY 响应的最常见原因。

第 2 步:轮询结果

先等待,再询问。立刻轮询只会白白浪费一次请求,拿到的还是 CAPCHA_NOT_READY.

sleep 15

curl -X POST http://127.0.0.1:8080/res.php \
  -d "key=YOUR_API_KEY" \
  -d "action=get" \
  -d "id=2122988149"

OK|03AGdBq26...   # the token, ready to inject into the form

关于 /res.php 有两点容易让人踩坑。任务还在跑的时候它会返回 CAPCHA_NOT_READY ,这不是错误,意思是继续轮询。还有, 每个结果只能读取一次,所以答案一到就立刻存下来。第二次读取同一个 ID 会返回空。

第一次轮询前要等多久,取决于验证码类型:

类型首次轮询等待
图片验证码1 秒
reCAPTCHA v215 到 20 秒
reCAPTCHA v310 到 15 秒
极验 v3约 5 秒

加上 json=1 就能解析响应

纯文本格式适合人在终端里看,交给脚本就很别扭。加上 json=1 ,两个端点都会改为返回一个结构稳定的对象。

// in.php with json=1
{"status": 1, "request": "2122988149"}

// res.php with json=1, once it is solved
{"status": 1, "request": "03AGdBq26..."}

// res.php with json=1, still working
{"status": 0, "request": "CAPCHA_NOT_READY"}

status 为 1 表示成功,其他情况一律为 0,而真正有用的值始终放在 request里。于是整件事就变成了两条 jq 表达式。

所有 method 一览表

九种验证码类型,五个 method 取值。各种变体只是额外的参数,而不是新的端点。

类型method必需参数
图片验证码,文件上传postfile
图片验证码,base64base64body
reCAPTCHA v2userrecaptchagooglekey, pageurl
reCAPTCHA v2 Invisibleuserrecaptcha加上 invisible=1
reCAPTCHA Enterpriseuserrecaptcha加上 enterprise=1
reCAPTCHA v3userrecaptcha加上 version=v3, action
Turnstile 小组件turnstilesitekey, pageurl
Turnstile 挑战页面turnstile加上 data, pagedata
极验 v3geetestgt, challenge, pageurl

图片既可以作为表单文件上传,也可以以 base64 放进请求体:

# File upload. Note the @ in front of the path. Use -F for every
# field here: curl refuses to mix -F and -d in one request.
curl -X POST http://127.0.0.1:8080/in.php \
  -F "key=YOUR_API_KEY" -F "method=post" -F "[email protected]"

# Or send the bytes inline, already base64 encoded.
curl -X POST http://127.0.0.1:8080/in.php \
  -d "key=YOUR_API_KEY" \
  -d "method=base64" \
  --data-urlencode "body=$(base64 < captcha.png | tr -d '\n')"

使用 --data-urlencode 适用于任何包含 +, /=的内容。base64 数据这三个字符全都有,而普通的 -d 会把它们弄坏。

Turnstile 还会一并返回 user agent

Cloudflare 会把 token 绑定到生成它的浏览器指纹上,所以用另一个 user agent 提交这个 token 就会被拒绝,哪怕 token 本身完全有效。原始 API 会在两个地方给出实际使用的那一个:

  • 使用 json=1时,作为响应上的 userAgent 字段。
  • 在纯文本模式下,作为响应头 X-Turnstile-User-Agent 响应头读取相同的值。
# -i prints the headers, which is where the user agent lives
# when you are not using json=1.
curl -i -X POST http://127.0.0.1:8080/res.php \
  -d "key=YOUR_API_KEY" -d "action=get" -d "id=2122988149"

X-Turnstile-User-Agent: Mozilla/5.0 ...
OK|0.abc123...

整页挑战还需要 data (也就是 cData 值)和 pagedata (chlPageData),要在提交前立刻从页面上抓取。小组件模式两者都不需要。 Turnstile 识别工具 页面对这个区别有更详细的说明。

极验(GeeTest)的答案是三个字段,而不是一个

极验不会返回单个 token。请求 JSON,你就会拿到该站点自己的前端本来会回传的那三个值。

{
  "status": 1,
  "request": {
    "geetest_challenge": "...",
    "geetest_validate":  "...",
    "geetest_seccode":   "..."
  }
}

加载的 gt 值对每个站点是固定的。而 challenge 值是一次性的,大约一分钟后就失效,所以要在提交前立刻获取,绝不要在长脚本的开头就取。

让识别走代理

两个参数,加到同一个 /in.php 调用里:

curl -X POST http://127.0.0.1:8080/in.php \
  -d "key=YOUR_API_KEY" \
  -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://example.com/page-with-recaptcha" \
  -d "proxy=login:[email protected]:3128" \
  -d "proxytype=HTTPS"

proxytype 接受 HTTP、HTTPS、SOCKS5 或 SOCKS5H。代理只对 reCAPTCHA、Turnstile 和极验生效。图片验证码识别读取的是你手头已有的像素,根本不会接触目标站点,所以代理在那里毫无作用。

一段完整脚本

提交、带上限地轮询、打印 token。大约二十行,唯一的依赖就是 curl.

#!/usr/bin/env bash
set -euo pipefail

API="http://127.0.0.1:8080"
KEY="YOUR_API_KEY"

# Submit and keep only the part after the pipe.
ID=$(curl -s -X POST "$API/in.php" \
  -d "key=$KEY" -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://example.com/page-with-recaptcha" | cut -d'|' -f2)

sleep 15

# Poll every 5s, give up after 20 tries so this cannot hang forever.
for _ in $(seq 20); do
  R=$(curl -s -X POST "$API/res.php" -d "key=$KEY" -d "action=get" -d "id=$ID")
  [ "$R" = "CAPCHA_NOT_READY" ] || { echo "${R#OK|}"; exit 0; }
  sleep 5
done

echo "timed out waiting for $ID" >&2; exit 1

加载的 || { ...; exit 0; } 分支会在任何不等于 CAPCHA_NOT_READY的内容上触发,其中也包括错误码。这是刻意为之:出错意味着停下,而不是继续轮询。

在这一层会遇到的错误

代码含义修复
ERROR_WRONG_USER_KEY密钥缺失或为空发送任意非空的 key
ERROR_WRONG_METHOD错误的 methodaction对照上表检查拼写
ERROR_BAD_PARAMETERS缺少某个必需参数对照上面的必需参数列检查
ERROR_GOOGLEKEY加载的 googlekey 值被拒绝你可能误把 sitekey 发过来了
ERROR_PAGEURL加载的 pageurl 值被拒绝带上协议头,并用 POST 而不是 GET
CAPCHA_NOT_READY仍在处理中这不是错误。继续轮询同一个 ID
响应为空已经读过,或者根本没有这个 ID结果只能读一次。第一次拿到就存下来

API 可能返回的每个错误码的确切文案,都列在 API 文档.

什么时候该放下 curl

原始 HTTP 非常适合冒烟测试、shell 管道,或者没有官方客户端的语言。而对于应用代码, CapSkip SDK 值得为它多加一个依赖,最重要的理由只有一个:它们不会按固定间隔轮询。它们从 250 毫秒起步,逐步退避到 pollingInterval,因此一次识别通常比上面那个手写循环更快返回,而后者每次都要老老实实等满 15 秒。

它们还会把错误字符串变成有类型的异常,并替你处理好 Turnstile 的 user agent 和极验的三字段答案。Python、Node.js、PHP 和 .NET 都有官方客户端。

常见问题

我可以用 GET 代替 POST 吗?

可以,两个端点都接受。问题在于, pageurl 如果自身带有查询字符串,就会在第一个未编码的 & 处被截断,于是你会收到 ERROR_PAGEURL ,或者对着错误的页面完成一次识别。如果非用 GET 不可,先把 URL 交给 --data-urlencode 处理一遍。

我该发送什么 API 密钥?

任意非空字符串。密钥校验默认关闭,因为服务只监听 localhost,所以 key=capskip 也完全可用。但它必须存在:省略它,你得到的是 ERROR_WRONG_USER_KEY ,而不是识别结果。

在 Windows PowerShell 里能用吗?

使用 curl.exe ,必须这样显式写出来。在 PowerShell 中, curlInvoke-WebRequest的别名,而后者并不认识 -d ,会抛出一个看起来像 API 问题的参数错误。写成 curl.exe 就能绕过这个别名。

我可以同时跑多个识别任务吗?

可以。你想提交多少任务就提交多少,然后各自独立轮询每个 ID。没有任何东西会把它们串行化,也没有每分钟配额,因为计算发生在你自己的硬件上,而不是在共享队列里。

小结

把任务 POST 到 /in.php,保存返回的 ID,按类型等待相应的时长,然后轮询 /res.php ,直到拿到的东西不再是 CAPCHA_NOT_READY。若响应由脚本读取,请加上 json=1 。注意两个命名陷阱: googlekey 用于 reCAPTCHA,而 sitekey 用于 Turnstile;另外别忘了结果只能读取一次。

因为它是一个 验证码识别工具 ,就跑在你自己的机器上,上面那个循环既没有配额要遵守,也没有余额要充值。把它指向 localhost,唯一的限制就是你的 CPU 处理队列的速度。