用 CapSkip SDK 在 Python 中识别 hCaptcha:一次调用,一个 token

要在 Python 中用 CapSkip SDK 识别 hCaptcha,请用小组件的 sitekey 和页面地址调用 solver.hcaptcha(),然后带着它返回的 token 和它报告的 User-Agent 提交你的表单。SDK 会提交任务、轮询结果,并把这两个值一并交给你,所以脚本里主要就是重试规则和表单提交。它背后调用的是 CapSkip hCaptcha 识别工具。它运行在你自己的 Windows PC 上,采用统一授权定价,所以替换一个过期或被拒绝的 token 不花一分钱。
你需要什么
- 一台运行 CapSkip 4.0.0 或更高版本的 Windows PC。该版本发布于 2026 年 11 月 3 日,新增了 hCaptcha 识别,包括 Enterprise。安装方法请参阅 设置指南.
- Python 3.10 或更高版本(这是 capskip 包声明的最低版本),以及两个包:capskip 1.6.0 或更高版本(第一个带有 hCaptcha 方法的版本)和 requests。已在 Python 3.11.7 和 3.14.0 上用 capskip 1.6.0 和 requests 2.34.2 测试,其他版本均未测试。
- 一个 PowerShell 提示符,在其中运行 pip install capskip requests。
- 一个你拥有或被允许自动化操作、带有 hCaptcha 小组件的页面,以及它的 sitekey。第 2 步会说明在哪里找到它。
- CapSkip 的地址。脚本在哪里运行,决定使用哪种模式。Local 模式(127.0.0.1)只为 CapSkip 所在的那台 PC 提供服务。Server 模式还会监听你的网络地址或公网 IP,这样另一台 PC、Windows VPS 或托管平台就能发送相同的请求。你可以在 连接设置中切换模式。下面的代码要求使用默认的 CapSkip 格式,所以请让所有 Service Emulator 设置保持默认值。
- 可选:代理。第 2 步说明它放在哪里。
测试方式:针对我自己写的两个模拟服务器,而不是真实的 CapSkip 应用或线上的 hCaptcha 站点。其中一个照搬了 CapSkip API 文档中 hCaptcha 部分的检查。另一个扮演注册表单,同时兼作正向代理。这里的内容无法衡量真实的识别耗时,模拟服务器中的站点规则(token 单次使用、User-Agent 必须一致、两分钟有效期)来自 CapSkip 文档,而不是 hCaptcha。真正做检查的是你要访问的站点。具体如何检查,请参阅 hCaptcha 开发者文档.
第 1 步:创建 solver
下面的四个代码块属于同一个文件,请按顺序放入。把它命名为 hcaptcha_demo.py。最后一个代码块就位之前,什么都不会运行。这些运行中的 SDK 是真实的 capskip 1.6.0,只有它背后的 CapSkip 应用是模拟的。构造函数的 apiKey 参数默认为字符串 capskip,在 API Key Validation 关闭时它是有效的。如果你开启了验证,请把你的密钥作为 apiKey 传入。
构造函数还会设置 SDK 等待 hCaptcha 的时长。它的 recaptchaTimeout 参数默认是 300 秒,之所以适用,是因为小组件要在真实浏览器里通过。代码把它降到了 180 秒,这是我自己的选择,不是 CapSkip 的设置。这个限制只覆盖轮询。SDK 没有给自己的 HTTP 调用设置超时,在模拟服务器上,把回复延迟 20 秒,即使 recaptchaTimeout 为 5,调用也会等满这 20 秒。问题都以异常的形式出现,你可以从 capskip 导入它们:SDK 在发送任何东西之前就拒绝你的参数时是 ValidationException,CapSkip 返回错误码时是 ApiException,等待超时时是 TimeoutException,连不上 CapSkip 时是 NetworkException。
# pip install capskip requests import re import requests from capskip import ApiException, CapSkip PAGE_URL = "https://example.com/signup" # the page that shows the widget SITEKEY = "YOUR_SITEKEY" # the data-sitekey of its h-captcha div # Local mode. In Server mode, use your server's address as the host. # With API Key Validation on, also pass apiKey="YOUR_API_KEY". solver = CapSkip(host="127.0.0.1", port=8080, recaptchaTimeout=180)
第 2 步:向 CapSkip 要 token
sitekey 是公开的,对每个访客都相同。大多数页面会把它作为 data-sitekey 写在小组件容器上。用脚本构建小组件的页面,则把它作为 hcaptcha.render() 的 sitekey 选项传入,而 hCaptcha iframe 的地址里带有 sitekey=。把它填入 SITEKEY,并把显示该小组件的页面的完整地址填入 PAGE_URL。
调用方式是 solver.hcaptcha(SITEKEY, PAGE_URL)。它会提交任务并轮询到 token 就绪,所以你不需要持有任务 ID,也不用写循环。原始 API 文档建议先等 10 秒,但 SDK 会立刻轮询,然后休眠 0.25 秒,每收到一次未就绪的回答就把休眠时间翻倍,最长 5 秒。按文档所说,一次识别通常需要 10 到 30 秒。我从没给真实的识别计过时。
结果是一个字典。它的 token 和 code 字段是同一个字符串,也就是 h-captcha-response 要用的那个,而 userAgent 是获得它时所用的 User-Agent。respKey 字段是 hCaptcha 交给小组件的第二个值。它始终存在,没有时为空,而文档说大多数站点从不读取它,所以代码忽略了它。每个结果只交付一次,SDK 会替你读取,因此这个函数会把 token 和 User-Agent 一起交还给你。文档说,只有当 userAgent 参数与 CapSkip 用来识别的浏览器一致时,CapSkip 才会采用它,所以示例让 CapSkip 自己选。
对这种方法而言,代理是可选的。文档说,测试时用一个地址就够了,而来自同一个 IP 的持续不断的识别请求,正是 hCaptcha 的速率限制所要捕捉的。用量大时,请在 hCaptcha 设置中加载代理列表,或随每个任务发送一个代理。任务上的代理优先于列表。该函数接受完整 URL 形式的代理,例如 http://user:[email protected]:8080,并把协议作为 proxytype 发送,其余部分作为 proxy 字段发送。HTTP 和 HTTPS 代理可直接使用。SOCKS 代理则需要先运行 pip install requests[socks]。如果你要轮换代理, 代理轮换指南 会告诉你具体做法。
def solve(proxy=None):
options = {}
if proxy: # for example http://user:[email protected]:8080
scheme, address = proxy.split("://", 1)
# The SDK sends type as proxytype and uri as proxy.
options["proxy"] = {"type": scheme.upper(), "uri": address}
result = solver.hcaptcha(SITEKEY, PAGE_URL, **options)
return result["token"], result["userAgent"] # delivered once: keep both第 3 步:用相同的 User-Agent 提交表单
在浏览器中,小组件会把 token 写入一个名为 h-captcha-response 的隐藏 textarea,并带有一个名为 g-recaptcha-response 的副本。没有浏览器时,你要自己发送 h-captcha-response,如果表单里有 g-recaptcha-response,也要一并发送。下面的代码两者都发送。你的表单自己的字段会随它们一起提交,字段名由站点决定。表单提交发往 PAGE_URL,所以如果表单的 action 指向另一个地址,请修改 post 调用的第一个参数 PAGE_URL。
User-Agent 很重要。回复里会指明获取该 token 的浏览器,文档要求你用同一个字符串提交表单。因此该函数在加载页面之前就把它设到会话上,这样页面加载和表单提交使用相同的 User-Agent。token 只能使用一次,并且在签发后约两分钟过期,所以要立即提交,并且每次尝试都请求一个新的。
这个提交还把来源页面作为 Referer 带上,因为 requests 自己既不发送 Referer,也不发送 Origin 请求头。有些框架会拒绝两者都没有的 HTTPS 表单提交。比如 Django CSRF 文档就描述了对不带 Origin 头的 HTTPS 请求所做的严格 referer 检查。
def submit_form(session, token, user_agent, email):
# Every request from here on carries the User-Agent the token was
# earned with, the page load included.
session.headers["User-Agent"] = user_agent
session.get(PAGE_URL, timeout=30) # picks up the page's own cookies
return session.post(
PAGE_URL,
data={
"email": email, # your form's own fields go here
"h-captcha-response": token,
"g-recaptcha-response": token, # the widget fills both
},
headers={"Referer": PAGE_URL}, # requests sends no Referer by itself
timeout=30,
)第 4 步:带重试规则的完整示例
最后一个代码块把各步骤串成一个脚本。把它添加到 hcaptcha_demo.py 的末尾,检查顶部的 PAGE_URL 和 SITEKEY 是你自己的,然后在存放该文件的文件夹里运行 python hcaptcha_demo.py(如果 python 不在你的 PATH 中,用 py hcaptcha_demo.py 也行)。脚本最多尝试三次。
错误码决定什么情况值得再试一次。文档说,ERROR_CAPTCHA_UNSOLVABLE 表示任务失败,应该提交一个新任务,所以它是重试集合里唯一的错误码。另有两种失败会点明原因,因为重新提交也会以同样的方式失败:hCaptcha 不接受该页面的 sitekey,以及连不上的代理。常见错误表中的其他错误码指向请求或设置本身。SDK 把错误码放在 ApiException 的消息里,形式是纯文本或一个小的 JSON 信封,所以 error_code 取出 ERROR_ 开头的那个词,对两种形式都适用。其余一切都会让脚本立刻停下,包括循环不捕获的那三个异常:ValidationException、TimeoutException 和 NetworkException。
站点拒绝的表单会得到一个新的 token 和再一次尝试。如果三个 token 都以同样的方式失败,就去检查表单字段。缺少字段导致的 HTTP 400,无论 token 多好都会重复出现。另外,reply.ok 只表示状态码低于 400。许多站点返回 200,却在页面里显示错误信息,所以请把这个判断换成检查只有成功时你的站点才会显示的内容。
# Only a task CapSkip could not solve gets another try. A bad sitekey, a
# dead proxy or a wrong key would fail the same way again.
RETRY = {"ERROR_CAPTCHA_UNSOLVABLE"}
def error_code(err):
# The message holds the code as bare text or inside a JSON envelope.
found = re.search(r"ERROR_[A-Z_]+", str(err))
return found.group(0) if found else str(err)
def sign_up(email, proxy=None):
session = requests.Session()
if proxy:
session.proxies = {"http": proxy, "https": proxy}
for attempt in range(1, 4):
try:
token, user_agent = solve(proxy)
except ApiException as err:
code = error_code(err)
if code not in RETRY:
raise
print(f"attempt {attempt}: {code}, new task")
continue
reply = submit_form(session, token, user_agent, email)
if reply.ok:
return reply
print(f"attempt {attempt}: the site answered {reply.status_code}, new token")
raise RuntimeError("no accepted form after 3 attempts")
if __name__ == "__main__":
reply = sign_up("[email protected]")
print(reply.status_code, reply.text[:60])在模拟运行中,脚本的表现如下,Python 3.11.7 和 3.14.0 上的结果相同:
| 模拟设置 | 脚本的表现 |
|---|---|
| 第二次轮询时 token 就绪 | 一个任务,然后表单被接受,状态码 200 |
| 前两次识别被拒绝 | 两行 attempt 输出,然后第三个任务返回状态 200 |
| 站点对第一次表单提交返回 403 | 一行 attempt 输出,第二个任务拿到新 token,然后状态码 200 |
| 轮询时返回 ERROR_PROXY_CONNECT_REFUSED | 一个任务之后抛出,没有重试 |
| 错误以纯文本而不是 JSON 发送 | 行为与 JSON 相同,包括重试 |
| 传给 sign_up 的代理 URL | in.php 收到了代理,proxytype 为 HTTP,页面加载和表单提交都经过了模拟代理 |
| 用默认的 requests User-Agent 提交 token | 模拟站点返回 403 |
| 同一个 token 提交两次 | 第二次提交得到 403 |
| CapSkip 始终未就绪,recaptchaTimeout 降到 25 秒 | 调用后约 28 秒出现 TimeoutException,因为两次轮询之间的最后一次休眠是 5 秒 |
请把站点这几行当作对我自己的模拟服务器的测试。它的规则遵循 CapSkip 文档,只是我加了一个会话 cookie 检查,并要求两个 token 字段必须一致。它们无法证明任何关于 hCaptcha 或真实站点的事情。
常见错误
| 错误 | 原因 | 修复 |
|---|---|---|
| ValidationException | SDK 在发送之前就拒绝了这次调用:空的 sitekey、不是完整 http 或 https URL 的页面地址、不是 0、1、true 或 false 的 invisible 值、不支持的代理类型,或者 CapSkip 的 hCaptcha API 不接受的选项,例如 2Captcha 的 domain | 读一读消息,它会指出问题所在,然后改正这个参数 |
| ERROR_RECAPTCHA_INVALID_SITEKEY | hCaptcha 不接受该页面的这个 sitekey。空 sitekey 到不了这一步,因为 SDK 会先拒绝它 | 再次从页面复制 data-sitekey,并检查 PAGE_URL 是显示该小组件的页面 |
| ERROR_CAPTCHA_UNSOLVABLE | 没有通过 hCaptcha | 提交一个新任务。循环最多这样做三次 |
| ERROR_PROXY_FORMAT | CapSkip 无法读取代理值 | 检查代理值。CapSkip 能读取 IP:PORT、LOGIN:PASSWORD@IP:PORT 和 IP:PORT:LOGIN:PASSWORD,而这个脚本会把 http://user:pass@IP:PORT 这样的 URL 拆分成 proxytype 和协议之后的地址 |
| ERROR_PROXY_CONNECT_REFUSED | 无法通过代理连接到目标。我只能模拟它 | 单独测试该代理,然后重新检查它的类型和登录信息 |
| ERROR_TASK_NOT_SUPPORTED | 这个 CapSkip 不接受该任务类型。可能的原因是版本低于 4.0.0,或者 Service Emulator 设置成了拒绝 hCaptcha 的服务 | 把 CapSkip 更新到 4.0.0 或更高版本,并让 Service Emulator 保持默认 |
| ERROR_WRONG_USER_KEY 或 ERROR_KEY_DOES_NOT_EXIST | API Key Validation 开启时密钥不对。除非你传入 apiKey,否则 SDK 发送的是字符串 capskip | 把你的密钥作为 CapSkip() 的 apiKey 参数传入 |
| TimeoutException | 第 1 步中 recaptchaTimeout 设定的 180 秒等待结束后仍然没有 token,或者 SDK 轮询期间 CapSkip 不再回答(SDK 把这算作未就绪)。它不在重试集合中 | 打开 CapSkip,在它的 Monitor 中查看该任务,然后再次运行脚本 |
| NetworkException | 你给 CapSkip() 的主机和端口上没有程序在监听,或者 CapSkip 回复了 HTTP 错误状态码 | 启动 CapSkip,然后对照连接设置检查主机和端口 |
| 站点拒绝一个看起来有效的 token | token 已经用过、超过约两分钟、配了另一个 User-Agent,或者提交时没带 Referer。前三个原因来自 CapSkip 文档 | 每个 token 只提交一次,拿到后立刻提交,使用同一个回复里的 User-Agent,并把页面作为 Referer |
如果站点仍然拒绝 token
有些站点,尤其是 hCaptcha Enterprise 站点,会拒绝 hCaptcha 评为有风险的 token,即使小组件已经通过。CapSkip 文档把这些称为严格的 sitekey。文档建议对 hCaptcha 使用 High score mode,位置在 Settings, Captcha types, hCaptcha。它需要在运行 CapSkip 的 PC 上安装 Google Chrome。4.0.0 的更新日志说该模式默认开启,并说当它关闭或缺少 Chrome 时,hCaptcha 会在 CapSkip Custom Browser 中识别。文档还要求使用优质代理,并要求从同一个地址、使用回复中的 User-Agent 提交表单。没有真实的严格站点,我无法测试这些内容,所以请把它当作 CapSkip 文档的指引,而不是我验证过的结论。
在脚本中通过服务器使用 CapSkip
脚本需要从 CapSkip 得到的只有主机和端口。在 Server 模式下,CapSkip 还接受来自你网络中其他设备的连接,或者在 Windows VPS 上接受来自互联网的连接,设置指南建议使用静态公网 IP 以获得稳定的访问。Server 模式要求 CapSkip 以管理员身份运行。把服务器的地址和端口传给 CapSkip(),脚本里的其他部分都不用改。所有权和计费也不变:这台机器是你的,没有任何计量。不过这确实会让端口对其他地址开放,所以请为你不信任的地址创建一条 Windows Firewall 阻止规则。
识别现在在服务器上进行。对于严格的 sitekey,文档要求两端使用同一个地址,所以给 sign_up 的 proxy 参数传入一个代理,它就会同时用于任务和你的会话。
常见问题
我可以使用异步客户端吗?
可以。AsyncCapSkip 接受相同的构造函数参数,用同样的 sitekey 和页面地址 await 它的 hcaptcha() 方法,返回同样的字典。它使用 httpx,pip 安装 capskip 时会一并装上。我只针对模拟服务器运行过它。
我能从托管平台调用 CapSkip 吗?
可以,通过 Server 模式。在有可访问地址的 Windows 机器上运行 CapSkip,把这个地址传给 CapSkip(),调用方式相同。识别工具留在你掌控的硬件上,平台只是向它发送 HTTP 请求。
隐形和 Enterprise 小组件呢?
它们使用同一个 hcaptcha() 方法。隐形小组件的 data-size 属性设为 invisible,所以请传入 invisible=True。对于设置了 rqdata 值的 Enterprise 站点,把它作为 data 传入,并且每个任务都要取一个新的。在模拟服务器上,两种情况下 in.php 都收到了 invisible=1 和 data 值。我没有在真实站点上运行过任何一种。
简短版结论
在 Python 中识别 hCaptcha,归结起来就是一次 hcaptcha() 调用,加上在获得 token 时所用的 User-Agent 下提交表单。只在 CapSkip 没能识别任务时重试,其他错误都让脚本停下。因为 CapSkip 是一个 本地验证码识别工具 ,采用统一授权定价,重试被拒绝的 token 只需再运行一遍循环。 Python 验证码识别页面 中有其他类型的 SDK 调用,想看小组件如何通过,可以打开 hCaptcha 演示.
