API 文档
关于可用端点、请求格式、参数以及示例响应的参考,帮助你将 API 集成到自己的应用中。
本文档面向希望将 CapSkip 直接集成到自己脚本、应用或自动化系统中的开发者。使用第三方软件的用户请参阅“教程”部分获取设置说明。
CapSkip 模拟广泛使用的验证码识别服务的 API,使其无需任何改动即可连接到兼容的第三方软件。集成通常只需运行 CapSkip。
本文档说明如何提交请求并获取结果。CapSkip 支持多个 API 家族,包括 2Captcha 风格的 API(in.php / res.php)、AntiCaptcha、CapMonster 和 CapSolver 使用的 JSON createTask / getTaskResult API,以及 DeathByCaptcha REST API。同一家族内的服务共享相同的端点、请求和响应格式,每个服务只有基础 URL(主机和端口)不同。
| API 家族 | 服务 |
|---|---|
| 2captcha 风格 | 2captcha.com, rucaptcha.com, solvecaptcha.com, captchas.io |
| JSON(createTask / getTaskResult) | anti-captcha.com, capmonster.cloud, capsolver.com |
| DeathByCaptcha | deathbycaptcha.com |
图片验证码
普通验证码是一张包含扭曲但人类可读文字的图片。要识别它,用户必须输入图片中显示的文字。
要识别普通验证码,请通过 HTTP POST 请求将图片提交到 API 端点。使用配置好的本地地址和端口,将请求直接发送到你的 CapSkip 实例,例如: http://127.0.0.1:PORT/in.php
CapSkip 接受 multipart/form-data 或 Base64 编码格式的图片。
Multipart 示例表单
<form method="post" action="http://127.0.0.1:PORT/in.php" enctype="multipart/form-data"> <input type="hidden" name="method" value="post"> Your key: <input type="text" name="key" value="YOUR_APIKEY"> The CAPTCHA file: <input type="file" name="file"> <input type="submit" value="Upload and get the ID"> </form>
YOUR_APIKEY 表示你的 API 密钥(如果在 CapSkip 中启用了 API 密钥校验)。如果禁用了 API 密钥校验,任何字符串值都会被接受。
Base64 示例表单
<form method="post" action="http://127.0.0.1:PORT/in.php"> <input type="hidden" name="method" value="base64"> Your key: <input type="text" name="key" value="YOUR_APIKEY"> The CAPTCHA file body in base64 format: <textarea name="body">BASE64_FILE</textarea> <input type="submit" value="Upload and get the ID"> </form>
YOUR_APIKEY 表示你的 API 密钥(如果在 CapSkip 中启用了 API 密钥校验)。如果禁用了 API 密钥校验,任何字符串值都会被接受。
BASE64_FILE 是 Base64 编码的图片数据。
POST 请求参数列表
| POST 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| method | 字符串 | 是 |
post – submit the image using multipart/form-data base64:以 Base64 编码字符串提交图片 |
| file | 文件 | 是* |
验证码图片文件。 * 当 method=post 时必填。 |
| body | 字符串 | 是* |
Base64 编码的验证码图片数据。 * 当 method=base64 时必填。 |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应 1:以 JSON 格式返回响应 |
提交验证码(multipart 文件上传):
curl -X POST -F "key=YOUR_API_KEY" -F "method=post" -F "[email protected]" http://127.0.0.1:8080/in.php
提交验证码(base64 编码):
curl -X POST -d "key=YOUR_API_KEY&method=base64&body=BASE64_IMAGE_DATA" http://127.0.0.1:8080/in.php
提交请求后,如果一切正确,CapSkip 会以纯文本返回验证码 ID: OK|12345
如果使用了 json=1 参数,响应将以 JSON 格式返回:
{
"status":1,
"request":"12345"
}等待 1 秒,然后向结果端点(/res.php)发送带有返回的验证码 ID 的 HTTP GET 请求。
如果验证码已被识别,CapSkip 会以纯文本返回结果: OK|TEXT
如果指定了 json=1 ,响应将是:
{
"status":1,
"request":"TEXT"
}如果验证码尚未识别完成,CapSkip 会返回: CAPCHA_NOT_READY
这种情况下,等待 1 秒并重复请求,直到收到最终结果。如果 CapSkip 返回空的响应体,说明结果已被读取或该 ID 不存在。每个结果只能读取一次。
GET 请求参数列表
| GET 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| action | 字符串 | 是 | get:获取所提交验证码的答案。 |
| id | 整数 | 是 |
由以下方法返回的验证码 ID in.php. |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应 1:以 JSON 格式返回响应 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=CAPTCHA_ID"
reCAPTCHA V2
reCAPTCHA v2,也称为“我不是机器人”reCAPTCHA,是一种广泛使用的验证码类型。访客勾选一个复选框后,Google 要么立即放行,要么要求其选出匹配的图片,之后才能提交表单。
要识别 reCAPTCHA v2,请发送 googlekey 和 pageurl 参数,连同 method=userrecaptcha 以及你的 CapSkip API 密钥。
你可以获取 googlekey 使用以下方法之一:
右键点击 reCAPTCHA 小组件并选择 检查。找到一个以如下开头的 URL:
www.google.com/recaptcha/api2/anchor
复制该 URL 中 k 参数的值。或者,在页面源代码中找到 data-sitekey 属性并复制其值。

获得 site key 后,向以下地址提交 HTTP GET 或 POST 请求: http://127.0.0.1:PORT/in.php
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| method | 字符串 | 是 | userrecaptcha:指定一个 reCAPTCHA v2 请求。 |
| googlekey | 字符串 | 是 | 在目标页面上找到的 k 或 data-sitekey 参数的值。 |
| pageurl | 字符串 | 是 | reCAPTCHA 所在页面的完整 URL。 |
| enterprise | 整数 默认:0 | 否 |
1:表示 reCAPTCHA Enterprise v2。 0:标准 reCAPTCHA v2。 |
| invisible | 整数 默认:0 | 否 |
1:表示隐形 reCAPTCHA。 0:标准复选框 reCAPTCHA。 |
| data-s | 字符串 | 否 | 在页面上找到的 data-s 参数的值。适用于 Google 搜索和某些 Google 服务。 |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应。 1:以 JSON 格式返回响应。 |
| proxy | 字符串 | 否 | 代理地址。IP 认证格式: IP:PORT (示例: 123.123.123.123:3128)。登录/密码认证格式: login:password@IP:PORT |
| proxytype | 字符串 | 否 | 代理类型。支持的值: HTTP, HTTPS, SOCKS5, SOCKS5H。默认: HTTP 当提供了 proxy 但省略了 proxytype 时。 |
提交 reCAPTCHA v2(标准):
curl -X POST -d "key=YOUR_API_KEY&method=userrecaptcha&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://example.com" http://127.0.0.1:8080/in.php
提交 reCAPTCHA v2(隐形):
curl -X POST -d "key=YOUR_API_KEY&method=userrecaptcha&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://example.com&invisible=1" http://127.0.0.1:8080/in.php
提交 Enterprise reCAPTCHA v2:
curl -X POST -d "key=YOUR_API_KEY&method=userrecaptcha&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://example.com&enterprise=1" http://127.0.0.1:8080/in.php
提交 Enterprise reCAPTCHA v2(隐形):
curl -X POST -d "key=YOUR_API_KEY&method=userrecaptcha&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://example.com&enterprise=1&invisible=1" http://127.0.0.1:8080/in.php
如果请求成功,CapSkip 会以纯文本返回验证码 ID: OK|12345
如果使用了 json=1 参数,响应将以 JSON 格式返回:
{
"status":1,
"request":"12345"
}如果请求失败,CapSkip 会返回一个错误代码。
等待 15 到 20 秒,然后向结果端点发送 HTTP GET 请求以获取答案: http://127.0.0.1:PORT/res.php
GET 请求参数列表
| GET 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| action | 字符串 | 是 | get:获取所提交验证码的答案。 |
| id | 整数 | 是 |
由以下方法返回的验证码 ID in.php. |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应 1:以 JSON 格式返回响应 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=CAPTCHA_ID"
如果验证码已被识别,CapSkip 会以纯文本或 JSON 响应并返回答案 token。token 看起来类似如下:
03AHJ_Vuve5Asa4koK3KSMyUkCq0vUFCR5Im4CwB7PzO3dCxIo11i53epEraq-uBO5mVm2XRikL8iKOWr0aG50sCuej9bXx5qcviUGSm4iK4NC_Q88flavWhaTXSh0VxoihBwBjXxwXuJZ-WGN5Sy4dtUl2wbpMqAj8Zwup1vyCaQJWFvRjYGWJ_TQBKTXNB5CCOgncqLetmJ6B6Cos7qoQyaB8ZzBOTGf5KSP6e-K9niYs772f53Oof6aJeSUDNjiKG9gN3FTrdwKwdnAwEYX-F37sI_vLB1Zs8NQo0PObHYy0b0sf7WSLkzzcIgW9GR0FwcCCm1P8lB-50GQHPEBJUHNnhJyDzwRoRAkVzrf7UkV8wKCdTwrrWqiYDgbrzURfHc2ESsp020MicJTasSiXmNRgryt-gf50q5BMkiRH7osm4DoUgsjc_XyQiEmQmxl5sqZP7aKsaE-EM00x59XsPzD3m3YI6SRCFRUevSyumBd7KmXE8VuzIO9lgnnbka4-eZynZa6vbB9cO3QjLH0xSG3-egcplD1uLGh79wC34RF49Ui3eHwua4S9XHpH6YBe7gXzz6_mv-o-fxrOuphwfrtwvvi2FGfpTexWvxhqWICMFTTjFBCEGEgj7_IFWEKirXW2RTZCVF0Gid7EtIsoEeZkPbrcUISGmgtiJkJ_KojuKwImF0G0CsTlxYTOU2sPsd5o1JDt65wGniQR2IZufnPbbK76Yh_KI2DY4cUxMfcb2fAXcFMc9dcpHg6f9wBXhUtFYTu6pi5LhhGuhpkiGcv6vWYNxMrpWJW_pV7q8mPilwkAP-zw5MJxkgijl2wDMpM-UUQ_k37FVtf-ndbQAIPG7S469doZMmb5IZYgvcB4ojqCW3Vz6Q
如果验证码尚未识别完成,CapSkip 会返回 CAPCHA_NOT_READY。这种情况下,等待 5 秒并重复请求。如果 CapSkip 返回空的响应体,说明结果已被读取或该 ID 不存在。每个结果只能读取一次。
找到 ID 为 g-recaptcha-response 的元素,并通过移除 display: none 样式使其可见。

请注意: 在某些情况下,页面内容是动态生成的,而
g-recaptcha-responseelement may not appear in the static HTML source. In such situations, inspect the page structure using your browser’s developer tools to locate the dynamically generated element.
作为替代,你可以使用 JavaScript 直接设置 g-recaptcha-response 字段的值:
document.getElementById("g-recaptcha-response").innerHTML="TOKEN";页面上会出现一个输入框。将答案 token 粘贴到该字段并提交表单。
reCAPTCHA V2 回调
在某些情况下,页面没有提交按钮,而是使用回调函数。当 reCAPTCHA 被识别后,该回调函数会自动执行。
POST 和 GET 请求参数列表见此处: reCAPTCHA V2 POST 与 GET 请求参数
回调函数通常定义在 data-callback reCAPTCHA 小组件的属性,例如:
data-callback="myCallbackFunction"
在其他情况下,回调函数被定义为 callback 的 grecaptcha.render() 函数的参数,例如:
grecaptcha.render('example', {
'sitekey' : 'someSitekey',
'callback' : myCallbackFunction,
'theme' : 'dark'
});Another way to locate the callback function is to open the browser’s JavaScript console and inspect the reCAPTCHA configuration object:
___grecaptcha_cfg.clients[0].aa.l.callback
请注意, aa.l 属性可能会有所不同,页面上也可能存在多个 reCAPTCHA 客户端。在这种情况下,你还应检查 clients[1], clients[2],以及其他条目,以定位正确的配置对象。
或者,你可以使用以下脚本自动提取 reCAPTCHA 参数:
function findRecaptchaClients() {
if (typeof (___grecaptcha_cfg) !== 'undefined') {
return Object.entries(___grecaptcha_cfg.clients).map(([cid, client]) => {
const data = { id: cid, version: cid >= 10000 ? 'V3' : 'V2' };
const objects = Object.entries(client).filter(([_, value]) => value && typeof value === 'object');objects.forEach(([toplevelKey, toplevel]) => {
const found = Object.entries(toplevel).find(([_, value]) => (
value && typeof value === 'object' && 'sitekey' in value && 'size' in value
));
if (typeof toplevel === 'object' && toplevel instanceof HTMLElement && toplevel['tagName'] === 'DIV'){
data.pageurl = toplevel.baseURI;
}
if (found) {
const [sublevelKey, sublevel] = found;data.sitekey = sublevel.sitekey;
const callbackKey = data.version === 'V2' ? 'callback' : 'promise-callback';
const callback = sublevel[callbackKey];
if (!callback) {
data.callback = null;
data.function = null;
} else {
data.function = callback;
const keys = [cid, toplevelKey, sublevelKey, callbackKey].map((key) => `['${key}']`).join('');
data.callback = `___grecaptcha_cfg.clients${keys}`;
}
}
});
return data;
});
}
return [];
}最后,调用回调函数:
myCallbackFunction();
或者:
___grecaptcha_cfg.clients[0].aa.l.callback();
在某些情况下,回调函数需要一个参数。大多数情况下,你应该将识别得到的 token 作为该参数传入。例如:
myCallbackFunction('TOKEN');
reCAPTCHA V2 隐形版
reCAPTCHA v2 还有一种隐形模式。你可以在此查看示例:
https://www.google.com/recaptcha/api2/demo?invisible=true
隐形 reCAPTCHA 不显示“我不是机器人”复选框。相反,它通常附加在按钮上,或在页面加载或用户交互(例如点击按钮或提交表单)时自动触发。
在内部,隐形 reCAPTCHA 小组件被渲染在一个隐藏的 <div> 定位到视口可见区域之外的元素,使其对用户不可见。
根据用户的 Cookie 和风险评分,reCAPTCHA 可能会自动通过而不显示挑战。否则,会出现标准的图片挑战。
大多数情况下,挑战完成后会执行一个回调函数。更多详情请参阅上文的回调部分。
POST 和 GET 请求参数列表见此处: reCAPTCHA V2 POST 与 GET 请求参数
如何判断 reCAPTCHA 是否为隐形版?
你可以通过以下指标之一来识别隐形 reCAPTCHA:
“我不是机器人”复选框不可见,但在用户交互后会出现挑战。
reCAPTCHA iframe 的 URL 中包含参数
size=invisible.reCAPTCHA 配置对象包含一个
size属性,其值设置为invisible,例如:___grecaptcha_cfg.clients[0].aa.l.size === "invisible"
通过 API 识别隐形 reCAPTCHA 时,请包含参数: invisible=1
如何在浏览器中处理隐形 reCAPTCHA?
方法 1:使用 JavaScript
将 g-recaptcha-response 字段的值设置为 CapSkip 返回的 token:
document.getElementById("g-recaptcha-response").innerHTML="TOKEN";设置 token 后,执行验证成功后通常会发生的操作。
在大多数情况下,这意味着提交表单。你需要通过其 id, name或其他属性来识别正确的表单,然后触发提交。以下是几个示例:
document.getElementById("recaptcha-demo-form").submit(); //by id "recaptcha-demo-form"
document.getElementsByName("myFormName")[0].submit(); //by element name "myFormName"
document.getElementsByClassName("example").submit(); //by class name "example"在某些情况下,当 reCAPTCHA 被识别后会自动执行一个回调函数。
回调函数通常定义在 data-callback reCAPTCHA 小组件的属性,例如:
data-callback="myCallbackFunction"
在其他情况下,回调函数被定义为 callback 的 grecaptcha.render() 函数的参数,例如:
grecaptcha.render('example', {
'sitekey' : 'someSitekey',
'callback' : myCallbackFunction,
'theme' : 'dark'
});你只需调用该函数:
myCallbackFunction();
方法 2:修改 HTML
移除包含 reCAPTCHA 小组件的 <div> 元素。
<div style="visibility: hidden; position: absolute; width:100%; top: -10000px; left: 0px; right: 0px; transition: visibility 0s linear 0.3s, opacity 0.3s linear; opacity: 0;"> <div style="width: 100%; height: 100%; position: fixed; top: 0px; left: 0px; z-index: 2000000000; background-color: #fff; opacity: 0.5; filter: alpha(opacity=50)"></div> <div style="margin: 0 auto; top: 0px; left: 0px; right: 0px; position: absolute; border: 1px solid #ccc; z-index: 2000000000; background-color: #fff; overflow: hidden;"> <iframe src="https://www.google.com/recaptcha/api2/bframe?hl=en&v=r20170213115309&k=6LfP0CITAAAAAHq9FOgCo7v_fb0-pmmH9VW3ziFs#zglq3yifgkmj" title="reCAPTCHA 挑战" style="width: 100%; height: 100%;" scrolling="no" name="zglq3yifgkmj" frameborder="0"></iframe> </div> </div>
从页面中移除整个 reCAPTCHA 区块。
<div class="">
<!-- BEGIN: ReCAPTCHA implementation example. -->
<div
id="recaptcha-demo"
class="g-recaptcha"
data-sitekey="6LfP0CITAAAAAHq9FOgCo7v_fb0-pmmH9VW3ziFs"
data-callback="onSuccess"
data-bind="recaptcha-demo-submit"
>
<div
class="grecaptcha-badge"
style="width: 256px; height: 60px; transition: right 0.3s ease 0s; position: fixed; bottom: 14px; right: -186px; box-shadow: 0px 0px 5px gray;"
>
<div class="grecaptcha-logo">
<iframe
src="https://www.google.com/recaptcha/api2/anchor?k=6LfP0CITAAAAAHq9FOgCo7v_fb0-pmmH9VW3ziFs&co=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbTo0NDM.&hl=en&v=r20170213115309&size=invisible&cb=uror1hlow5a"
title="reCAPTCHA 小组件"
scrolling="no"
name="undefined"
width="256"
height="60"
frameborder="0"
></iframe>
</div>
<div class="grecaptcha-error"></div>
<textarea
id="g-recaptcha-response"
name="g-recaptcha-response"
class="g-recaptcha-response"
style="width: 250px; height: 40px; border: 1px solid #c1c1c1; margin: 10px 25px; padding: 0px; resize: none; display: none; "
></textarea>
</div>
</div>
<script>
var onSuccess = function (response) {
var errorDivs = document.getElementsByClassName('recaptcha-error');
if (errorDivs.length) {
errorDivs[0].className = '';
}
var errorMsgs = document.getElementsByClassName('recaptcha-error-message');
if (errorMsgs.length) {
errorMsgs[0].parentNode.removeChild(errorMsgs[0]);
}
document.getElementById('recaptcha-demo-form').submit();
};
</script>
<!-- Optional noscript fallback. --><!-- END: ReCAPTCHA implementation example. -->
</div>在被移除的区块位置插入以下代码:
<input type="submit" /> <textarea name="g-recaptcha-response">%g-recaptcha-response%</textarea>
%g-recaptcha-response% 表示从 CapSkip 收到的答案 token。
替换区块后,页面上会出现一个“提交查询”按钮。点击该按钮,将 g-recaptcha-response 值以及所有其他必需的表单数据一起提交到网站。
reCAPTCHA V3
reCAPTCHA v3 是 Google 开发的一种现代验证码机制。它不显示可见的挑战,也不需要用户交互,而是根据交互为人类的可能性给出一个分数。
从技术上讲,reCAPTCHA v3 与 reCAPTCHA v2 类似。网站从 reCAPTCHA API 获得一个 token,然后在 POST 请求中发送到目标服务器,并通过 reCAPTCHA API 验证。
关键区别在于 reCAPTCHA v3 不显示可见的挑战。相反,它返回一个评估用户是人类还是机器人的评分。该评分称为 score ,取值范围为 0.0 到 1.0。评分会发送到网站,网站再据此决定如何处理请求。
还有一个名为 action的附加参数,它让网站能够区分不同的用户交互。验证 token 后,reCAPTCHA API 会返回与该请求关联的 action 名称。
如何使用 CapSkip 识别 reCAPTCHA v3?
首先,确认目标网站正在使用 reCAPTCHA v3。
reCAPTCHA v3 的特征包括:
没有可见的验证码或图片挑战
加载的
api.js脚本带有一个render=SITEKEY参数,例如:https://www.google.com/recaptcha/api.js?render=SITEKEY加载的
___grecaptcha_cfg.clients数组中包含一个具有较高数字索引的条目,例如clients[100000]
要识别 reCAPTCHA v3,请确定以下参数:
- sitekey
可在render的api.js脚本 URL 中找到。它也可能出现在 iframe 的 URL 中、调用grecaptcha.execute()的 JavaScript 代码中,或___grecaptcha_cfg配置对象内部。 - action
通过检查 JavaScript 代码中对以下内容的调用来定位它:grecaptcha.execute(),例如:grecaptcha.execute('SITEKEY', {action: 'do_something'})在某些情况下,查找 action 需要检查页面加载的多个 JavaScript 文件。如果无法确定 action 值,你可以使用默认值"verify". - pageurl
。
理解评分
可接受的评分阈值因网站而异,只能通过测试确定。评分范围为:
0.0 → 很可能是机器人
1.0 → 很可能是人类
大多数网站使用 0.3 到 0.7 之间的阈值,因为即使是正常用户也可能获得较低的评分。
你可以使用以下参数传入你想要的阈值 min_score 参数,但最终评分始终由 Google 在验证时决定,识别工具无法保证。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| method | 字符串 | 是 | userrecaptcha:指定一个 reCAPTCHA 请求。 |
| version | 字符串 | 是 | v3:表示该请求用于 reCAPTCHA v3。 |
| googlekey | 字符串 | 是 | 在目标页面上找到的 data-sitekey 参数的值。 |
| pageurl | 字符串 | 是 | reCAPTCHA 所在页面的完整 URL。 |
| enterprise | 整数 默认:0 | 否 |
1:表示 reCAPTCHA Enterprise v3。 0:标准 reCAPTCHA v3。 |
| action | 字符串 默认:verify | 否 | 在目标页面上找到的 action 在页面上定义的参数的值。 |
| min_score | 浮点数 | 否 | 请求的 token 最低评分。当你的服务器验证 token 时,Google 会给出最终评分,因此此值只是一个提示,并不保证。无论 Google 之后给出的评分如何,CapSkip 都会返回它获得的 token。 |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应。 1:以 JSON 格式返回响应。 |
| proxy | 字符串 | 否 | 代理地址。IP 认证格式: IP:PORT (示例: 123.123.123.123:3128)。登录/密码认证格式: login:password@IP:PORT |
| proxytype | 字符串 | 否 | 代理类型。支持的值: HTTP, HTTPS, SOCKS5, SOCKS5H。默认: HTTP 当提供了 proxy 但省略了 proxytype 时。 |
提交 reCAPTCHA v3:
curl -X POST -d "key=YOUR_API_KEY&method=userrecaptcha&version=v3&action=submit&min_score=0.7&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://example.com" http://127.0.0.1:8080/in.php
提交 Enterprise reCAPTCHA v3:
curl -X POST -d "key=YOUR_API_KEY&method=userrecaptcha&version=v3&action=submit&min_score=0.7&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://example.com&enterprise=1" http://127.0.0.1:8080/in.php
如果请求成功,CapSkip 会以纯文本返回验证码 ID: OK|12345
如果使用了 json=1 参数,响应将以 JSON 格式返回:
{
"status":1,
"request":"12345"
}如果发生错误,CapSkip 会返回一个错误代码。
等待 10 到 15 秒,然后向结果端点发送 HTTP GET 请求: http://127.0.0.1:PORT/res.php
在请求中提供返回的验证码 ID。下表列出了全部可用参数。
如果验证码识别成功,CapSkip 会以纯文本或 JSON 格式返回结果。返回值是一个类似如下的验证 token:
03AHJ_Vuve5Asa4koK3KSMyUkCq0vUFCR5Im4CwB7PzO3dCxIo11i53epEraq-uBO5mVm2XRikL8iKOWr0aG50sCuej9bXx5qcviUGSm4iK4NC_Q88flavWhaTXSh0VxoihBwBjXxwXuJZ-WGN5Sy4dtUl2wbpMqAj8Zwup1vyCaQJWFvRjYGWJ_TQBKTXNB5CCOgncqLetmJ6B6Cos7qoQyaB8ZzBOTGf5KSP6e-K9niYs772f53Oof6aJeSUDNjiKG9gN3FTrdwKwdnAwEYX-F37sI_vLB1Zs8NQo0PObHYy0b0sf7WSLkzzcIgW9GR0FwcCCm1P8lB--gf50q5BMkiRH7osm4DoUgsjc_XyQiEmQmxl5sqZP7aKsaE-EM00x59XsPzD3m3YI6SRCFRUevSyumBd7KmXE8VuzIO9lgnnbka4-eZynZa6vbB9cO3QjLH0xSG3--o-fxrOuphwfrtwvvi2FGfpTexWvxhqWICMFTTjFBCEGEgj7_IFWEKirXW2RTZCVF0Gid7EtIsoEeZkPbrcUISGmgtiJkJ_KojuKwImF0G0CsTlxYTOU2sPsd5o1JDt65wGniQR2IZufnPbbK76Yh_KI2DY4cUxMfcb2fAXcFMc9dcpHg6f9wBXhUtFYTu6pi5LhhGuhpkiGcv6vWYNxMrpWJW_pV7q8mPilwkAP-zw5MJxkgijl2wDMpM-UUQ_k37FVtf-ndbQAIPG7S469doZMmb5IZYgvcB4ojqCW3Vz6Q
如果验证码尚未识别完成,CapSkip 会返回 CAPCHA_NOT_READY。等待 5 秒并重复请求。如果 CapSkip 返回空的响应体,说明结果已被读取或该 ID 不存在。每个结果只能读取一次。
GET 请求参数列表
| GET 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| action | 字符串 | 是 | get:获取所提交验证码的答案。 |
| id | 整数 | 是 |
由以下方法返回的验证码 ID in.php. |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应 1:以 JSON 格式返回响应 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=CAPTCHA_ID"
从 CapSkip 收到 token 后,你必须将它正确提交到目标网站。理解其工作方式的最佳方法是观察作为正常用户完成验证时发送的请求。大多数浏览器都提供开发者工具,其中的 Network 标签可让你检查发出的请求。
在大多数情况下,token 通过 POST 请求发送。参数名可能是 g-recaptcha-response,与 reCAPTCHA v2 类似,或类似于 g-recaptcha-response-100000。在某些实现中,可能使用不同的参数名。
你应检查网络请求,确定 token 的传输方式,然后据此构造你的请求。
reCAPTCHA Enterprise
reCAPTCHA Enterprise 是 Google reCAPTCHA 系统的高级版本。它可以在 v2 和 v3 两种模式下运行,为网站管理员提供额外的控制,包括评估并报告某次交互是人类还是自动化的能力。
如何识别 reCAPTCHA Enterprise?
第一步是确定该网站是否使用 Enterprise 版本的 reCAPTCHA。
reCAPTCHA Enterprise 的关键特征包括:
页面加载
enterprise.js而不是api.js,例如:<script src="https://recaptcha.net/recaptcha/enterprise.js" async defer></script>
网站的 JavaScript 代码调用
grecaptcha.enterprise.METHOD而不是grecaptcha.METHOD
接下来,确定使用的是哪种实现:v2、隐形 v2 还是 v3。通常可以通过分析小组件的渲染方式及其在页面上的行为来判断。
按照下面的流程图确定正确的实现。它适用于绝大多数情况。

按照针对 reCAPTCHA v2 或 v3 所述的相同方式确定验证码参数。
对于 v2 Enterprise 实现,可能有额外的可选数据。在大多数情况下,这是在 s 或 data-s 参数。如果存在,请通过以下方式将该值包含在你的请求中 data-s 参数中定义的自定义字符串。
POST 和 GET 请求参数列表见此处: reCAPTCHA V2 POST 与 GET 请求参数
对于 v3 Enterprise 实现,你可能还需要 action 值。要找到它,请检查网站的 JavaScript 代码并定位 grecaptcha.enterprise.execute() 调用。 action 参数通常在该函数中传递。请记住, action 是可选的,在某些情况下可能未定义。
POST 和 GET 请求参数列表见此处: reCAPTCHA V3 POST 与 GET 请求参数
向 /in.php 端点提交请求时,请包含附加参数: enterprise=1
之后,以与识别 reCAPTCHA v2 或 v3 相同的方式与 CapSkip API 交互。返回 token 后,根据目标网站的实现将其提交。
Cloudflare Turnstile
Cloudflare Turnstile 是 Cloudflare 开发的一种现代验证码替代方案。它无需依赖传统的视觉挑战即可验证访客是否为人类。Turnstile 可以作为独立小组件出现,也可以作为挑战页面的一部分,并且在极少或无需用户交互的情况下运行。
有两种常见的 Turnstile 实现:
1. 独立 Turnstile 小组件
独立的 Turnstile 小组件直接嵌入在网站页面中,通常用于保护表单免受自动提交。在这种情况下:
从页面中提取
sitekey。将它连同完整的
pageurl.一起发送到 CapSkip API。
cf-turnstile-response字段中。在某些实现中,token 可能还需要放入
g-recaptcha-response字段中。如果在
turnstile.render()配置中定义了回调,请用返回的 token 执行它。
然后照常提交表单。
2. Cloudflare 挑战页面上的 Turnstile
当网站通过 Cloudflare 代理,并在授予访问权限前显示 Turnstile 挑战页面时会出现这种情况。在这种情况下,你必须提取以下参数:
cDatachlPageDataaction
这些值必须包含在你的 API 请求中。此外,提交 token 时,你必须使用 CapSkip API 返回的 User-Agent 值。
如何提取所需参数?
要提取所需参数,你可以重写 turnstile.render 方法并拦截调用时传入的参数。例如,将以下 JavaScript 代码注入页面。该脚本必须在 Turnstile 小组件加载之前执行,才能成功捕获参数。
const i = setInterval(()=>{
if (window.turnstile) {
clearInterval(i)
window.turnstile.render = (a,b) => {
let p = {
method: "turnstile",
key: "YOUR_API_KEY",
sitekey: b.sitekey,
pageurl: window.location.href,
data: b.cData,
pagedata: b.chlPageData,
action: b.action,
userAgent: navigator.userAgent,
json: 1
}
console.log(JSON.stringify(p))
window.tsCallback = b.callback
return 'foo'
}
}
},50)POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| method | 字符串 | 是 | turnstile:指定一个 Cloudflare Turnstile 请求。 |
| sitekey | 字符串 | 是 | 在目标页面上找到的 data-sitekey 参数的值。 |
| pageurl | 字符串 | 是 | Turnstile 挑战所在页面的完整 URL。 |
| action | 字符串 | 否 |
在 data-action 属性中定义或传给 turnstile.render(). |
| data | 字符串 | 否 |
的可选 action 值。 cData 传给 turnstile.render() 或在 data-cdata 属性中定义的值。 |
| pagedata | 字符串 | 否 |
的可选 action 值。 chlPageData 传给 turnstile.render(). |
| json | 整数 默认:0 | 否 |
0:以纯文本返回响应。 1:以 JSON 格式返回响应。 |
| proxy | 字符串 | 否 | 代理地址。IP 认证格式: IP:PORT (示例: 123.123.123.123:3128)。登录/密码认证格式: login:password@IP:PORT |
| proxytype | 字符串 | 否 | 代理类型。支持的值: HTTP, HTTPS, SOCKS5, SOCKS5H。默认: HTTP 当提供了 proxy 但省略了 proxytype 时。 |
提交 Turnstile(独立):
curl -X POST -d "key=YOUR_API_KEY&method=turnstile&sitekey=0x4AAAAAAABUYP0XeMJF0xoy&pageurl=https://example.com" http://127.0.0.1:8080/in.php
提交 Turnstile(挑战,可选 action、data、pagedata):
curl -X POST -d "key=YOUR_API_KEY&method=turnstile&sitekey=0x4AAAAAAABUYP0XeMJF0xoy&pageurl=https://example.com&action=managed&data=...&pagedata=..." http://127.0.0.1:8080/in.php
如果请求成功,CapSkip 会以纯文本返回验证码 ID: OK|12345
如果使用了 json=1 参数,响应将以 JSON 格式返回:
{
"status":1,
"request":"12345"
}如果发生错误,CapSkip 会返回一个错误代码。
使用返回的 ID 从 API 的 /res.php 端点获取结果。
GET 请求参数列表
| GET 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是 | 你的 CapSkip API 密钥。 |
| action | 字符串 | 是 | get:获取所提交验证码的答案。 |
| id | 整数 | 是 |
由以下方法返回的验证码 ID in.php. |
| json | 整数 默认:0 | 否 |
0 - 以纯文本返回响应。 1 - 以 JSON 格式返回响应,包含 userAgent 值。 |
对于 Cloudflare Turnstile,识别工具会使用特定的浏览器 User-Agent,你在提交 token 时必须发送相同的 User-Agent。使用 json=1 时,响应中包含一个 userAgent 字段。在纯文本模式下,从 X-Turnstile-User-Agent 响应头读取相同的值。
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=CAPTCHA_ID"
极验 v3 滑块是极验开发的一种交互式验证码。它通过滑块挑战来验证用户,在区分人类与机器人的同时,提供快速无缝的验证体验。
要用 CapSkip 识别极验 v3 验证码,你必须先从目标网站获取所需的验证码参数。所需参数为:
- gt:网站公钥(静态)
- challenge:动态挑战值
- api_server:极验 API 服务器域名(可选)
这些值通常在网站初始化极验时可获得。
重要: 每次识别请求都必须获取一个新的
challenge值。验证码在页面上加载后,之前的值就会失效。你应检查网站的网络请求,找到生成新challenge值的请求,并在每次向 CapSkip 提交识别请求前执行该请求。challenge值。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| method | 字符串 | 是 | 必须为 geetest。指定你提交的是极验 v3 验证码。 |
| gt | 字符串 | 是 | 加载的 gt 从目标网站获取的值。 |
| challenge | 字符串 | 是 | 加载的 challenge 从目标网站获取的值。每次识别请求都必须获取一个新值。 |
| pageurl | 字符串 | 是 | 包含极验验证码的页面的完整 URL。 |
| api_server | 字符串 | 否 | 目标网站使用的极验 API 服务器域名(例如 api.geetest.com 或 api-na.geetest.com). |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
| proxy | 字符串 | 否 | 代理地址。IP 认证格式: IP:PORT (示例: 123.123.123.123:3128)。登录/密码认证格式: login:password@IP:PORT. |
| proxytype | 字符串 | 否 | 代理类型。支持的值: HTTP, HTTPS, SOCKS5, SOCKS5H。默认: HTTP 当提供了 proxy 但省略了 proxytype 时。 |
向你的 CapSkip API 端点(/in.php)提交 HTTP GET 或 POST 请求,method 为 method=geetest。包含上一步获取的所需极验参数,以及包含验证码的页面的完整 URL。
curl -X POST \ -d "key=YOUR_API_KEY" \ -d "method=geetest" \ -d "gt=f1ab2cdefa3456789012345b6c78d90e" \ -d "challenge=12345678abc90123d45678ef90123a456b" \ -d "pageurl=https://www.example.com/" \ -d "api_server=api-na.geetest.com" \ http://127.0.0.1:8080/in.php
如果一切成功,CapSkip 会以纯文本返回验证码 ID:OK|212
如果使用了 json=1 参数,响应将以 JSON 格式返回:
{
"status": 1,
"request": "212"
}否则,CapSkip 会返回相应的错误代码。
等待大约 5 秒,然后向 res.php 端点提交 HTTP GET 请求以获取结果。
GET 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| action | 字符串 | 是 | 指定 get 以获取验证码答案。 |
| id | 整数 | 是 | 由以下请求返回的验证码 ID: in.php 请求。 |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=212&json=1"
如果验证码识别成功,CapSkip 会以 JSON 格式返回答案:
{
"status": 1,
"request": "{\"geetest_challenge\":\"1a2b3456cd67890e12345fab678901c2de\",\"geetest_validate\":\"09fe8d7c6ba54f32e1dcb0a9fedc8765\",\"geetest_seccode\":\"12fe3d4c56789ba01f2e345d6789c012|jordan\"}"
}如果验证码尚未识别完成,CapSkip 会返回: CAPCHA_NOT_READY
等待 5 秒后重复请求。如果发生错误,CapSkip 会返回相应的错误代码。在向目标网站提交请求时,请使用 CapSkip 返回的值,填入以下字段:
geetest_challengegeetest_validategeetest_seccode
ALTCHA 是一种工作量证明(Proof of Work)类验证码。这里没有需要识别的图片,也没有需要播放的音频。目标站点下发一个挑战,客户端必须暴力枚举出满足该挑战的数字。CapSkip 会算出这个数字,并返回小组件本应生成的 payload。
工作原理
它的防御手段是 CPU 计算成本,而不是图像识别。服务器给出一个目标值和一个搜索范围(maxnumber),客户端不断对候选数字做哈希运算,直到其中一个与目标匹配。由此带来两点在验证码类型中并不常见的特性。
第一,识别过程是确定性的。这里没有模型,也没有准确率指标:答案要么就在给定范围之内,要么就是挑战本身格式有误。永远不会出现识别错误的情况。
第二,识别耗时由目标站点决定,而不是由 CapSkip 决定。官方参考小组件默认的范围是 1,000,000,只需几毫秒即可算完。站点可以自行调高这个值,有些站点会设为 999,999,999,对一个普通挑战来说大约相当于 5 亿次哈希运算。如果某个站点识别很慢,请先查看它的 maxnumber 值。
完整流程分为四步:
- 从小组件读取挑战的那个接口处取得该挑战。
- 将其提交到
/in.php连同method=altcha. - 轮询
/res.php直到 token 就绪。 - 将 token 回填到目标表单的
altcha字段中。
识别前需要准备什么
挑战本身,两种形式任选其一。两种都支持,你的爬虫手上已经有哪一种就发哪一种。
| 参数 | 适用场景 |
|---|---|
| challenge_json | 你已经拿到了挑战文档。CapSkip 会在本地直接计算,完全不发起任何网络请求,这是最快的方式。 |
| challenge_url | 你手上只有提供挑战的接口地址。CapSkip 会替你抓取该挑战(如果你传了代理,就通过你的代理抓取),然后再进行计算。 |
在哪里找到挑战
打开浏览器开发者工具,在目标页面切换到 Network(网络)标签页,找到由 <altcha-widget> 元素为获取挑战而发出的那个请求。它的路径通常类似于 /altcha/challenge。该请求的 URL 就是你的 challenge_url,而它返回的 JSON 就是你的 challenge_json.
指定该接口的小组件属性在不同版本之间变过,因此请直接查看页面源码,不要想当然。小组件 v1 和 v2 使用 challengeurl="...",而 v3 及更高版本统一使用 challenge="..." 来同时表示 URL 和内联数据两种情况。有些部署会直接在页面内生成挑战,完全不发起任何请求。
挑战文档的形式如下:
{
"algorithm": "SHA-256",
"challenge": "3dd28253be6cc0c54d95f7f98c517e68a1b2c3d4e5f60718293a4b5c6d7e8f90",
"salt": "46d5b1c8871e5152d902ee3f?expires=1893456000",
"signature": "4b1cf0e0be0f4e5247e50b0f9a4498301234567890abcdef1234567890abcdef",
"maxnumber": 1000000
}挑战会过期,而且时间窗口很短
每个挑战都自带过期时间,它要么出现在 salt 的查询字符串里,要么以 parameters.expiresAt的形式给出。一旦过期,目标站点只会返回一个笼统的验证失败,看起来和答案错误一模一样。短至两分钟的时间窗口也很常见。
请在创建任务前的最后一刻再去获取挑战,并尽快提交 token。不要提前囤积挑战,也不要在用户填写表单期间一直攥着 token。如果你发送的是 challenge_url 而不是 challenge_json,那么当挑战在排队期间过期时,CapSkip 会自动重新抓取。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| method | 字符串 | 是 | 必须为 altcha。表示你提交的是 ALTCHA 挑战。 |
| pageurl | 字符串 | 是 | 挑战来源页面的完整 URL。 |
| challenge_url | 字符串 | 是* | CapSkip 应当从中抓取挑战的接口地址。除非发送了 challenge_json ,否则该参数必填。 |
| challenge_json | 字符串 | 是* | 挑战文档本身,以 JSON 字符串形式提供。除非发送了 challenge_url ,否则该参数必填。 |
| proxy | 字符串 | 否 | 代理地址。接受 IP:PORT, LOGIN:PASSWORD@IP:PORT 或 IP:PORT:LOGIN:PASSWORD。仅用于 challenge_url 的抓取。 |
| proxytype | 字符串 | 否 | 代理类型: HTTP, HTTPS, SOCKS5 或 SOCKS5H. |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
只需发送 challenge_url 或 challenge_json。两者同时发送也是允许的,此时以内联文档为准,因为抓取只会重新取回你已经拥有的内容。
向你的 CapSkip API 端点(/in.php)提交 HTTP GET 或 POST 请求,method 为 method=altcha。表单字段和 JSON 请求体都支持,字段名完全相同;另外 GET 同样可用,因为 ALTCHA 不需要上传任何图片。
提交 ALTCHA(表单字段):
curl -X POST \ -d "key=YOUR_API_KEY" \ -d "method=altcha" \ -d "pageurl=https://www.example.com/signup" \ --data-urlencode "challenge_url=https://www.example.com/captcha/api/altcha/challenge" \ -d "json=1" \ http://127.0.0.1:8080/in.php
提交 ALTCHA(JSON 请求体):
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"key": "YOUR_API_KEY",
"method": "altcha",
"pageurl": "https://www.example.com/signup",
"challenge_url": "https://www.example.com/captcha/api/altcha/challenge",
"json": 1
}' \
http://127.0.0.1:8080/in.phpJSON 请求体支持三件表单编码做不到的事。开关类字段可以写成真正的布尔值 ("json": true), challenge_json 可以直接是嵌套的 JSON 文档,而不必是转义后的字符串;把某个字段设为 null 则等同于没有发送该字段。
如果一切正确,CapSkip 会以纯文本形式返回验证码 ID: OK|2122988149。如果发送了 json=1 参数,返回的则是 JSON 封装格式。无论哪种形式,返回的值都是你用于轮询结果的验证码 ID。
{
"status": 1,
"request": "2122988149"
}否则 CapSkip 会返回相应的错误代码。格式有误的内联挑战会在提交请求时就被拒绝,而不是等到轮询之后才报错,因此你能在出错的那次请求上当场发现问题。
请等待约 5 秒,然后向结果接口(/res.php)发送一个 HTTP GET 请求,并带上你收到的验证码 ID。
GET 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| action | 字符串 | 是 | 指定 get 以获取验证码答案。 |
| id | 整数 | 是 | 由以下请求返回的验证码 ID: in.php 请求。 |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=2122988149&json=1"
如果验证码已经识别完成,CapSkip 会以 JSON 格式返回结果:
{
"status": 1,
"request": "eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2RkMi...",
"solution": {
"token": "eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2RkMi...",
"number": 9661
},
"cost": "0.0020",
"createTime": 1788863246,
"endTime": 1788863246,
"errorId": 0,
"solveCount": 1
}request 和 solution.token 两者始终是同一个字符串,你的客户端需要读哪一个就读哪一个。 number 是解开该挑战的计数器值,返回它只是为了信息完整。如果没有发送 json=1,返回的结果就只是 OK|<token>.
如果验证码尚未识别完成,CapSkip 会返回 CAPCHA_NOT_READY,其拼写方式与其他所有方法完全一致。请等待 5 秒后重复该请求。
轮询循环请以 errorId为判断依据,而不是 status. status 是整数 1 ,也就是 res.php 契约一直以来返回的内容,也是所有兼容 SDK 读取的字段。如果你的客户端是参照某个展示了 "status": "ready"的文档页面写的,那么请改为检查 errorId === 0 ,或者检查是否存在 solution.token ,而不是前者。
提交 token
ALTCHA 小组件会把它的 payload 放进一个名为 altcha的表单字段中。请把 token 原封不动地填进该字段,与小组件本身提交的内容完全一致:
POST https://www.example.com/signup Content-Type: application/x-www-form-urlencodedemail=someoneexample.com&altcha=eyJhbGdvcml0aG0iOiJTSEEtMjU2Iiwi...
payload 的内部结构取决于生成它的那个挑战。旧版挑战生成的是一个扁平文档,其中带有 number,而 PoW v2 挑战生成的文档会把原始挑战嵌套在 challenge 之下,把答案嵌套在 solution之下。请把 token 当作不透明数据原样传递,因为目标站点自己知道该期待哪种结构。
有些集成方式改为从 JSON 请求体的某个字段读取 payload,因此请看清页面自己的提交请求发送了什么,并照着做。不要对 token 重新编码、裁剪空白或调整字段顺序。它是一份 JSON 文档的 base64 编码,文档中的字段都被服务器的 HMAC 签名覆盖,任何改动都会让它失效。
支持的算法
你不需要自己判断某个站点用的是哪种方案。CapSkip 会读取挑战并自动选用对应的算法。
| 方案 | 代次 | 是否支持 | 说明 |
|---|---|---|---|
| Legacy PoW | v1 | 是 | 求出 n ,使得 SHA(salt + n) 等于挑战值。SHA-1、SHA-256、SHA-384 和 SHA-512 均受支持。这也是目前大多数部署仍在运行的形式。 |
| PBKDF2 | PoW v2 | 是 | 找出一个计数值,使其派生密钥以目标前缀开头。支持 SHA-256、SHA-384 和 SHA-512。这是 ALTCHA 官方推荐的默认方案。 |
| SHA | PoW v2 | 是 | 同一方案的迭代哈希变体。 |
| Argon2id | PoW v2 | 否 | 内存密集型密钥派生函数。直接拒绝,而不会尝试计算。 |
| scrypt | PoW v2 | 否 | 内存密集型密钥派生函数。直接拒绝,而不会尝试计算。 |
两种计算强度模式都能正常处理,且无需你做任何配置。在 确定性(deterministic) 模式下,服务器会预先算好目标值,因此识别耗时可预测;而在 概率性(probabilistic) 模式下,识别耗时会因挑战而异。三种小组件类型 (native, checkbox 和 switch) 均受支持,因为类型只是控件的视觉样式,永远不会传到 API。
Argon2id 和 scrypt 会被直接拒绝,而不会尝试计算。使用其中任意一种的任务会返回 ERROR_CAPTCHA_UNSOLVABLE ,耗时约三分之一秒,并且永不重试,因此绝不会在无声无息中给出错误答案。由于 ALTCHA 推荐 PBKDF2 作为默认方案,这只会影响极少数站点。
CaptchaFox 是一种注重隐私的验证码,它对浏览器本身进行评分,而不是要求访客去辨认任何内容。大多数访客根本不会看到任何挑战。CapSkip 通过在真实浏览器中驱动真实的小组件来完成识别,并返回该小组件本应生成的验证 token。
工作原理
CaptchaFox 的判定分三层进行,其中只有最后一层是可见的。小组件会先运行一小段工作量证明,再收集大量浏览器信号,然后把两者一起发送到它自己的 API。如果这些证据让服务满意,就会立即签发 token,屏幕上不会绘制任何挑战。只有在证据无法通过时,才会出现交互式挑战。
这种结构带来一个实际影响,值得在接入之前了解。token 由真实的浏览器会话产生,而不是根据你提供的参数计算出来的,因此 CapSkip 会针对你的页面 URL 加载小组件并让它真实运行。你把 site key 和页面交给 CapSkip,CapSkip 把 token 返回给你。
完整流程分为四步:
- 从目标页面读取 site key。
- 将其提交到
/in.php连同method=captchafox. - 轮询
/res.php直到 token 就绪。 - 将 token 回填到目标表单的
cf-captcha-response字段中。
识别前需要准备什么
只有两个值,而且都直接从目标页面读取。没有需要抓取的挑战文档,也没有任何东西会在任务排队期间过期。
| 参数 | 来源 |
|---|---|
| sitekey | 渲染小组件时使用的公钥。它并不保密,对每位访客都相同,其前缀通常为 sk_. |
| pageurl | 小组件所在页面的完整 URL。CaptchaFox 会拿它与该 key 注册的域名进行核对,因此必须是真实的页面。 |
在哪里查找 site key
在目标页面上打开浏览器开发者工具,找到 CaptchaFox 的容器元素。站点会用两种方式之一渲染小组件,两种方式里 key 都是可见的。
<!-- Automatic rendering: the key is an attribute -->
<div class="captchafox" data-sitekey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G"></div><!-- Explicit rendering: the key is in the render call -->
<script>
captchafox.render("#container", {
sitekey: "sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G",
onVerify: function (token) { /* ... */ }
});
</script>如果页面是在运行时才构建小组件,导致两种写法都不在返回的 HTML 中,请打开 Network 标签页,找到发往 api.captchafox.com的请求。在路径中,key 就紧跟在这一段之后: /captcha/.
页面 URL 必须与 key 相符
CaptchaFox 的 key 会绑定一份允许域名列表,服务在签发任何内容之前都会先检查主机名。key 本身正确、却用在列表之外的页面上时,会被永久拒绝,而不是偶尔失败。
CapSkip 会直接报告这种情况,而不是重试,因为重试不可能有帮助。如果某个 site key 立即且持续失败,请确认 pageurl 就是小组件真正运行的那个页面,而不是搜索页、跳转页,或者解析到别处的短链接。
挑战类型
出现哪种挑战不由你选择。由 CaptchaFox 决定,CapSkip 负责处理它给出的类型。
| Challenge | 出现频率 | 是否支持 | 说明 |
|---|---|---|---|
| 隐形 | 通常 | 是 | 浏览器证据让服务满意,屏幕上不绘制任何内容就直接签发 token。这是最常见、也是最快的一条路径。 |
| 滑块 | 有时 | 是 | 需要把一块拼图拖入缺口的滑块挑战。CapSkip 会定位目标位置并完成拖动。 |
| 图片选择 | 很少 | 否 | 一组供挑选的图片网格。会被报告为无法识别,这样你的客户端可以重新提交、换到一个新的挑战,而不必一直等到超时。 |
| 音频 | 很少 | 否 | 无障碍备用方案。出于同样的原因被报告为无法识别。 |
这两种不支持的挑战并不常见,重试通常会换到另一种。请把无法识别的结果当作重新提交的信号,而不是该 key 永久失效。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| method | 字符串 | 是 | 必须为 captchafox。表示你提交的是 CaptchaFox 验证码。 |
| sitekey | 字符串 | 是 | 从目标页面读取到的 site key,其前缀通常为 sk_. |
| pageurl | 字符串 | 是 | 小组件所在页面的完整 URL。 |
| api_server | 字符串 | 否 | 要加载的小组件入口地址。默认值为 https://cdn.captchafox.com/。参见下方的 选择小组件来源 一节。 |
| useragent | 字符串 | 否 | 为与其他服务保持兼容而接受,但不会被使用。CapSkip 在真实浏览器中完成识别,并使用该浏览器自身一致的身份标识。 |
| proxy | 字符串 | 否 | 代理地址。接受 IP:PORT, LOGIN:PASSWORD@IP:PORT 或 IP:PORT:LOGIN:PASSWORD. |
| proxytype | 字符串 | 否 | 代理类型: HTTP, HTTPS, SOCKS5 或 SOCKS5H. |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
与大多数服务不同,CapSkip 并不强制这个方法使用代理。但只要你的识别量上来了,就需要代理:CaptchaFox 不只评估浏览器,也评估小组件所在的网络,因此同一个地址反复识别会把这个地址推向交互式挑战,再往后就是直接拒绝。测试和偶尔识别用一个地址就够。超出这个范围,请在 CapSkip 中配置代理池,让它分摊负载;如果 token 必须来自特定网络,也可以在每个请求里单独发送代理。
向你的 CapSkip API 端点(/in.php)提交 HTTP GET 或 POST 请求,method 为 method=captchafox。表单字段和 JSON 请求体都支持,字段名完全相同;另外 GET 同样可用,因为 CaptchaFox 没有需要上传的图片。
提交 CaptchaFox(表单字段):
curl -X POST \ -d "key=YOUR_API_KEY" \ -d "method=captchafox" \ -d "sitekey=sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G" \ -d "pageurl=https://www.example.com/signup" \ -d "json=1" \ http://127.0.0.1:8080/in.php
提交 CaptchaFox(JSON 请求体):
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"key": "YOUR_API_KEY",
"method": "captchafox",
"sitekey": "sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G",
"pageurl": "https://www.example.com/signup",
"json": 1
}' \
http://127.0.0.1:8080/in.php如果一切正确,CapSkip 会以纯文本形式返回验证码 ID: OK|2122988149。如果发送了 json=1 参数,返回的则是 JSON 封装格式。无论哪种形式,返回的值都是你用于轮询结果的验证码 ID。
{
"status": 1,
"request": "2122988149"
}否则 CapSkip 会返回相应的错误码。缺少 site key 或页面 URL 不可用时,会在提交请求本身上就被拒绝,而不是等到轮询之后才报错,因此你能在出错的那次请求上直接发现问题。
请等待约 5 秒,然后向结果接口(/res.php),并带上你收到的验证码 ID。一次 CaptchaFox 识别会运行真实的浏览器会话,因此可以预期它比 ALTCHA 这类基于计算的方法耗时更长,而在绘制出交互式挑战时还会更久一些。
GET 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| action | 字符串 | 是 | 指定 get 以获取验证码答案。 |
| id | 整数 | 是 | 由以下请求返回的验证码 ID: in.php 请求。 |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=2122988149&json=1"
如果验证码已经识别完成,CapSkip 会以 JSON 格式返回结果:
{
"status": 1,
"request": "177f50c25b845601e5c779cdb51b040d523e8ab69efb4d5b343e28df07d05076",
"solution": {
"token": "177f50c25b845601e5c779cdb51b040d523e8ab69efb4d5b343e28df07d05076"
},
"cost": "0.00145",
"createTime": 1788863246,
"endTime": 1788863262,
"errorId": 0,
"solveCount": 1
}request 和 solution.token 始终是同一个字符串,因此你的客户端需要哪一个就读哪一个。如果不带 json=1,返回的结果就只是 OK|<token>.
如果验证码尚未识别完成,CapSkip 会返回 CAPCHA_NOT_READY,其拼写方式与其他所有方法完全一致。请等待 5 秒后重复该请求。
轮询循环请以 errorId为判断依据,而不是 status. status 是整数 1 ,也就是 res.php 契约一直以来返回的内容,也是所有兼容 SDK 读取的字段。如果你的客户端是参照某个展示了 "status": "ready"的文档页面写的,那么请改为检查 errorId === 0 ,或者检查是否存在 solution.token ,而不是前者。
提交 token
CaptchaFox 小组件会把 token 放进一个表单字段,该字段名为 cf-captcha-response的表单字段中。请把 token 原封不动地填进该字段,与小组件本身提交的内容完全一致:
POST https://www.example.com/signup Content-Type: application/x-www-form-urlencodedemail=someoneexample.com&cf-captcha-response=177f50c25b845601e5c779cdb51b040d...
有些接入方式改为从 JSON 请求体的字段里读取 token,所以请检查目标页面自身的提交请求发送了什么,并照着做。请把 token 当作不透明字符串原样传递。它会在服务器端针对产生它的那个会话进行校验,因此任何改动都会让它失效。
token 的有效期很短。请尽快提交,不要在用户填写表单期间一直持有;如果表单被放弃后又重新填写,请再识别一次。
选择小组件来源
CaptchaFox 会从两个位置发布它的小组件,站点加载的是哪一个,决定了它期望收到的 token 形态。需要发送 api_server 的场景只有一个:目标页面没有使用默认来源。
| api_server | Token | 默认值 | 说明 |
|---|---|---|---|
| https://cdn.captchafox.com/ | 普通的 | 是 | 标准小组件,绝大多数站点用的都是它。当你什么都不发送时,CapSkip 加载的就是它。 |
| https://s.uicdn.com/mampkg/ | 带 MAM_ 前缀 | 否 | 某些平台内嵌的打包版本。它返回的 token 会带有前缀 MAM_。请完全按照目标页面 script 标签中出现的样子,原样传入完整的包路径。 |
请从目标页面上加载小组件的那个 <script> 标签中读取该值。如果你发送了错误的来源,识别依然会成功,但返回的 token 格式是目标站点不会接受的,这看起来更像一次静默的校验失败,而不是一个报错。
Capy Puzzle 是一种拖放式验证码:从一张照片中切出一块拼图,访客要把它滑回原来的缺口。CapSkip 会返回小组件本应写入页面的那三个值,可以直接随你的表单一起提交。
工作原理
在本页的各种验证码中,Capy 很特别:挑战的任何部分都不是由服务器签发的。小组件会自己生成 challenge key,向 Capy API 请求属于这个 key 的拼图,再由访客把拼图块拖到位。没有需要事先获取的 token,也没有需要重放的握手过程。
答案不是一个坐标。小组件会记录拼图块被拖动的轨迹,并把它编码成一个字符串,所以你提交回目标站点的是一段可信的拖动过程,而不是一个终点。CapSkip 会替你生成这条轨迹。
完整流程分为四步:
- 从目标页面读取验证码密钥。
- 将其提交到
/in.php连同method=capy. - 轮询
/res.php直到结果就绪。 - 把返回的三个值填回目标表单中提交。
识别前需要准备什么
只有两个值,而且都直接从目标页面读取。
| 参数 | 来源 |
|---|---|
| captchakey | 该网站的公开 Capy 密钥,其前缀通常为 PUZZLE_。它在页面源码中显示为 capy_captchakey,在小组件脚本 URL 中则是 k 查询参数。 |
| pageurl | 小组件所在页面的完整 URL。Capy 不会看到这个值,但密钥是按站点注册的,因此请发送真实的页面。 |
还有第三个值得确认的值: api_server,即该密钥所属 Capy API 的根地址。它可以从同一个 script 标签中读取。CapSkip 默认使用 https://jp.api.capy.me,线上服务就在这个地址;只有当目标页面指向别处时,才需要另行指定。
有些验证码识别服务的文档里仍然写着 api.capy.me ,缺少区域前缀。该主机已经无法解析。如果你是从别处复制过来的,请把它删掉,让 CapSkip 使用默认值。
在哪里查找验证码密钥
在目标页面上打开浏览器开发者工具,找到 Capy 小组件。站点加载它的两种方式里,密钥都是可见的。
<!-- In the page source, as the widget configuration -->
<div id="capy"></div>
<script>
window.capyOptions = {
captchakey: "PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
puzzle_div: "capy"
};
</script><!-- Or in the script URL itself, as the k parameter -->
<script src="https://jp.api.capy.me/puzzle/get_js/?k=PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v"></script>该脚本 URL 的根地址,也就是上例中的 https://jp.api.capy.me/ ,就是你在需要时要发送的 api_server 值。
答案是三个值,而不是一个 token
这是本页所有方法中唯一的结构性差异,值得在编写客户端之前先读一遍。reCAPTCHA、Turnstile 和 CaptchaFox 返回的都是一个不透明的字符串,而 Capy 的结果是三个各自独立、只有一起使用才有效的值。
| 返回的值 | 填入的目标表单字段 |
|---|---|
| captchakey | capy_captchakey |
| challengekey | capy_challengekey |
| answer | capy_answer |
由于单纯的 OK|<token> 只能容纳一个值而不是三个, /res.php 在这个方法下会返回完整的解决方案对象。纯文本响应里就带着它,JSON 响应的 request 字段同样如此。第四个字段 respKey会返回一个空字符串,以兼容那些针对其他服务编写的客户端。对拼图识别来说它不携带任何信息,可以忽略。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| method | 字符串 | 是 | 必须为 capy。表示你提交的是 Capy Puzzle 验证码。 |
| captchakey | 字符串 | 是 | 从目标页面读取到的验证码密钥,其前缀通常为 PUZZLE_. sitekey 和 websiteKey 均可作为别名使用。 |
| pageurl | 字符串 | 是 | 小组件所在页面的完整 URL。 |
| api_server | 字符串 | 否 | 该密钥所属 Capy API 的根地址。默认值为 https://jp.api.capy.me. |
| version | 字符串 默认:puzzle | 否 | 挑战类型。只有 puzzle 会被识别。参见下方的 Puzzle 与 Avatar 一节。 |
| userAgent | 字符串 | 否 | 随拼图请求一起发送的 User-Agent。可选,很少需要设置。 |
| proxy | 字符串 | 否 | 代理地址。接受 IP:PORT, LOGIN:PASSWORD@IP:PORT 或 IP:PORT:LOGIN:PASSWORD. |
| proxytype | 字符串 | 否 | 代理类型: HTTP, HTTPS, SOCKS5 或 SOCKS5H. |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
CapSkip 并不强制这个方法使用代理,但只要你的识别量上来了,就需要代理。每次识别都会通过一次实时请求向 Capy API 取一份新的拼图,而同一个地址持续发出这样的请求,正是限流机制要抓的模式。测试和偶尔识别用一个地址就够。超出这个范围,请在 CapSkip 中配置代理池,让它分摊负载;如果某次识别必须来自特定网络,也可以在每个请求里单独发送代理。
向你的 CapSkip API 端点(/in.php)提交 HTTP GET 或 POST 请求,method 为 method=capy。表单字段、查询字符串和 JSON 请求体都以完全相同的字段名被接受,因为 Capy 没有需要上传的图片。
提交 Capy Puzzle(表单字段):
curl -X POST \ -d "key=YOUR_API_KEY" \ -d "method=capy" \ -d "captchakey=PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v" \ -d "pageurl=https://www.example.com/login" \ -d "json=1" \ http://127.0.0.1:8080/in.php
提交 Capy Puzzle(JSON 请求体):
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"key": "YOUR_API_KEY",
"method": "capy",
"captchakey": "PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
"api_server": "https://jp.api.capy.me/",
"pageurl": "https://www.example.com/login",
"json": 1
}' \
http://127.0.0.1:8080/in.php如果一切正确,CapSkip 会以纯文本形式返回验证码 ID: OK|2122988149。如果发送了 json=1 参数,返回的则是 JSON 封装格式。无论哪种形式,返回的值都是你用于轮询结果的验证码 ID。
{
"status": 1,
"request": "2122988149"
}否则 CapSkip 会返回相应的错误码。缺少验证码密钥、页面 URL 不可用,或者 api_server 不是一个 URL 时,都会在提交请求本身上就被拒绝,而不是等到轮询之后才报错,因此你能在出错的那次请求上直接发现问题。
等待大约 3 秒,然后向结果端点(/res.php),并带上你收到的验证码 ID。识别结果会被刻意推迟到一个真人可能达到的时长之后才返回,原因参见下方的 时间控制 一节。
GET 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| action | 字符串 | 是 | 指定 get 以获取验证码答案。 |
| id | 整数 | 是 | 由以下请求返回的验证码 ID: in.php 请求。 |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=2122988149&json=1"
如果验证码已经识别完成,CapSkip 会以 JSON 格式返回结果:
{
"status": 1,
"request": {
"captchakey": "PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
"challengekey": "BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP",
"answer": "0xax8ex0xax84x0xkx7qx0x18x76x0x1ix6sx0x26x68x0x2gx5kx0x34x50x",
"respKey": ""
},
"solution": {
"captchakey": "PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
"challengekey": "BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP",
"answer": "0xax8ex0xax84x0xkx7qx0x18x76x0x1ix6sx0x26x68x0x2gx5kx0x34x50x",
"respKey": ""
},
"cost": "0.00299",
"createTime": 1788863246,
"endTime": 1788863250,
"errorId": 0,
"solveCount": 1
}request 和 solution 携带的是同一个对象,你的客户端需要哪一个就读哪一个。如果不带 json=1,同一个对象会跟在 OK| 前缀之后,成为一行 JSON。客户端以纯文本方式读取 res.php ,拿到的就是这一行:
OK|{"captchakey":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v","challengekey":"BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP","answer":"0xax8ex0xax84x0xkx7qx","respKey":""}如果验证码尚未识别完成,CapSkip 会返回 CAPCHA_NOT_READY,与其他所有方法一样,就是这个拼写。请等待 3 秒后重复该请求。
每个结果只投递一次。第一次成功的轮询会返回结果并把它丢弃,之后对同一个 ID 的轮询都会返回空内容,因此请保存好携带这些值的那次回复。
提交识别结果
这三个值要填进 Capy 小组件本应自己填写的那些表单字段。请原样提交:
<input type="hidden" name="capy_captchakey" value="PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v"> <input type="hidden" name="capy_challengekey" value="BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP"> <input type="hidden" name="capy_answer" value="0xax8ex0xax84x0xkx7qx0x18x76x0x1ix6sx">
不要对 answer 字符串做裁剪、重新编码或任何其他形式的清理。它就是小组件本应记录下来的拖动轨迹,目标站点的后端会拿它与自己签发的挑战进行校验,因此任何改动都会让它失效。
challenge key 是一次性的,而且有效期很短。CapSkip 会为每次识别生成一个新的 challenge key,拼图也与它绑定,所以请尽快提交这三个值,而不要缓存它们,也绝不要把同一个 challengekey 用于第二次提交。
时间控制:Capy 会拒绝来得太快的答案
如果你自己动手对接 Capy,这部分会让你白白搭进去一天。Capy 会测量从发出拼图到收到答案之间的实际时间差,并拒绝任何看起来超出常人速度的答案。它的拒绝信息和答错时收到的完全一样,所以一个在 200 毫秒内返回的完全正确的答案,和一个有问题的识别工具没有任何区别。
在 Capy 自己的登录页面上实测,保持答案正确不变:
| 从拼图到验证的耗时 | 结果 |
|---|---|
| 0.48 seconds | Refused |
| 1.03 秒及以上,最高测试到 4 秒 | Accepted |
因此,CapSkip 会把每个结果都保留到自绘制拼图起经过足够时间之后再返回,大约是实测下限的两倍,因为这个下限属于 Capy,随时可能变动。这一等待会自动应用到每一次识别,无需任何配置。它消耗的是延迟而不是吞吐量;如果你是通过轮询获取结果,它是不可见的:任务只是大约需要两秒。
Puzzle 与 Avatar
Capy 提供两种挑战类型。它们是位于不同端点后面的不同挑战,CapSkip 只识别其中一种。
| version | Challenge | 是否支持 | 说明 |
|---|---|---|---|
| puzzle | Assemble a puzzle | 是 | 默认类型,几乎所有部署用的都是它。把切下来的拼图块拖回它原本所在的缺口。 |
| avatar | Drag an object | 否 | 另一种独立的挑战类型。提交时会直接返回 ERROR_BAD_PARAMETERS 而被拒绝,不会去尝试识别。 |
不发送 version 即表示 puzzle,所以大多数接入方式从来不会设置它。 avatar 请求被有意拒绝而不是尝试识别:把它当作 puzzle 来回答,会返回一个目标站点不接受的结果,这比一个明确的错误更糟,因为它看起来像是识别工具出了故障,而不是一种不受支持的挑战。
Friendly Captcha 不要求访客做任何事,而是让访客的浏览器做一段算术运算。没有需要点击的图片,没有滑块,也没有音频备选方案,屏幕上根本没有可能做错的东西。CapSkip 会返回小组件本应生成的 token,可以直接随你的表单提交。
工作原理
Friendly Captcha 是一种工作量证明(proof of work)验证码。小组件会拿到一个难度设置,搜索哈希值低于该难度的数值,并把结果写入你表单中的隐藏字段。访客始终看不到任何东西,这正是该产品的用意:使用它的页面看起来就像没有验证码一样。
同一个名字下发布了两套完全不同的协议,而 sitekey 并不能告诉你某个站点用的是哪一套。它们只共用品牌和 sitekey 命名空间,此外毫无关系。判断该用哪一套是集成时首先必须做对的事,因此下文单独为它设了一节。
完整流程分为四步:
- 从目标页面读取 sitekey,并连同小组件脚本 URL 一起读出。
- 将两者一起提交给
/in.php连同method=friendly_captcha. - 轮询
/res.php直到 token 就绪。 - 把 token 放进小组件本应填写的表单字段,然后提交。
该方法的识别时间不是固定值。服务会在请求发出的那一刻决定这次请求值多少工作量,因此同一个 sitekey 在不同时候的开销可能明显不同。请在轮询时为此留出余量,不要假定一个固定时长。
识别前需要准备什么
有两个值是必需的,第三个只要能拿到就值得一并发送。
| 参数 | 来源 |
|---|---|
| sitekey | 加载的 data-sitekey 属性,取自小组件元素,也就是带有以下 class 的元素: class="frc-captcha". |
| pageurl | 小组件所在页面的完整 URL。 |
| module_script | 加载的 src 属性,取自带有以下属性的小组件脚本标签: type="module"。不是必需的,但它能告诉 CapSkip 站点使用的是哪个协议版本,因此页面上若有就发送它。 |
版本 1 与版本 2
这一节如果跳过,会白白搭进去你一个下午。两个版本都在运行,也都有人在用,而且为其中一个版本注册的 sitekey 在另一个版本的端点上同样会有响应。识别错版本,你会拿到一个格式完全正确、却被目标站点拒绝的 token,而且任何地方都不会提示问题出在版本上。
| version | 小组件包 | 是否支持 | 说明 |
|---|---|---|---|
| v1 | friendly-challenge | 是 | 最初的开源小组件。其脚本为 widget.module.min.js 或 widget.min.js。在没有其他线索时的默认版本。 |
| v2 | @friendlycaptcha/sdk | 是 | 当前的 SDK。其脚本为 site.min.js。加载该脚本的页面就是 v2,不论其他特征看起来如何。 |
CapSkip 按以下顺序判断版本,并在得到第一个答案时停止:
- 加载的
version参数,如果你发送了的话。v1和v2是可用的写法;直接写1或2也可以。 - 小组件脚本 URL,取自
module_script或nomodule_script。这是现有信号中最可靠的一个,因为它就是站点实际加载的构建版本。 - 两者都没有时,
v1.
CapSkip 不会向服务查询某个 sitekey 属于哪个版本,因为两个版本它都会作答。请发送 version,或者发送脚本 URL,这个问题就不会出现。
在哪里找到 sitekey
在目标页面上打开浏览器开发者工具,找到小组件元素。sitekey 和脚本在页面源码中彼此相邻,两者合在一起就提供了该方法所需的全部信息。
<!-- Version 1: the friendly-challenge widget --> <div class="frc-captcha" data-sitekey="FCMEXAMPLE1234AB"></div> <script type="module" src="https://cdn.example.com/[email protected]/widget.module.min.js"></script> <script nomodule src="https://cdn.example.com/[email protected]/widget.min.js"></script><!-- Version 2: the @friendlycaptcha/sdk widget --> <div class="frc-captcha" data-sitekey="FCMEXAMPLE1234AB"></div> <script type="module" src="https://cdn.example.com/@friendlycaptcha/[email protected]/site.min.js"></script>
有些部署会从自有域名而不是 CDN 提供该脚本。起决定作用的是文件名,而不是它来自哪个主机。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| method | 字符串 | 是 | 必须为 friendly_captcha。表示你提交的是 Friendly Captcha。 |
| sitekey | 字符串 | 是 | 加载的 data-sitekey 值,从目标页面的小组件元素上读取。 |
| pageurl | 字符串 | 是 | 小组件所在页面的完整 URL。 |
| version | 字符串 默认值:v1 | 否 | 协议版本,参见上文 v1 或 v2。参见下方的 版本 1 与版本 2 一节。 |
| module_script | 字符串 | 否 | 加载的 src 属性,取自带有以下属性的小组件脚本标签: type="module"。在未发送 version 时用来判断版本。 |
| nomodule_script | 字符串 | 否 | 加载的 src 属性,取自带有以下属性的小组件脚本标签: nomodule。出于同样的原因也会读取。 |
| api_server | 字符串 | 否 | CapSkip 专有。sitekey 所属的数据驻留端点。可接受 global (默认值)、 eu,或者一个完整 URL。参见 数据驻留 一节。 |
| useragent | 字符串 | 否 | 随请求发送的 User-Agent。可选,很少需要。 |
| proxy | 字符串 | 否 | 代理地址。接受 IP:PORT, LOGIN:PASSWORD@IP:PORT 或 IP:PORT:LOGIN:PASSWORD. |
| proxytype | 字符串 | 否 | 代理类型: HTTP, HTTPS, SOCKS5 或 SOCKS5H. |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
CapSkip 不会强制该方法使用代理,但在本页几乎所有方法中,这里是最早会需要代理的。服务会决定每个请求值多少工作量,并且对已经见过很多次的地址调高这个数值:在单个地址上针对同一个 sitekey 实测,难度设置在一轮测试过程中持续上升;而官方公布的区间显示,全新地址与被大量使用的地址之间,换取同一个 token 所需的工作量相差接近三十倍。用一个地址做测试或偶尔识别没有问题。超出这个范围,就在 CapSkip 中配置代理池让它分摊负载,或者在某次识别必须来自特定网络时,随请求发送单独的代理。
向你的 CapSkip API 端点(/in.php)提交 HTTP GET 或 POST 请求,method 为 method=friendly_captcha。表单字段、查询字符串和 JSON 请求体都以相同的字段名被接受,因为该方法没有图片需要上传。
提交 Friendly Captcha(表单字段):
curl -X POST \ -d "key=YOUR_API_KEY" \ -d "method=friendly_captcha" \ -d "sitekey=FCMEXAMPLE1234AB" \ -d "pageurl=https://www.example.com/signup" \ -d "version=v2" \ -d "json=1" \ http://127.0.0.1:8080/in.php
提交 Friendly Captcha(JSON 请求体,用脚本 URL 代替显式的 version):
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"key": "YOUR_API_KEY",
"method": "friendly_captcha",
"sitekey": "FCMEXAMPLE1234AB",
"pageurl": "https://www.example.com/signup",
"module_script": "https://cdn.example.com/@friendlycaptcha/[email protected]/site.min.js",
"nomodule_script": "https://cdn.example.com/@friendlycaptcha/[email protected]/site.compat.js",
"json": 1
}' \
http://127.0.0.1:8080/in.php如果一切正确,CapSkip 会以纯文本形式返回验证码 ID: OK|2122988149。如果发送了 json=1 参数,返回的则是 JSON 封装格式。无论哪种形式,返回的值都是你用于轮询结果的验证码 ID。
{
"status": 1,
"request": "2122988149"
}否则 CapSkip 会返回相应的错误码。缺少 sitekey 或页面 URL 不可用,会在提交请求本身上就被拒绝,而不是等到轮询之后,因此你会在出错的那个请求上就发现问题。
请等待约 5 秒,然后向结果接口(/res.php)发送一个 HTTP GET 请求,并带上你收到的验证码 ID。
GET 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| key | 字符串 | 是* | 你的 CapSkip API 密钥。仅当启用了 API 密钥校验 时才需要。 |
| action | 字符串 | 是 | 指定 get 以获取验证码答案。 |
| id | 整数 | 是 | 由以下请求返回的验证码 ID: in.php 请求。 |
| json | 整数 默认:0 | 否 | 0 以纯文本返回响应。 1 以 JSON 返回响应。 |
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=2122988149&json=1"
如果验证码已被识别,CapSkip 会以 JSON 格式返回 token。token 位于 request,以及 solution.token 也携带同一个字符串,供期望在那里读取它的客户端使用:
{
"status": 1,
"request": "c62c4da36bbaf7f253873035832709ef.aqwpWwdbzRWKY/UQAQwwpgAAAAAAAAAAM7hBvJOzqjc=.AAAAAArcCQABAAAAxv8QAAIAAACKYRgA.AgAB",
"solution": {
"token": "c62c4da36bbaf7f253873035832709ef.aqwpWwdbzRWKY/UQAQwwpgAAAAAAAAAAM7hBvJOzqjc=.AAAAAArcCQABAAAAxv8QAAIAAACKYRgA.AgAB"
},
"cost": "0.00299",
"createTime": 1789667786,
"endTime": 1789667807,
"errorId": 0,
"solveCount": 1
}不带 json=1时,同一个 token 会跟在 OK| 前缀之后,以纯文本形式返回,这也是把 res.php 当作文本读取的客户端会拿到的内容:
OK|c62c4da36bbaf7f253873035832709ef.aqwpWwdbzRWKY/UQAQwwpgAAAAAAAAAAM7hBvJOzqjc=.AAAAAArcCQABAAAAxv8QAAIAAACKYRgA.AgAB
如果验证码尚未识别完成,CapSkip 会返回 CAPCHA_NOT_READY,其拼写方式与其他所有方法完全一致。请等待 5 秒后重复该请求。
每个结果只投递一次。第一次成功的轮询会返回 token 并把它丢弃,之后对同一个 ID 的每次轮询都会返回空的响应体,因此请在携带它的那次响应里就把 token 保存下来。
两个版本产生的 token 在形态和大小上差别很大。v1 的 token 由四段以点号分隔的部分组成,长度为几百个字符,如上所示。v2 的 token 则是一个不透明的单一字符串,以 AQQA. 开头,长度约为六千字节,因此请确保承载它的地方,无论是隐藏字段、数据库列还是转发的请求,都按这个长度留足空间。
提交 token
token 要放进小组件本应自己填写的隐藏字段,而两个版本使用的字段名并不相同。把已经跑通的 v1 集成搬到 v2 站点上的人,常在这里栽跟头。
| version | token 写入的表单字段 |
|---|---|
| v1 | frc-captcha-solution |
| v2 | frc-captcha-response |
<!-- 版本 1 --> <input type="hidden" name="frc-captcha-solution" value="c62c4da3...AgAB"><!-- 版本 2 --> <input type="hidden" name="frc-captcha-response" value="AQQA.vW7kd3CujKT8PaQgEcW18QaH...">
原样提交 token。不要裁剪、不要重新编码,也不要去掉看起来像填充符的字符:它的每一部分都会对照签发它的那次挑战进行校验,任何改动都会让它失效。
站点可以自行重命名该字段,也确实有站点这样做。如果你要对接的页面用的是别的名字,请从小组件元素上读出该名字并改用它。如果页面为小组件定义了回调,把 token 作为唯一参数调用该回调也能达到同样的效果。
数据驻留
Friendly Captcha 为不同的数据驻留地区运行各自独立的端点,而一个 sitekey 只属于其中之一。两个端点都会为同一个 sitekey 签发 token,因此把请求发到错误的端点,会得到一个在这边看起来完全有效、却被目标站点拒绝的 token,而且除了校验失败之外拿不到任何更有用的信息。
小组件通过 data-api-endpoint 属性指明自己所属的地区。如果目标页面带有该属性,请把相同的值传给参数 api_server。该参数是 CapSkip 专有的,在别处没有对应项,因此针对其他服务编写的客户端不会发送它:当目标页面使用区域端点时,请自行加上。
| api_server | 它选择的内容 |
|---|---|
| global | 默认值。属性缺失时使用,这也是常见情况。 |
| eu | 欧洲端点,对应页面上的 data-api-endpoint="eu". |
| A full URL | 自托管或其他自定义部署。请完全按照页面给出的形式发送该端点。 |
使用代理
对于 reCAPTCHA v2、v3、Invisible、Enterprise 和 Cloudflare,你可以随每个任务发送一个代理。CapSkip 将通过该代理识别验证码,而不使用 CapSkip 应用中配置的代理池。
当目标网站检查验证码 token 是否与你自己的请求来自同一 IP 地址时(例如 Cloudflare 后面的站点、严格的 reCAPTCHA 评分或地理限制页面),这很有用。
POST 请求参数列表
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| proxy | 字符串 | 否 | 代理地址。IP 认证格式: IP:PORT (示例: 123.123.123.123:3128)。登录/密码认证格式: login:password@IP:PORT |
| proxytype | 字符串 | 否 | 代理类型。支持的值: HTTP, HTTPS, SOCKS5, SOCKS5H。默认: HTTP 当提供了 proxy 但省略了 proxytype 时。 |
错误代码
| 代码 | 含义 |
|---|---|
ERROR_KEY_DOES_NOT_EXIST | 无效的 API 密钥。 |
ERROR_WRONG_USER_KEY | API 密钥缺失或为空。 |
ERROR_WRONG_METHOD | 无效的 HTTP 方法或 action 参数中定义的自定义字符串。 |
ERROR_WRONG_ID_FORMAT | 验证码 ID 格式无效。 |
ERROR_BAD_PARAMETERS | 缺少或无效的必需参数。 |
ERROR_UPLOAD | 未提供图片数据或上传失败。 |
ERROR_INVALID_IMAGE | 图片格式无效或图片数据损坏。 |
ERROR_INVALID_BASE64 | 无效的 base64 编码。 |
ERROR_TOO_BIG_CAPTCHA_FILESIZE | 图片大小超过 600 kB 或尺寸超过 1000px。 |
ERROR_CAPTCHA_UNSOLVABLE | 识别验证码失败。请提交新任务并重试。 |
ERROR_GOOGLEKEY | 无效的 googlekey 参数中定义的自定义字符串。 |
ERROR_PAGEURL | 无效的 pageurl 参数中定义的自定义字符串。 |
ERROR_ZERO_BALANCE | 该 API 密钥在此方法上已无可用额度。 |
ERROR_PROXY_FORMAT | 加载的 proxy 值无法解析。 |
CAPCHA_NOT_READY | 验证码仍在处理中。请继续轮询。 |
| (空响应) | 结果已被读取,或该 ID 不存在。 |
