如何在 Postman 里识别验证码并轮询获取 token

你不用写一行应用代码,就能在 Postman 里识别验证码。这个 API 兼容 2captcha,而且跑在你自己的机器上,所以只要两个请求:把任务 POST 到 /in.php 并保存 ID,然后轮询 /res.php 直到答案返回。一段简短的 post-response 脚本能把第二个请求变成循环,一个集合变量在两者之间传递 ID。本文给出确切的字段、两段脚本,以及那个让循环不会永远跑下去的设置。
你需要什么
- Postman 桌面应用。它直接和 127.0.0.1 通信,不需要任何 agent 参与。网页版在下文另有说明,它还需要多一个部件
- CapSkip 正在运行并且可以访问。Local 模式监听 127.0.0.1 的 8080 端口,适合同一台机器上的客户端;Server 模式监听你的内网或公网 IP,这样笔记本、同事或托管 runner 都能访问到它。两种模式都配置在 连接设置
- 一个 sitekey,以及要识别的页面 URL
别的都不需要。不用 SDK,不用依赖,也不用账号。key 校验默认是关闭的,所以任何非空字符串都能当作 key 的值;而什么都不发,返回的会是 ERROR_WRONG_USER_KEY,不是识别结果。
先设置三个集合变量
把它们放在集合上,而不是放在环境里。集合变量会随导出一起走,所以别人导入之后整套东西照样能用。
| 变量 | 初始值 | 原因 |
|---|---|---|
| baseUrl | http://127.0.0.1:8080 | 识别工具搬到服务器上时,只需要改这一处 |
| apiKey | capskip | 任何非空字符串。校验默认是关闭的 |
| captchaId | 空 | 提交脚本写入它,轮询请求读取它 |
请求 1:提交任务
一个 POST 请求,发往 {{baseUrl}}/in.php,请求体用表单。选择 x-www-form-urlencoded,而不是原始 JSON:这个 API 读取的是表单字段。
| 键 | 值 |
|---|---|
| key | {{apiKey}} |
| method | userrecaptcha |
| googlekey | YOUR_SITEKEY |
| pageurl | https://example.com/page-with-recaptcha |
| json | 1 |
json=1 这个字段在 Postman 里比在终端里更重要。没有它,响应就是纯字符串 OK,后面跟一个竖线和 ID,你还得自己动手切分。加上它,你拿到的是一个对象,格式化显示也能正常工作。
把这段加到 Scripts 标签页的 Post-response 下面。旧版 Postman 把同一个标签页叫作 Tests。
// Post-response script on the submit request.
const body = pm.response.json();
pm.test('task accepted', function () {
pm.expect(body.status).to.eql(1);
});
// Hand the ID to the polling request.
pm.collectionVariables.set('captchaId', body.request);对每一种验证码类型,响应的结构都是一样的。任务被接受时 status 为 1,而你真正关心的值始终在 request 字段里。
{"status": 1, "request": "2122988149"}注意参数名。reCAPTCHA 用 googlekey,Turnstile 用 sitekey,发错这个参数是收到 ERROR_GOOGLEKEY 响应最常见的原因。 Turnstile 识别工具 页面列出了挑战页面所需的完整字段,这类页面还需要从页面上抓取 cData 和 chlPageData 的值。
同一个请求,换成其他八种类型
只有提交请求会变。五个 method 值就覆盖了 CapSkip 支持的每一种类型,各种变体只是同一个表单体上的额外字段,而不是另外的端点。复制一份 Request 1,套用最后一列,请求的其余部分保持原样。
| 类型 | 把 method 设为 | 然后改动请求体 |
|---|---|---|
| 图片验证码,文件上传 | method = post | 把请求体切换成 form-data,并把图片作为 file 附上 |
| 图片验证码,base64 | method = base64 | 去掉 googlekey 和 pageurl,用 body 发送编码后的图片 |
| reCAPTCHA v2 复选框 | method = userrecaptcha | 无需改动。上面演示的就是这个请求 |
| reCAPTCHA v2 Invisible | method = userrecaptcha | 增加 invisible,值为 1 |
| reCAPTCHA Enterprise | method = userrecaptcha | 增加 enterprise,值为 1 |
| reCAPTCHA v3 | method = userrecaptcha | 增加 version 并设为 v3,再加一个 action |
| Turnstile 小组件 | method = turnstile | 把 googlekey 改名为 sitekey |
| Turnstile 挑战页面 | method = turnstile | 把 googlekey 改名为 sitekey,然后加上 data 和 pagedata |
| 极验 v3 | method = geetest | 用 gt 和 challenge 代替 googlekey 发送 |
其中有两种在 Postman 里要格外注意。上传图片是唯一不能用 x-www-form-urlencoded 的,因为文件需要 multipart 请求体,所以要把那一个请求切换成 form-data。极验则有时间限制:它的挑战值大约一分钟就会过期,所以要在发送之前马上去取,而不是复用之前保存的那个。
轮询请求永远不变。这正是把这一切做成一个集合的理由:一个 Request 2 就能服务列表里的每一种类型,因为答案回来的形状始终一样。
请求 2:轮询直到答案返回
一个 POST 请求,发往 {{baseUrl}}/res.php,同样使用表单请求体。
| 键 | 值 |
|---|---|
| key | {{apiKey}} |
| action | get |
| id | {{captchaId}} |
| json | 1 |
任务还在运行时,这里会返回 CAPCHA_NOT_READY,拼写就是这样,少的那个字母也照原样。它是状态,不是错误,唯一正确的应对就是再问一次。
// Post-response script on the polling request.
const body = pm.response.json();
const tries = Number(pm.collectionVariables.get('tries') || 0);
if (body.request === 'CAPCHA_NOT_READY' && tries < 20) {
// Run this same request again.
pm.collectionVariables.set('tries', tries + 1);
pm.execution.setNextRequest(pm.info.requestId);
} else {
pm.collectionVariables.set('captchaToken', body.request);
pm.collectionVariables.set('tries', 0);
pm.execution.setNextRequest(null);
}那段脚本里有三点值得拎出来说,因为每一点都够你搭进去一个下午。
循环只在 Collection Runner 里跑。 Postman 自己的文档写得很明确:发送单个请求时 setNextRequest 不起作用,所以在轮询请求上点 Send 只会发一次,脚本看起来像是死了。请运行整个集合、Postman CLI 或 Newman。
计数器不是可选项。 没有上限,一个永远解不出来的任务就会一直循环,直到你手动停掉这次运行。二十次尝试、每次间隔五秒就是一百多秒的等待,足以从容覆盖 reCAPTCHA v2 任务所需的 15 到 20 秒。
用 ID 来引用请求。 传入 pm.info.requestId 会让循环指向当前正在运行的这个请求,这样以后重命名也不会悄悄把链路弄断。
给 runner 设个延迟,否则你会把端点打爆
上面这段脚本会以 runner 能跑到的最快速度循环,而去轮询一个才跑了两秒的任务纯属白费力气。Collection Runner 的运行配置里有一个 Delay 字段,单位是毫秒,会在每个请求之前生效。把它设成 5000。
第一次轮询之前该等多久,取决于你提交的是什么:
| 类型 | 在此之前不会就绪 |
|---|---|
| 图片验证码 | 1 秒 |
| reCAPTCHA v2 | 15 到 20 秒 |
| reCAPTCHA v3 | 10 到 15 秒 |
| 极验 v3 | 约 5 秒 |
在 Newman 里,同一个设置是一个命令行参数,所以在应用里跑得通的集合,放到 CI 里不用改也能跑。
# npm install -g newman newman run captcha.postman_collection.json --delay-request 5000
改读纯文本响应
去掉 json=1,响应体就会以文本形式返回,偶尔这正是你想要的。两个小助手就能覆盖它。
// Plain text mode: OK|2122988149
const id = pm.response.text().split('|')[1];
// Turnstile also returns the user agent, as a header.
const ua = pm.response.headers.get('X-Turnstile-User-Agent');那个请求头不是摆设。Cloudflare 会把 Turnstile 的 token 和生成它的浏览器指纹绑定,所以提交 token 时必须带上同一个 user agent,否则 token 本身完全有效,站点却照样拒绝。
还有一条规则专门坑 Postman 用户,因为这个应用实在太容易让人点两次 Send: 结果只能读取一次。同一个 ID 的第二次读取会返回空,看起来和识别失败一模一样。第一次读到时就把 token 存进变量。
agent 的问题,以及把 Postman 指向服务器
如果你用的是 Postman 网页版而不是桌面应用,请求会经过一个 agent,而选哪个 agent 决定了 127.0.0.1 到底能不能访问到。Cloud Agent 跑在 Postman 自己的基础设施里,够不到私有网络上的任何东西。Desktop Agent 跑在你的机器上,就够得到。
| 你怎么运行 Postman | 它能访问本地的识别工具吗? |
|---|---|
| 桌面应用 | 可以,不需要 agent |
| 网页版加 Desktop Agent | 可以,agent 会经由你的机器转发 |
| 网页版加 Cloud Agent | 不行,它看不到私有网络 |
Server 模式会改变这笔账。把 CapSkip 跑在一台监听内网或公网 IP 的机器上,把 baseUrl 变量改成指向它,集合里的每个请求就都跟着走了。别的什么都不用变,因为动的只是地址。
# The collection variable is the only edit. baseUrl = http://YOUR_SERVER_IP:8080
建议使用静态公网 IP,具体步骤见 连接设置。Server 模式仍然是你自己的硬件,仍然不计量:它改变的是识别工具监听的位置,而不是它归谁所有。
常见错误及其含义
| 你所看到的 | 原因 | 修复 |
|---|---|---|
ERROR_WRONG_USER_KEY | key 字段收到时是空的,因为 {{apiKey}} 没有解析出来 | 把 apiKey 定义在集合上,而不是放在一个你必须记得去选中的环境里 |
ERROR_WRONG_METHOD | method 或 action 的值拼错了 | 这两个都是表单字段,不是 HTTP 方法:提交请求带一个 method 字段,轮询请求要把 action 字段设成 get |
ERROR_GOOGLEKEY | 把 Turnstile 的 sitekey 发给了 userrecaptcha | 让 method 和字段名对上 |
ERROR_PAGEURL | 页面 URL 没有协议头,或者被截断了 | 带上 https,并且用表单请求体而不是查询字符串 |
| 响应体是空的 | 那个 ID 已经被读过一次了 | 第一次读到就把 token 存下来;要新的就重新提交 |
| 无法发送请求,连接被拒绝 | 那个地址上没有任何东西在监听 | 启动识别工具,或者检查是不是选中了 Desktop Agent |
| 脚本跑了,但没有循环 | 你点的是 Send,而不是运行整个集合 | setNextRequest 只在集合运行时有效 |
API 可能返回的每个错误码的确切文案,都列在 API 文档.
常见问题
我能用一个请求把整件事做完吗?
技术上可以,在 pre-request 脚本里用 pm.sendRequest,但很少值得这么做。那个沙箱是为短脚本设计的,阻塞式轮询会让请求在等待期间看起来像卡死了,没有任何反馈。两个请求加上 runner 能让你看到每一次尝试,而这正是你选择待在 Postman 里而不是写代码的理由。
这套东西能通过 Newman 在 CI 里跑吗?
可以。setNextRequest 在 Newman 和 Postman CLI 里的表现和在应用里完全一样,所以导出的集合不用改就能跑。唯一要解决的是可达性:托管 runner 有自己的回环地址,所以识别工具需要以 Server 模式跑在 runner 能路由到的地址上。
对我能排多少个任务有速率限制吗?
没有。想提交多少任务就提交多少,每个 ID 各自轮询。活儿是在你自己的硬件上干的,而不是在别人的共享队列里,所以上限是你的机器处理得多快,而不是某个配额或余额。
什么时候该从 Postman 毕业
Postman 适合用来验证这个 API 能用、探索一种新的验证码类型,以及给同事一份他们导入就能跑的东西。它不适合生产环境,主要是因为轮询:runner 每一次都要把固定的延迟等满,而 官方 SDK 从 250 毫秒开始轮询并逐步退避,所以识别得快就返回得快。它们还会把错误字符串变成带类型的异常。
在 shell 脚本里做同样的两次调用,请参见 cURL 完整教程。无论哪种方式,调用都只发往你自己的机器,而这正是让一个 无限量验证码识别工具 值得被集合指向的原因:你可以在把字段调对的过程中把集合跑上二十遍,除了自己的 CPU 之外不花任何成本。
