如何用 Node.js 和 TypeScript 识别 Cloudflare Turnstile

Cloudflare Turnstile 以两种形态出现,它们需要不同的代码。表单中的小组件只是一个两参数调用。而整页插页式挑战则需要从页面抓取两个额外的值,并且只有当你回传识别工具所使用的 user agent 时,token 才会被接受。正是最后这一条要求,导致人们得到那些看起来完美无缺、却每次都失败的 token。
如何区分两者
| 小组件 | 挑战页面 | |
|---|---|---|
| 外观 | 可用表单中的一个复选框 | 整页插页式拦截,页面被阻止 |
| 需要 cData 和 chlPageData | 否 | 是 |
| 需要回传的 user agent | 否 | 是 |
我们的 Turnstile 在线演示 运行的是小组件版本,当你在判断自己面对的是哪一种时,它是一个有用的参考。
设置
npm install capskip
const { CapSkip } = require('capskip');
const solver = new CapSkip({
host: '127.0.0.1',
port: 8080,
recaptchaTimeout: 300, // seconds, also covers Turnstile
});CapSkip 在本地识别,因此在任何调用成功之前,桌面应用必须处于运行状态。
小组件模式
const result = await solver.turnstile( '0x4AAAAAAA...', // the data-sitekey attribute 'https://example.com/login', ); console.log(result.code); // cf-turnstile-response token
将 result.code 放入 cf-turnstile-response 字段并提交,无需其他任何操作。
挑战页面需要另外两个值
插页式挑战携带与 token 绑定的每次请求状态。其中有两部分必须随识别请求一起传递:
- cData,作为
data - chlPageData,作为
pagedata
这两个值都存在于挑战页面本身,而不是某个表单属性中,因此必须先获取页面才能进行识别。在标准的 Cloudflare 插页式拦截中,它们与 sitekey 一起位于页面自身的挑战选项对象上。它们是一次性的,且与该次页面加载绑定,所以应当获取与识别一并完成,而不要缓存。
const result = await solver.turnstile(sitekey, pageUrl, {
data: cData, // the cData value
pagedata: chlPageData, // the chlPageData value
action: 'managed', // optional, when the page declares one
});
console.log(result.code);
console.log(result.userAgent); // required for the next stepuser agent 不是可选项
Turnstile 将 token 与生成它的浏览器指纹绑定,而 user agent 是该指纹的一部分。CapSkip 会返回它所使用的那个,位于 result.userAgent。如果改用 Node 默认的 fetch user agent 提交 token,Cloudflare 就会拒绝一个本来完全有效的 token。
userAgent 仅在 Turnstile 时才会被填充。对于其他所有验证码类型它都为 undefined,这正是复用现成 reCAPTCHA 辅助代码的人会中招的原因。
const response = await fetch(pageUrl, {
method: 'POST',
headers: {
// Send back the exact user agent the solve was performed with.
'User-Agent': result.userAgent,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'cf-turnstile-response': result.code,
}),
});如果 token 被拒绝而 cData 是新鲜的,那几乎肯定就是原因所在。
TypeScript
import { CapSkip, SolveResult } from 'capskip';
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
const result: SolveResult = await solver.turnstile(sitekey, pageUrl);
// userAgent is optional on the type, because only Turnstile populates it.
if (result.userAgent) {
// safe to forward
}类型定义随包一起提供,因此无需额外 @types 安装。以下项上的可选类型标注 userAgent 是一个有用的提醒:编译器不会让你忘记其他验证码类型会将其留空。
代理与并发
// Solve through the same egress you will submit from.
await solver.turnstile(sitekey, pageUrl, {
data: cData,
pagedata: chlPageData,
proxy: { type: 'HTTPS', uri: 'user:[email protected]:3128' },
});
// Several at once.
const results = await Promise.all(targets.map((t) =>
solver.turnstile(t.sitekey, t.url)));代理适用于 Turnstile、reCAPTCHA 和极验,但不适用于图片验证码,因为图片验证码从不与目标站点交互。对于挑战页面,每次识别都需要各自新获取的 cData,因此应在映射函数内部获取,而不是事先批量获取。
常见问题
小组件需要 cData 吗?
不需要,而且传入空值只会导致识别失败,而不会有任何帮助。只有整页插页式挑战才会用到它们。
token 看起来没问题,但网站却拒绝它。
几乎总是 user agent 的问题。请使用 result.userAgent 提交,而不是使用你的 HTTP 客户端默认发送的那个。第二可能的原因是过期的 cData,它与单次页面加载绑定。
我可以将它与 Puppeteer 一起使用吗?
可以,而且配合得很好:用以下方式从已加载的页面读取 cData page.evaluate,用本 SDK 进行识别,然后在继续之前将相同的 user agent 设置到页面上。我们的 Node.js 验证码识别 页面介绍了自动化方面的内容。
小结
小组件需要一个 sitekey 和一个 URL。挑战页面需要 data 和 pagedata 从页面实时读取,并且 token 必须连同以下内容一起提交: result.userAgent。其余都是普通的 Promise 处理。
其他语言见 Cloudflare Turnstile 识别 页面,参数细节见 API 文档,更全面的 Node 接口见 Node.js 验证码识别 页面。CapSkip 是一款 无限量验证码识别工具 ,它运行在你自己的硬件上。
