基礎から学ぶPython入門 90日コース | Web・API - Day 50:APIエラー処理

Python 90日で身につけるPython
スポンサーリンク
スポンサーリンク

Day 50:APIエラー処理 ― 「うまくいかないとき」に備える力をつける

Day 50では、APIを使うときに避けて通れない エラー処理 をテーマにします。 実務でAPIを使うと、「いつも必ず成功する」なんてことはほぼありません。 ネットワークの揺らぎ、サーバー側の不調、認証ミス…いろいろな「うまくいかない」が起こります。

そこで今日は、次の4つを軸に、 APIエラー処理の基本をステップバイステップで整理していきます。

  • タイムアウト
  • リトライ
  • HTTPエラー
  • 例外処理

「失敗する前提で設計する」という視点を、少しずつ体に馴染ませていきましょう。

タイムアウト:いつまでも待たずに「区切りをつける」

なぜタイムアウトを設定するのか

APIにアクセスするとき、 サーバーの応答が遅かったり、ネットワークが不安定だったりすると、 リクエストが「いつまでも返ってこない」状態になることがあります。

そのまま放置すると、

  • プログラムが固まったように見える
  • バッチ処理が止まってしまう
  • ユーザー体験が悪くなる

といった問題につながります。

そこで使うのが タイムアウト です。

「○秒待っても返事が来なかったら、いったん諦める」

というルールを決めておくイメージです。

requestsでタイムアウトを設定する基本

# day50_timeout_basic.py
import requests


def get_with_timeout():
    """タイムアウト付きでAPIにアクセスする例です。"""

    url = "https://httpbin.org/delay/3"  # 3秒待ってから返事をするテスト用APIです。

    try:
        # timeout= で「最大何秒待つか」を指定します。
        # ここでは 2秒 にしているので、3秒かかるAPIはタイムアウトになります。
        response = requests.get(url, timeout=2)

        print("ステータスコード:", response.status_code)
        print("レスポンス本文:")
        print(response.text)

    except requests.exceptions.Timeout:
        # タイムアウトが発生したときの処理です。
        print("タイムアウトが発生しました。サーバーの応答が遅い可能性があります。")


if __name__ == "__main__":
    get_with_timeout()
Python

ポイント:

  • timeout= を付けないと、いつまでも待ち続ける可能性があります。
  • requests.exceptions.Timeout をキャッチすることで、「待ちすぎたとき」の処理を自分で決められます。

リトライ:一度ダメでも「もう一回だけ試す」優しさ

なぜリトライが必要なのか

APIの世界では、「一時的な失敗」がよくあります。

  • ネットワークの一瞬の途切れ
  • サーバーの一時的な負荷
  • CDNやプロキシの揺らぎ

こうした「たまたまダメだった」ケースでは、 少し待ってからもう一度試す(リトライ) ことで、普通に成功することがよくあります。

ただし、無限にリトライすると逆効果なので、

  • 最大回数
  • 間隔(何秒おいて再試行するか)

を決めておくのが基本です。

シンプルなリトライ実装の例

# day50_retry_basic.py
import time
import requests


def get_with_retry(
    url: str,
    max_retries: int = 3,
    delay_seconds: float = 1.0,
):
    """
    シンプルなリトライ付きGET関数です。
    - 最大 max_retries 回まで再試行します。
    - 失敗したら delay_seconds 秒待ってから再試行します。
    """

    for attempt in range(1, max_retries + 1):
        print(f"[TRY {attempt}/{max_retries}] {url} にアクセスします。")

        try:
            response = requests.get(url, timeout=5)

            # ステータスコードをチェックします。
            if 200 <= response.status_code < 300:
                print("成功しました。")
                return response
            else:
                print("HTTPエラーが発生しました。ステータスコード:", response.status_code)

        except requests.exceptions.Timeout:
            print("タイムアウトが発生しました。")

        except requests.exceptions.RequestException as e:
            # その他のリクエスト関連の例外をまとめて扱います。
            print("リクエストエラーが発生しました:", e)

        # ここまで来たということは、失敗したということです。
        if attempt < max_retries:
            print(f"{delay_seconds}秒待ってから再試行します。")
            time.sleep(delay_seconds)
        else:
            print("最大リトライ回数に達しました。諦めます。")
            return None


if __name__ == "__main__":
    # 正常なURLでも、ネットワーク状況によっては失敗することがあります。
    url = "https://httpbin.org/status/200"
    get_with_retry(url)
Python

ポイント:

  • for ループで「試行回数」を管理するシンプルなパターンです。
  • 成功したらすぐ return し、失敗したら待ってから再試行します。
  • 最後まで成功しなかった場合は None を返して「諦めた」ことを呼び出し側に伝えます。

HTTPエラー:ステータスコードで「何が起きたか」を知る

ステータスコードのざっくり分類

HTTPのステータスコードは、 APIの世界でも「何が起きたか」を知るための重要な手がかりです。

ざっくり分類すると:

  • 2xx:成功(200 OK など)
  • 4xx:クライアント側の問題(400 Bad Request, 401 Unauthorized, 404 Not Found など)
  • 5xx:サーバー側の問題(500 Internal Server Error など)

APIエラー処理では、

「2xxなら成功、それ以外は何かしらのエラー」

という前提で、分岐処理を書くことが多いです。

HTTPエラーをチェックしてメッセージを出す例

# day50_http_error_basic.py
import requests


def get_with_http_error_check(url: str):
    """HTTPステータスコードをチェックしてエラー内容をざっくり分類する例です。"""

    try:
        response = requests.get(url, timeout=5)
    except requests.exceptions.RequestException as e:
        print("リクエスト自体が失敗しました:", e)
        return

    status = response.status_code
    print("ステータスコード:", status)

    if 200 <= status < 300:
        print("成功しました。レスポンス本文:")
        print(response.text)

    elif 400 <= status < 500:
        print("クライアント側のエラーです。URLやパラメータ、認証情報を確認してください。")
        print("レスポンス本文:")
        print(response.text)

    elif 500 <= status < 600:
        print("サーバー側のエラーです。時間をおいて再試行する必要があります。")
        print("レスポンス本文:")
        print(response.text)

    else:
        print("想定外のステータスコードです。レスポンス本文:")
        print(response.text)


if __name__ == "__main__":
    # ここを 404 や 500 に変えると挙動が変わります。
    test_url = "https://httpbin.org/status/404"
    get_with_http_error_check(test_url)
Python

ポイント:

  • ステータスコードを見て、「自分のリクエストが悪いのか」「サーバーが不調なのか」をざっくり分類できます。
  • クライアント側のエラー(4xx)は、自分のコードや設定を見直す必要があります。
  • サーバー側のエラー(5xx)は、時間をおいて再試行する・問い合わせるなどの対応になります。

例外処理:予期せぬトラブルを「落ち着いて受け止める」

例外処理の役割

APIを扱うときには、 ネットワークや外部サービスに依存しているぶん、 予期せぬトラブルが起こりやすくなります。

  • DNSの問題
  • SSL証明書の問題
  • 接続拒否
  • タイムアウト

こうした「例外的な状況」に対して、 プログラムがそのままクラッシュしてしまうのではなく、

「エラーメッセージを出して終了する」 「ログを残して次の処理に進む」

といった「落ち着いた振る舞い」をさせるのが 例外処理 の役割です。

requestsの例外をまとめて扱う基本パターン

# day50_exception_basic.py
import requests


def safe_get(url: str):
    """requestsの例外をまとめて扱う安全なGET関数の例です。"""

    try:
        response = requests.get(url, timeout=5)
    except requests.exceptions.Timeout:
        print("タイムアウトが発生しました。")
        return None
    except requests.exceptions.ConnectionError:
        print("接続エラーが発生しました。ネットワークやURLを確認してください。")
        return None
    except requests.exceptions.RequestException as e:
        # その他のリクエスト関連の例外をまとめて扱います。
        print("予期せぬリクエストエラーが発生しました:", e)
        return None

    # ここまで来たということは、例外は発生していません。
    print("ステータスコード:", response.status_code)
    return response


if __name__ == "__main__":
    url = "https://httpbin.org/get"
    resp = safe_get(url)

    if resp is not None and 200 <= resp.status_code < 300:
        print("成功しました。レスポンス本文:")
        print(resp.text)
    else:
        print("レスポンスを取得できませんでした。")
Python

ポイント:

  • 例外ごとにメッセージを変えることで、原因の切り分けがしやすくなります。
  • RequestException は「requests関連の例外の親クラス」なので、最後にまとめてキャッチすると便利です。

Day 50ミニテンプレート:エラー処理付きAPIクライアント

最後に、Day 50の内容をギュッとまとめた、 タイムアウト+リトライ+HTTPエラー+例外処理 を備えたミニAPIクライアントを紹介します。

# day50_api_client_with_error_handling.py
import time
import requests


def get_api_with_error_handling(
    url: str,
    params: dict | None = None,
    max_retries: int = 3,
    delay_seconds: float = 1.0,
    timeout_seconds: float = 5.0,
):
    """
    エラー処理付きのAPIクライアント関数です。
    - タイムアウトを設定します。
    - リトライ回数と間隔を指定できます。
    - HTTPステータスコードをチェックします。
    - requestsの例外を安全に扱います。
    """

    for attempt in range(1, max_retries + 1):
        print(f"[TRY {attempt}/{max_retries}] {url} にアクセスします。")

        try:
            response = requests.get(
                url,
                params=params,
                timeout=timeout_seconds,
            )
        except requests.exceptions.Timeout:
            print("タイムアウトが発生しました。")
        except requests.exceptions.ConnectionError:
            print("接続エラーが発生しました。ネットワークやURLを確認してください。")
        except requests.exceptions.RequestException as e:
            print("予期せぬリクエストエラーが発生しました:", e)
        else:
            # 例外が発生しなかった場合の処理です。
            status = response.status_code
            print("ステータスコード:", status)

            if 200 <= status < 300:
                # 成功したのでJSONを返します。
                try:
                    data = response.json()
                    print("JSONを受け取りました。")
                    return data
                except ValueError:
                    print("JSONとして解釈できませんでした。レスポンス本文:")
                    print(response.text)
                    return None
            elif 400 <= status < 500:
                print("クライアント側のエラーです。URLやパラメータ、認証情報を確認してください。")
                print("レスポンス本文:")
                print(response.text)
            elif 500 <= status < 600:
                print("サーバー側のエラーです。時間をおいて再試行する必要があります。")
                print("レスポンス本文:")
                print(response.text)
            else:
                print("想定外のステータスコードです。レスポンス本文:")
                print(response.text)

        # ここまで来たということは、成功していないということです。
        if attempt < max_retries:
            print(f"{delay_seconds}秒待ってから再試行します。")
            time.sleep(delay_seconds)
        else:
            print("最大リトライ回数に達しました。諦めます。")
            return None


def main():
    url = "https://jsonplaceholder.typicode.com/posts"
    data = get_api_with_error_handling(url)

    if isinstance(data, list):
        print("取得した投稿件数:", len(data))
        if data:
            print("最初の投稿タイトル:", data[0].get("title"))
    else:
        print("データを取得できませんでした。")


if __name__ == "__main__":
    main()
Python

このテンプレートは、

  • タイムアウト
  • リトライ
  • HTTPエラーの分類
  • 例外処理

をひと通り備えた「エラー処理付きAPIクライアント」の基本形になっています。

Day 50のまとめ

Day 50では、APIエラー処理として、

  • タイムアウトは「いつまでも待たずに区切りをつける」ための重要な設定であること
  • リトライは「一時的な失敗」に対して、最大回数と間隔を決めて再試行するための仕組みであること
  • HTTPエラーは、ステータスコードで「クライアント側の問題か、サーバー側の問題か」を切り分ける手がかりになること
  • 例外処理は、ネットワークや外部サービスのトラブルに対して、プログラムを落ち着いて振る舞わせるための仕組みであること
  • これらを組み合わせることで、「失敗する前提で設計された」実務的なAPIクライアントを作れること

を、コード例とともに整理しました。

ここから先は、

  • ログ出力
  • アラート通知
  • エラー内容に応じた分岐(再試行・中断・代替処理)

などを組み合わせて、 より「現場でそのまま使える」エラー処理へと育てていくことができます。

タイトルとURLをコピーしました