Scrapyダウンローダーミドルウェアでsolvethreeを使用する
非同期ダウンローダーミドルウェアを使用して、スパイダーが明示的にマークしたリクエストだけを解決します。同期版 solvethree SDK をワーカースレッドで実行し、トークン、プロキシ、返されたブラウザー識別情報を含む FormRequest を返します。
この例は、設定済みの単一の reCAPTCHA Enterprise フォームエンドポイントを処理します。チャレンジの検出や、任意のウェブサイトがどのようにトークンを受け取るかの調査は行いません。
このページの内容
ミドルウェアを有効にする前にターゲットを設定する
- Scrapy 2.19+、Python 3.10+、公式の solvethree Python SDK を使用してください。SDK は同期式で、ネイティブの非同期クライアントはありません。
- コンソールでワークスペースの有効なサイトプロファイルを作成してください。ホスト名とアクションを送信しても、サイトプロファイルは作成されません。サイトプロファイルの設定を参照してください。
- TARGET_URL に正確なフォーム送信先 URL、SOLVETHREE_PROXY にこのアプリケーションリクエストで使用するプロキシ、SOLVETHREE_API_KEY にワークスペースキーを設定してください。キーはサーバー側で保持してください。
- 以下に示すように AsyncioSelectorReactor を有効にし、ミドルウェアを優先度 740 で登録してください。これにより、優先度 750 の HttpProxyMiddleware より前に配置されます。
明示的にマークされたリクエストだけを解決する
# middlewares.py; Scrapy 2.19+, Python 3.10+, official solvethree SDK.
import asyncio
import os
from urllib.parse import urlsplit
from scrapy.http import FormRequest
from solvethree import Solvethree
class CaptchaMiddleware:
async def process_request(self, request):
if not request.meta.get("solvethree_form") or request.meta.get("solvethree_applied"):
return None
target = os.environ["TARGET_URL"]
if request.url != target:
raise ValueError("Only the configured form target may use this middleware")
proxy = os.environ["SOLVETHREE_PROXY"]
def solve():
return Solvethree(os.environ["SOLVETHREE_API_KEY"]).solve({
"site": urlsplit(target).hostname, "action": "login", "proxy": proxy,
"requestId": request.meta["solvethree_request_id"],
}, timeout=180)
result = await asyncio.to_thread(solve)
solution = result["solution"]
fields = dict(request.meta["solvethree_fields"])
fields["g-recaptcha-response"] = solution["token"]
meta = {**request.meta, "proxy": proxy, "solvethree_applied": True,
"dont_retry": True, "dont_redirect": True}
return FormRequest(target, formdata=fields, callback=request.callback,
errback=request.errback, headers={"User-Agent": solution["userAgent"],
"sec-ch-ua": solution["secChUa"]}, meta=meta, dont_filter=True)
# settings.py:
# TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
# DOWNLOADER_MIDDLEWARES = {"myproject.middlewares.CaptchaMiddleware": 740}
# The spider first loads its form/CSRF data with the same proxy and cookiejar.
# Then yield a Request(TARGET_URL, callback=your_result_handler, meta={
# "solvethree_form": True, "solvethree_request_id": one_saved_logical_job_id,
# "solvethree_fields": {"csrf": token_from_the_form, "email": "your-test-value"},
# "cookiejar": your_cookiejar_id,
# })
# Inspect the final response in your_result_handler; a ready task is not that response.ミドルウェアの process_request メソッドは request のみを受け取り、非推奨の spider 引数は受け取りません。マークされていないリクエストは、そのまま通過します。マークされたリクエストは TARGET_URL と完全に一致する必要があります。不一致の場合は、別のエンドポイントにトークンを送信するのではなく、エラーを発生させます。
asyncio.to_thread により、同期 SDK のネットワーク呼び出しとポーリングをイベントループのスレッド外で実行します。process_request を async と宣言するだけでは、これは実現できません。SDK はタスクを作成し、ready の結果を待つか、例外を発生させます。その後、ミドルウェアがフォーム送信を構築します。
アプリケーションが実際に送信するフォームに合わせる
- 1.
正しいアクションとトークンフィールドを設定する
この例では login アクションを使用し、solution.token を g-recaptcha-response に設定します。ターゲットの実装に合わせて変更してください。タスクの site は TARGET_URL のホスト名から取得します。reCAPTCHA タスクは type フィールドや url フィールドを受け取りません。recaptchaType を省略すると enterprise-v3 が選択されます。
- 2.
既存のフォームフィールドを指定する
スパイダーは、現在の CSRF 値を含む、エンドポイントが必要とするフィールドを solvethree_fields に設定する必要があります。ミドルウェアはそれらのフィールドをコピーしてトークンを追加します。フォームの読み込みや hidden 入力の抽出は行いません。
- 3.
セッションの一貫性を保つ
アプリケーションが必要とする同じプロキシとブラウザー識別情報で、最初のフォームを読み込んでください。その cookiejar ID を、マークしたリクエストのメタデータに渡してください。返される FormRequest はその cookiejar を保持し、solution.userAgent と solution.secChUa を使用しますが、この例ではそれ以前のセッションの準備や修復は行いません。
- 4.
アプリケーションのレスポンスを処理する
フォーム送信の成功を確認する callback と、リクエスト失敗時の errback を指定してください。タスクが ready であることは、解が生成されたことを意味し、その後のリクエストがウェブサイトに受け入れられたことを意味するわけではありません。
この例では、解決と送信に意図的に 1 つのプロキシを使用しています。解決後に無関係なプロキシに置き換えないでください。また、User-Agent やクライアントヒントを書き換えるカスタムダウンローダーミドルウェアも確認してください。解とともに返された識別情報を上書きしてはいけません。
再試行で追加の解決が発生しないようにする
マークしたリクエストを yield する前に solvethree_request_id を保存し、その論理的な解決処理に対して保持してください。requestId には、ASCII の英字、数字、アンダースコア、ハイフンを 1–64 文字使用できます。同一のタスク入力で再利用すると、既存のタスクを復旧できます。ID を保持したまま入力を変更すると競合します。
返される FormRequest は solvethree_applied を設定し、追加の解決が発生しないようにします。また、dont_retry と dont_redirect を設定し、その送信に対する自動再試行とリダイレクトを防ぎます。これらのフラグは、独自の callback やジョブランナーが別のリクエストを開始することまでは防がないため、その判断は明示的に行ってください。
- 解決の呼び出しがタイムアウトする。
タスクがすでに作成されているかを確認してください。接続のタイムアウトだけでは、作成に失敗したとは断定できません。
試してみましょう: 新しい論理的な解決リクエストを作成する前に、既知のタスクを取得するか、保存した requestId と同一のタスク入力を使って復元してください。
- 解決処理が結果を返さずに例外を送出する。
HTTP リクエストのエラーと、終端状態となったタスクの失敗を区別してください。失敗したタスクでは errorCode と error を確認します。
試してみましょう: 同じマーク付きリクエストを繰り返し yield するのではなく、失敗に対処してください。無効な入力を修正してから再試行し、失敗したタスクのポーリングは継続しないでください。
- 送信したフォームがウェブサイトに拒否される。
まず、送信するトークンフィールド、CSRF フィールド、Cookie、プロキシ、ブラウザー識別情報を確認してください。
試してみましょう: 別の解決処理に料金を支払う前に、アプリケーションのリクエストを修正してください。フィールドには、タスク ID や古いトークンではなく solution.token を設定する必要があります。
参照元とAPIリファレンス
最初のタスクを実行しましょう。
$5のワークスペースクレジットで始められます。