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 —— 只依赖标准库;行为与错误码见本页说明。
相关文档
最近更新: