SolveHub

Python 標準ライブラリで CAPTCHA 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

同じ流れを 1 ファイルにまとめた例です(標準ライブラリのみ)。タスクを作成し、終状態までポーリングし、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 に表示し、完了した solution のみ stdout に出力します。業務記録に taskId を保存してください。この例は有料タスクを作成する場合があり、プレースホルダーだけでは動作しません。

既存タスクの再開

クライアント期限や不確定な結果では再作成せず、保存した ID を使います。

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

ID を取得できなかった場合は元のキーと本文を保持し、再送可能期間内に再試行します。別のタスクに古いキーを流用しないでください。

障害時の動作

リクエストのタイムアウトは 20 秒、全体 240 秒、照会間隔 3 秒です。再試行可能な障害は最大 3 回で、HTTP 日付を含む Retry-After に従います。待機が残りの予算を超える場合は停止します。これはクライアントの設定で処理時間の保証ではありません。

failed/cancelled で終了し、uncertain は照会を続けます。別の送信先に Key を渡さないよう HTTP リダイレクトを拒否します。タスク全文や認証情報は自動記録しません。実運用前に冪等性、エラー処理、API 仕様を確認してください。

クライアントのバージョンと失敗時の扱い

ダウンロードするクライアントはバージョン 1.0.0 で、Python 標準ライブラリ(3.10 以上)のみを使用します。挙動は固定で文書化されているため、失敗時もソースを読まずに切り分けできます。

  • 1 リクエストあたり 20 秒のタイムアウト、全体で 240 秒の予算、1 リクエストにつき最大 3 回の試行。
  • 429、500、502、503、504 では Retry-After(秒数または HTTP 日付)に従います。
  • HTTP リダイレクトを拒否し、API Key が別オリジンへ転送されないようにします。
  • taskId は判明次第 stderr に出力し、stdout には結果のみを出力します。完了したタスクは 1 つの JSON 値だけを表示します。
レスポンス 意味 対応
400 INVALID_TASK タスク項目の欠落または不正。 項目を修正したうえで、新しい冪等キーを使う新しいタスクとして送信します。元の結果が不明な場合は、再送の前に元のタスクを確認してください。
401 UNAUTHORIZED Key が無効または失効。 Key を差し替えます。同じリクエストの再試行では成功しません。
402 INSUFFICIENT_BALANCE 残高不足でタスクは作成されていません。 入金後、同じ冪等キーで再試行します。
403 で Key 関連のエラーコード 権限・有効期限・IP 許可リストで拒否。 コンソールで 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 で始める アカウント作成

最終更新: