captchaトークンの期限切れ:原因と対処方法
captchaトークンは、チャレンジを通過したことを示す短命の証明です。ScarnoxのPopularCaptchaのトークンは、発行から約2分で期限切れになります。その時間を過ぎてからトークンを送信すると、サイトはフォームを拒否します。多くの場合、captchaの期限が切れている、または無効だというメッセージが表示されます。解決策はほとんどいつもタイミングの問題です。captchaを送信のできるだけ直前に解決し、トークンは1回だけ使い、最初の試行が受け付けられなかったと確信できる場合にだけ再試行してください。
トークンが期限切れになる理由
トークンは永続的な資格情報ではありません。サイトは、人(または代わりに動くソルバー)が最近チャレンジを完了したことを確かめたいので、トークンに短い寿命を与えています。期限が始まるのは、APIを最初に呼び出した時点ではなく、トークンが発行された時点です。解決と送信の間に起きることはすべて、その2分を消費します。
よくある原因
- 早すぎる解決。 一括でトークンを事前に解決して、後で使うために保持しておくことが、最も多い原因です。トークンが使われる頃には古くなっています。
- 解決と送信の間の遅いコード。 長いデータベースの書き込み、キュー、フォーム投稿の前に置いた
time.sleepは、トークンの期限を過ぎさせてしまいます。 - トークンの再利用。 すでに受け付けられたトークン、または失敗した試行ですでに使われたトークンは、二度と動きません。トークンは1回限りとして扱ってください。
- ページの再読み込み。 解決の後にページを再読み込みすると、フォームが新しいチャレンジを発行し、手元のトークンが無効になることがあります。
対処:送信の直前に解決する
最も確実な方法は、解決と送信を1つのステップにすることです。トークンを取得し、フォームに入れ、フォームを投稿し、その間には遅い処理を入れません。
import requests
API_KEY = "scarnox_YOURAPIKEY"
CREATE_URL = "https://api.scarnox.com/api/tasks/create"
def fresh_token(sitekey: str, page_url: str) -> str:
r = requests.post(
CREATE_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # 呼び出しはトークンの準備ができるまでブロックされる
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
def submit_form(page_url: str, sitekey: str, form: dict) -> requests.Response:
token = fresh_token(sitekey, page_url) # 投稿の直前に解決する
return requests.post(
page_url,
data={**form, "h-captcha-response": token, "g-recaptcha-response": token},
timeout=30,
)
submit_formは、トークンをキャッシュにもキューにも保持しないことに注意してください。トークンを要求し、投稿して、次に進みます。失敗した解決は課金されないので、失敗したリクエストは解決側の費用にはなりません。
安全にリトライする
サイトによっては、期限切れのトークンを明確なレスポンスで拒否します。一方で、ページ内にエラーメッセージを含む通常のページを返すサイトもあり、その場合はステータスコードだけでは分かりません。期限切れのトークンに対してサイトが実際に返すレスポンスを確認して、それに基づいてチェックを作ってください。リトライのループは次のようになります。
def submit_with_retry(page_url: str, sitekey: str, form: dict, attempts: int = 2):
for _ in range(attempts):
resp = submit_form(page_url, sitekey, form)
if resp.status_code < 400 and "captcha" not in resp.text.lower():
return resp # このサイトのレスポンスから見て受け付けられた
raise RuntimeError("captcha token rejected after retries")
何かを作成するフォームでのリトライには注意してください。最初の試行が実際には受け付けられていたのに、コードがレスポンスを読み誤ると、リトライで重複したアカウント、注文、メッセージが作られてしまいます。最初の投稿が受け付けられなかったと確信できる場合にだけ再試行し、そのチェックは扱っているサイトに固有のものにしてください。
タイミングを正直に保つ
- 後で使うつもりのトークンをまとめて解決するループは作らない。送信ごとに解決する。
- 解決と投稿の間の作業は最小限にする。
- 解決のリクエストには約185秒のタイムアウトを設定する。呼び出しはトークンの準備ができるまでブロックされるからです。自分側のフォーム投稿のタイマーは、トークンの寿命よりずっと短くしてください。
- パイプラインを速くする必要があるなら、captchaの解決速度を参照してください。トークンを保存せずに並列で解決する方法を説明しています。
よくある間違い
- トークンを辞書やRedisに保存して後で使う。 キャッシュは無害に見えますが、トークンが期限を過ぎてから使われると、すべての送信が失敗します。
- セッションごとに一度だけ解決する。 長く続くセッションでも、フォームの投稿ごとに新しいトークンが必要です。投稿ごとにチャレンジのチェックが走ることがあるからです。
- 解決と送信の間にスリープを入れる。 解決の後に置かれたレート制限や意図的な一時停止は、トークンの寿命を使い切ります。ペース調整は解決の前に置いてください。
- すべての失敗を同じように扱う。 自分側のタイムアウト、拒否されたトークン、サイトのエラーは、それぞれ異なる扱いが必要です。別々にログに残してください。
原因を突き止めるためのログ
送信が失敗したときは、トークンが発行された時刻、フォームを投稿した時刻、HTTPステータス、レスポンスの最初の数百文字を記録してください。この4つの値があれば、トークンが期限切れになったのか、それとも別の理由でサイトに拒否されたのかが一目で分かります。共有されるログには、トークン全体を記録しないでください。有効なトークンは、そのチャレンジに対する資格情報だからです。
それでも失敗する場合の確認事項
- トークンが返ってから、フォームを投稿するまでの時間を出力する。
site_urlが、captchaを表示しているページと一致しているか確認する。- サイトキーが、古いコピーではなく、現在のページから読み取られたものか確認する。
- フォームのフィールド名を確認する。PopularCaptchaのトークンは
h-captcha-responseに入り、g-recaptcha-responseにも入ることが多いです。
エラーレスポンスの一覧と、それぞれの対処方法は、captcha解決のエラー処理にまとめています。