SolveHub

Python 验证码 API 接入:标准库完整示例

无需第三方库的 Python 客户端,包含请求超时、有上限重试、固定幂等键和可恢复轮询。

适用于授权自动化测试、风控 QA 与浏览器集成。请仅在有权测试的站点和业务流程中使用。

前置条件

在服务端或测试运行器中使用 Python 3.10+,无需第三方 HTTP 库。需要具有 tasks 权限的有效 Key、足够余额以及有权测试的站点。注册并验证邮箱后,可在开放时开通自助服务,或绑定已签发的 Key。先查实时价格。

下载并运行

通过下方链接下载 python-client.py,创建 task.json 并填写自有授权站点参数:

{"type":"turnstile","task":{"websiteURL":"https://example.com/login","websiteKey":"REPLACE_WITH_SITE_KEY"}}

密钥放在测试环境变量中,不提交到代码仓库。首次请求前,为该业务任务生成并保存一个固定键:

curl -fsSLO https://www.tiam.codes/downloads/python-client.py
export SOLVEHUB_API_KEY='sh_REPLACE_ME'
export SOLVEHUB_IDEMPOTENCY_KEY='persisted-authorized-test-0001'
export SOLVEHUB_TASK_FILE='task.json'
python3 python-client.py

下面是同一流程的单文件版本,只依赖标准库。它会创建任务、轮询到终态,并在已有 SOLVEHUB_TASK_ID 时直接从该任务继续:

import json, os, sys, time, urllib.request

BASE = os.environ.get("SOLVEHUB_URL", "https://www.tiam.codes")
KEY = os.environ["SOLVEHUB_API_KEY"]
DEADLINE = time.time() + 240


def call(method, path, payload=None, idem=None):
    body = json.dumps(payload).encode() if payload is not None else None
    request = urllib.request.Request(BASE + path, data=body, method=method)
    request.add_header("Authorization", "Bearer " + KEY)
    if body:
        request.add_header("Content-Type", "application/json")
    if idem:
        request.add_header("Idempotency-Key", idem)
    with urllib.request.urlopen(request, timeout=20) as response:
        return json.load(response)


def wait(task_id):
    while time.time() < DEADLINE:
        task = call("GET", "/v1/tasks/" + task_id)
        if task["status"] == "completed":
            return task["solution"]
        if task["status"] in {"failed", "cancelled"}:
            raise SystemExit("task %s ended as %s; check failureReason" % (task_id, task["status"]))
        time.sleep(3)
    raise SystemExit("client deadline reached; keep taskId and resume later")


if os.environ.get("SOLVEHUB_TASK_ID"):
    task_id = os.environ["SOLVEHUB_TASK_ID"]
else:
    with open(os.environ["SOLVEHUB_TASK_FILE"]) as handle:
        task_id = call("POST", "/v1/tasks", json.load(handle), os.environ["SOLVEHUB_IDEMPOTENCY_KEY"])["taskId"]

print("taskId=" + task_id, file=sys.stderr)
print(json.dumps(wait(task_id)))

The script prints only the solution to stdout and the task ID to stderr. A completed Turnstile task therefore prints:

{"value": "0.EXAMPLE_TOKEN"}

客户端到达截止时间只会让脚本退出,服务端任务仍在运行;设置 SOLVEHUB_TASK_ID 重新运行即可继续查询同一任务,不要为此重复付费建单。

客户端获知 taskId 后立即写入 stderr;stdout 仅输出完成的 solution。请将 taskId 保存到业务记录。示例可能创建付费任务,单靠占位参数不能构成可用的测试环境。

恢复已有任务

客户端到达截止时间或结果不明时,不要创建替代任务,使用已保存的 ID:

export SOLVEHUB_TASK_ID='SAVED_TASK_ID'
python3 python-client.py

如果未收到 ID,保留原键与完整请求体,在约定的重放窗口内重试。不要把旧键用于不同任务。

故障行为

单次请求超时为 20 秒,总预算 240 秒,轮询间隔 3 秒。可重试故障最多尝试 3 次,遵守 Retry-After(含 HTTP 日期)。所需等待超过剩余预算就停止。这些是客户端参数,不是处理时间保证。

failed/cancelled 结束流程,uncertain 继续查询。客户端拒绝 HTTP 跳转,避免把 Key 发给其他来源;不自动记录完整任务或凭据。生产使用前阅读幂等、错误处理和完整 API 文档。

客户端版本与失败处理

下载的客户端版本为 1.0.0,只依赖 Python 标准库(3.10+)。行为固定且已文档化,排错时不必读源码:

  • 每个请求 20 秒超时,整体预算 240 秒,每个请求最多 3 次尝试。
  • 对 429、500、502、503、504 遵循 Retry-After(秒数或 HTTP 日期)。
  • 拒绝 HTTP 重定向,API Key 不会被转发到其他来源。
  • 一旦拿到 taskId 就写入 stderr;stdout 只输出结果,因此一次完成的任务只会打印一个 JSON 值。
响应 含义 处理方式
400 INVALID_TASK 任务字段缺失或非法。 修正字段后作为新任务、使用新幂等键重发;若原请求结果不确定,先核实原任务再决定。
401 UNAUTHORIZED Key 无效或已吊销。 更换 Key,重试同一请求不会成功。
402 INSUFFICIENT_BALANCE 余额不足,未创建任务。 充值后用同一幂等键重试。
403 且为 Key 相关错误码 权限、有效期或 IP 白名单拒绝该 Key。 在控制台检查 Key 权限、到期时间与白名单。
429 触发限流。 按 Retry-After 等待;客户端已自动退避。
500、502、503、504 上游临时故障。 让有上限的重试跑完,再用相同 Key 与请求体重试。
TASK_FAILED、TASK_TIMEOUT 任务结束但无可用结果。 读取 failureReason;不要自动重投同一任务。
TASK_UNCERTAIN 结果不明确。 继续轮询同一 taskId,不要新建替代任务。

退出码也是契约的一部分:0 表示已输出结果,1 表示 stderr 上有错误信息。修正后的请求体属于另一个请求,需要新的幂等键——同一个键必须始终对应同一份请求体,详见避免重复任务。

开发者下载

python-client.py v1.0.0 —— 只依赖标准库;行为与错误码见本页说明。

相关文档

从 cURL 开始接入 注册账户

最近更新: