aiohttpでreCAPTCHAを解いて送信する

著者:solvethree読了時間:1分

aiohttp で REST API を直接呼び出します。タスクを 1 つ作成し、準備が完了するまでポーリングしてから、solution.token を対象に送信します。ワークスペースの Bearer トークンが対象に送られないよう、API と対象へのリクエストには別々のセッションを使用してください。

この例には Python 3.11+ が必要で、作成、ポーリング、対象への送信全体に 180 秒の期限を設定しています。これはアプリケーションのタイムアウトであり、解決時間を保証するものではありません。

このページの内容

サイト、プロキシ、対象リクエストを設定する

  • SOLVETHREE_API_KEY にワークスペースの API キーを設定してください。キーはサーバー上で保持してください。
  • SOLVETHREE_PROXY に、サービスが解決に使用するプロキシ URL を設定してください。対象への送信にも同じプロキシを使用します。
  • accounts.example.com と login アクションを、連携で使用するホスト名とアクションに置き換えてください。そのサイトには、ワークスペースで利用可能な有効なプロファイルが必要です。タスクを作成する前にプロファイルを設定してください。
  • 対象のログイン URL とフォームデータを、実際のアプリケーションリクエストに置き換えてください。この例では g-recaptcha-response のみを送信します。対象が必要とする他のフィールドを追加し、対象が想定するトークンフィールドを使用してください。

タスクは site、action、単一の proxy を送信します。デフォルトは reCAPTCHA Enterprise v3 です。type や url フィールドは追加しないでください。対象のフローで以前のリクエストの Cookie が必要な場合は、対象用セッション経由でそれらのリクエストを実行してください。この例は新しいセッションから開始します。

1 回だけ作成し、ポーリングしてから送信する

aiohttp · 一度だけ作成し、ポーリングして送信
# pip install aiohttp
import asyncio
import os
import uuid
from urllib.parse import urlsplit, unquote
import aiohttp

async def main():
    api = "https://api.solvethree.com/v1"
    proxy = os.environ["SOLVETHREE_PROXY"]
    task = {"site": "accounts.example.com", "action": "login", "proxy": proxy, "requestId": str(uuid.uuid4())}
    task_id = None
    try:
        async with asyncio.timeout(180):
            async with aiohttp.ClientSession(headers={"Authorization": "Bearer " + os.environ["SOLVETHREE_API_KEY"]}) as client:
                async with client.post(api + "/tasks", params={"waitMs": 0}, json=task, allow_redirects=False) as response:
                    response.raise_for_status()
                    if response.status >= 300: raise RuntimeError("Unexpected API redirect")
                    result = await response.json()
                task_id = result["id"]
                while result["status"] in ("pending", "solving"):
                    async with client.get(api + "/tasks/" + task_id, params={"waitMs": 15000}, allow_redirects=False) as response:
                        response.raise_for_status()
                        if response.status >= 300: raise RuntimeError("Unexpected API redirect")
                        result = await response.json()
                    if result["status"] in ("pending", "solving"): await asyncio.sleep(1)
                if result["status"] != "ready":
                    raise RuntimeError("Task failed: " + str(result.get("errorCode", "unknown")))
            solution = result["solution"]
            # A separate session prevents the API Bearer header reaching the target.
            proxy_url = urlsplit(proxy)
            netloc = proxy_url.netloc.rsplit("@", 1)[-1]
            server = f"{proxy_url.scheme}://{netloc}"
            auth = aiohttp.BasicAuth(unquote(proxy_url.username), unquote(proxy_url.password or "")) if proxy_url.username else None
            async with aiohttp.ClientSession() as target:
                async with target.post("https://accounts.example.com/login", proxy=server, proxy_auth=auth,
                    headers={"User-Agent": solution["userAgent"], "sec-ch-ua": solution["secChUa"]},
                    data={"g-recaptcha-response": solution["token"]}, allow_redirects=False) as response:
                    print("Application HTTP status:", response.status)
    except TimeoutError:
        print("Timed out; recover requestId", task["requestId"], "taskId", task_id)
        raise

asyncio.run(main())

これは REST を直接使用する非同期処理であり、公式 Python SDK の非同期メソッドではありません。/v1/tasks に作成用の POST を 1 回送信し、返された id を保存して、GET リクエストでそのタスクをポーリングします。pending と solving ではループを継続し、ready では送信が可能になり、タスクが failed になるとループを停止します。

タスク本文の proxy は、使用するプロキシを solvethree に指示します。aiohttp の proxy 引数と proxy_auth 引数は、対象への HTTP 呼び出しにのみ指定し、API 呼び出しには指定しません。この例では、プロキシ認証情報をプロキシサーバー URL から分離し、返された userAgent と secChUa とともにトークンを送信します。

セッションは意図的に分離しており、Authorization ヘッダーを持つのは API セッションだけです。リダイレクトは無効にしており、API のリダイレクトには追従せず、エラーとして扱います。

API リクエストで HTTP エラーが返される。

この例では、成功時のペイロードを読み取る前に HTTP ステータスを確認します。

試してみましょう: HTTP エラーはタスクの失敗とは別に処理してください。再試行する前に、不正な入力や認可の問題を修正してください。

タスクが failed になる。

タスクレスポンスの errorCode と error を確認してください。この例では errorCode のみを使って例外を送出します。

試してみましょう: そのタスクのポーリングを停止し、報告された原因に対処してください。再試行する前に、報告された原因を修正してください。

タスクは ready だが、アプリケーションがリクエストを拒否する。

送信するトークンフィールド、対象 URL、返されたブラウザー識別情報とプロキシに加え、必要なフォームフィールドや Cookie を確認してください。

試してみましょう: 同じプロキシ経由で、返された識別情報とともに solution.token を送信してください。アプリケーションのレスポンスは、その対象の成功基準に従って確認してください。表示される HTTP ステータスだけでは、受け入れられたとは判断できません。

解決を再購入せずにタイムアウトから復旧する

作成がタイムアウトしても、作成に失敗したとは限りません。この例では、元の入力を task に保持し、期限が切れるとその requestId と task_id を表示します。これらの変数が保持されるのはプロセスの実行中だけです。表示された ID だけでは、完全な復旧記録にはなりません。

  1. 1.

    作成前に元のリクエストを保存する

    再起動後も復旧できるように、requestId と proxy を含むタスク本文全体を安全に保持してください。この例では実行のたびに新しい UUID を生成するため、単に再実行すると、新しい論理リクエストが作成されます。

  2. 2.

    既存のタスクを再開する

    task_id がある場合は、そのタスクを取得してください。ID を受け取る前に作成がタイムアウトした場合は、保存したタスク本文と requestId を変更せずに作成を再実行してください。API は既存のタスクを返します。同じ ID を再利用しながら入力を変更すると、競合が発生します。

  3. 3.

    送信がすでに行われたか確認する

    期限は対象への POST にも適用されます。そのリクエストがタイムアウトした場合は、再送信する前にアプリケーションの処理結果を確認してください。クライアントがレスポンスを受信していなくても、アプリケーションはリクエストを受信している可能性があります。solvethree タスクを復旧しても、対象側で何が起きたかは判明しません。

最初のタスクを実行しましょう。

$5のワークスペースクレジットで始められます。

ワークスペースを作成
aiohttpでreCAPTCHAを解いて送信する · solvethree