Pythonで失敗したリクエストを管理する

Pythonで失敗したHTTPリクエストを効果的なリトライ戦略とカスタムロジックで処理する方法を学びます。
3 分読
Managing Failed Requests in Python blog image

HTTPを扱っていると、リクエストの失敗は避けられない現実であり、それに対処する必要があります。ウェブ開発では、200 というステータスは良いレスポンスを示します。しかし、常に 200 が返ってくるとは限りません。このガイドでは、200 以外のステータスコードの扱い方について説明します。

Mozillaによると、ステータスコードは以下のカテゴリーに分けられる:

  • 100-199:情報提供
  • 200-299:成功した回答
  • 300-399:リダイレクション・メッセージ
  • 400-499:クライアントエラーメッセージ
  • 500-599:サーバーエラーメッセージ

ステータス・コードとは?

エラーコードは重要である。ウェブスクレイパーのようなクライアント側のプログラムを構築する場合、主に400以上と500以上のステータスコードに注目する必要がある。400番台のコードは一般的に、認証の問題、レート制限、タイムアウト、悪名高い404のようなクライアント側のエラーをカバーします:File Not Foundエラーなどです。500番台のコードでは、一般的にサーバーの問題を調べます。

何十年もの間、Mozilla はW3CIETF のウェブ開発標準を文書化してきました。以下は、あなたが遭遇するかもしれない一般的なエラーコードのリストです。このリストは網羅的ではありません。これらのエラーは、Mozilla の公式文書に記載されているものです。あなたのターゲットサイトによっては、コードが多少異なるかもしれませんが、ロジックは同じままであるべきです。

ステータスコード 意味 説明
400 悪いリクエスト リクエストフォーマットの確認
401 無許可 APIキーの確認
403 禁止 このデータにはアクセスできません。
404 見つかりません サイト/エンドポイントが存在しない
408 リクエストタイムアウト リクエストがタイムアウトしました。
429 リクエストが多すぎる リクエストを遅くする
500 内部サーバーエラー 一般的なサーバーエラー、リクエストの再試行
501 未実施 サーバーはまだこれをサポートしていない
502 バッド・ゲートウェイ 上流サーバーからの応答失敗
503 サービス利用不可 サーバーが一時的にダウンしています。
504 ゲートウェイタイムアウト 上流サーバー待ちでタイムアウト

リトライ戦略

リトライの仕組みを実装する場合、HTTPAdapter や Tenacity のような組み込み済みのライブラリを使うことができます。場合によっては、独自の再試行ロジックを書きたくなるかもしれません。

一般的には、リトライの上限と、リトライを中止するための戦略が必要だ。リトライの無限ループに巻き込まれないようにするために、リミットが必要なのだ。ホストサーバーを尊重するために、少しずつ手を引いていく必要がある。リクエストが早すぎると、ブロックされたり、サーバーに負担がかかったりします。

  • リトライ制限:制限を設定する必要がある。X回リトライすると、スクレーパーは諦める。
  • バックオフ・アルゴリズム:これは比較的シンプルだ。小さなバックオフから始めて、リトライのたびに増やしていく。0.3から始め、0.6、1.2と増やしていく。

ある限度までリクエストを再試行したい。各リクエストが失敗したら、もう少し待ちたい。

HTTPアダプタ

allowed_methodsは必須ではありませんが、再試行条件を定義することでコードをより安全にします。以下のコードでは、httpbinを使用して自動的にエラーを発生させ、再試行ロジックをトリガーしています。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

#create a session
session = requests.Session()

#configure retry settings
retry = Retry(
    total=3,                  #maximum retries
    backoff_factor=0.3,       #time between retries (exponential backoff)
    status_forcelist=(429, 500, 502, 503, 504), #status codes to trigger a retry
    allowed_methods={"GET", "POST"}
)

#mount the adapter with our custom settings
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)

#actually make a request with our retry logic
try:
    print("Making a request with retry logic...")
    response = session.get("https://httpbin.org/status/500")
    response.raise_for_status()
    print("✅ Request successful:", response.status_code)
except requests.exceptions.RequestException as e:
    print("❌ Request failed after retries:", e)

Sessionオブジェクトを作成したら、次のようにします:

  • Retryオブジェクトを作成し、以下のように定義する:
    • total: リクエストを再試行する上限。
    • backoff_factor:再試行までの待ち時間。リトライ回数が増えるにつれて指数関数的に調整される。
    • status_forcelist:バッドステータスコードのリスト。このリストにあるコードは自動的にリトライのトリガーとなる。
  • HTTPAdapter オブジェクトを作成し、retry 変数を指定します:adapter = HTTPAdapter(max_retries=retry).
  • アダプタを作成したら、session.mount() を使用して HTTP および HTTPS メソッドにマウントします。

このコードを実行すると、3回のリトライ(合計=3)が実行され、次のような出力が得られます。

Making a request with retry logic...
❌ Request failed after retries: HTTPSConnectionPool(host='httpbin.org', port=443): Max retries exceeded with url: /status/500 (Caused by ResponseError('too many 500 error responses'))

粘り強さ

Python用のオープンソースのリトライライライブラリとして有名なTenacityを使うこともできる。HTTPに限ったことではありませんが、リトライを実装するための表現力豊かで理解しやすい方法を提供してくれます。

まず、インストールする必要がある。

pip install tenacity

インストールしたら、デコレーターを作成してリクエスト関数をラップします。retryデコレータで、stop 引数、wait 引数、retry引数を追加します。

import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, RetryError

#define a retry strategy
@retry(
    stop=stop_after_attempt(3),  #retry up to 3 times
    wait=wait_exponential(multiplier=0.3),  #exponential backoff
    retry=retry_if_exception_type(requests.exceptions.RequestException),  #retry on request failures
)

def make_request():
    print("Making a request with retry logic...")
    response = requests.get("https://httpbin.org/status/500")
    response.raise_for_status()
    print("✅ Request successful:", response.status_code)
    return response

# Attempt to make the request
try:
    make_request()
except RetryError as e:
    print("❌ Request failed after all retries:", e)

ここでのロジックと設定は、HTTPAdapterを使った最初の例とよく似ている。

  • stop=stop_after_attempt(3):これはtenacity に、3 回再試行に失敗したら諦めるように指示する。
  • wait=wait_exponential(multiplier=0.3)は、前に使ったのと同じwaitを使う。また、前と同じように指数関数的に後退します。
  • retry=retry_if_exception_type(requests.exceptions.RequestException)はRequestExceptionが発生するたびにこのロジックを使うようにtenacityに指示します。
  • make_request() は、エラーエンドポイントへのリクエストを行います。このメソッドは、上で作成したデコレータからすべての trait を受け取ります。

このコードを実行すると、同じような出力が得られる。

Making a request with retry logic...
Making a request with retry logic...
Making a request with retry logic...
❌ Request failed after all retries: RetryError[<Future at 0x75e762970760 state=finished raised HTTPError>]

独自のリトライ・メカニズムを構築する

独自のリトライ・メカニズムを構築することもできる。カスタムコードを扱う場合、これが最良のアプローチになることが多い。比較的少ないコード量で、これらのライブラリから得られるのと同じ効果を得ることができる。

以下のコードでは、指数バックオフのためにsleepをインポートする必要がある。totalbackoff_factorbad_codesを設定する。リトライのロジックを保持するためにwhileループを使用します。

import requests
from time import sleep

#create a session
session = requests.Session()

#define our retry settings
total = 3
backoff_factor = 0.3
bad_codes = [429, 500, 502, 503, 504]

#try counter and success boolean
current_tries = 0
success = False

#attempt until we succeed or run out of tries
while current_tries < total and not success:
    try:
        print("Making a request with retry logic...")
        response = session.get("https://httpbin.org/status/500")
        if response.status_code in bad_codes:
            raise requests.exceptions.HTTPError(f"Received {response.status_code}, triggering retry")
        print("✅ Request successful:", response.status_code)
        success = True
    except requests.exceptions.RequestException as e:
        print(f"❌ Request failed: {e}, retries left: {total-current_tries}")
        sleep(backoff_factor)
        backoff_factor = backoff_factor * 2
        current_tries+=1

ここでの実際のロジックは、単純なwhileループで処理される。

  • response.status_codeが bad_codesのリストに含まれている場合、例外をスローします。
  • リクエストに失敗した場合、私たちは
    • エラーメッセージをコンソールに表示する。
    • sleep(backoff_factor) は、次のリクエストを送る前に待つ。
    • backoff_factor = backoff_factor * 2は、次のトライのためのbackoff_factorを2倍にする。
    • current_triesをインクリメントすることで、ループに無限に留まらないようにしている。

これがカスタム再試行ロジックの出力です。

Making a request with retry logic...
❌ Request failed: Received 500, triggering retry, retries left: 3
Making a request with retry logic...
❌ Request failed: Received 500, triggering retry, retries left: 2
Making a request with retry logic...
❌ Request failed: Received 500, triggering retry, retries left: 1

ブロックを乗り越える

野生のサイトでは、あなたをブロックするサイトもあるでしょう。Pythonのリクエストには常にプロキシを使うのがベストプラクティスです。プロキシを使うと、リクエストは別のマシンを経由するようになります。これはあなたのアイデンティティを保護し、あなたの IP アドレスがターゲットサイトにブロックされるのを防ぎます。IPブロックを突破するための詳しいガイドもあります。私たちの住宅用プロキシは、これらの課題を克服するために構築されています。

結論

これでPythonでHTTPリクエストに失敗したときの対処法がわかったと思います。スクレイパーやAPIクライアント、自動化ツールを書いているのであれば、これらの問題をどのように扱うべきかを知っているはずです。あらゆる種類の失敗したリクエストを避けるために、私たちはWeb Unlocker APIScraping Browser のような製品を開発しました。これらのツールは、ボット対策、CAPTCHAチャレンジ、IPブロックを自動的に処理し、最も困難なウェブサイトでもシームレスで効率的なウェブスクレイピングを保証します。

今すぐ登録し、無料トライアルを開始しましょう。