Python | Web / API:レスポンスコード

Python Python
スポンサーリンク

概要(レスポンスコードは「今どういう状態か」を示す信号)

HTTPのレスポンスコードは、APIから返ってくる「成功か失敗か、次に何をすべきか」を表す番号です。200番台は成功、300番台はリダイレクト、400番台はクライアント側の誤り、500番台はサーバ側の不調という大分類をまず覚えます。Pythonのrequestsでは、status_code、ok、raise_for_status、headersを使って正しく判定し、必要なら待機や再試行、原因の修正に繋げます。


基本の扱い方(ここが重要)

成功判定と例外化の型

import requests

r = requests.get("https://httpbin.org/status/200", timeout=5)
print(r.status_code, r.ok)  # 200 True

r = requests.get("https://httpbin.org/status/404", timeout=5)
print(r.status_code, r.ok)  # 404 False
try:
    r.raise_for_status()     # 4xx/5xx を HTTPError にする
except requests.HTTPError as e:
    print("HTTPエラー:", e)
Python

status_codeで番号を確認し、okで成功判定を簡略化できます。実務ではraise_for_statusを必ず呼んで失敗を例外に変換し、壊れた状態で処理を続けないのが安全です。

代表コードの意味と初動

  • 200 OK: 成功。レスポンス本文をそのまま処理。
  • 201 Created: 作成成功(POSTなど)。Locationヘッダーに新規リソースURLが入る場合あり。
  • 204 No Content: 成功だが本文なし。削除・更新系で返る。本文を読みにいかない。
  • 301/302/307 Redirect: 移動。r.historyで経路確認。必要ならallow_redirects=Falseで止めて自分で追随。
  • 304 Not Modified: 変更なし。ETag/If-None-Match等のキャッシュ連携で使う。
  • 400 Bad Request: リクエスト不正。送信データ・クエリ・ヘッダーを修正。
  • 401 Unauthorized: 未認証。トークンや認証ヘッダーを付与。
  • 403 Forbidden: 権限不足。権限付与やロール確認。
  • 404 Not Found: 見つからない。URL・ID・環境の再確認。
  • 409 Conflict: 競合。リソースの状態と操作順序を調整(楽観ロックなど)。
  • 422 Unprocessable Entity: バリデーションエラー。入力値を修正。
  • 429 Too Many Requests: レート制限。Retry-Afterを尊重して待機。
  • 500/502/503/504: サーバ不調やゲートウェイ問題。短いバックオフで再試行を検討。

実務で効く深掘り(リダイレクト・レート制限・キャッシュ)

リダイレクトの扱い(移動にどう追随するか)

import requests

r = requests.get("https://httpbin.org/redirect-to?url=/get", timeout=5)
print([h.status_code for h in r.history], "->", r.status_code)  # 経路を確認

r = requests.get("https://httpbin.org/redirect-to?url=/get",
                 allow_redirects=False, timeout=5)
if 300 <= r.status_code < 400:
    print("次の場所:", r.headers.get("Location"))
Python

デフォルトで自動追随します。追随を制御したい場合はallow_redirects=FalseにしてLocationを読んで自前対応します。

レート制限(429)への正しい反応

import time, requests

r = requests.get("https://httpbin.org/status/429", timeout=5)
if r.status_code == 429:
    retry_after = r.headers.get("Retry-After")
    wait = int(retry_after) if (retry_after or "").isdigit() else 2
    time.sleep(wait)
    # ここで安全に再試行する
Python

429ではRetry-Afterヘッダーがあればその秒数待機します。無ければ短い固定待機+指数バックオフを使い、叩きすぎを避けます。

キャッシュ連携(304)で無駄を減らす

import requests

etag = None
r1 = requests.get("https://httpbin.org/etag/abc", timeout=5)
etag = r1.headers.get("ETag")

r2 = requests.get("https://httpbin.org/etag/abc",
                  headers={"If-None-Match": etag}, timeout=5)
print(r2.status_code)  # 304なら「変更なし」
Python

ETag/If-None-Matchを使うと変更がない場合304が返り、転送量と処理時間を節約できます。


例外と再試行(事故を防ぐ型)

例外を網羅して安全側に倒す

import requests

url = "https://httpbin.org/status/500"
try:
    r = requests.get(url, timeout=5)
    r.raise_for_status()
except requests.Timeout:
    print("タイムアウト")
except requests.ConnectionError:
    print("接続エラー")
except requests.HTTPError as e:
    print("HTTPエラー:", e)
except requests.RequestException as e:
    print("その他の例外:", e)
Python

Timeout、ConnectionError、HTTPErrorを個別ハンドリングし、最後に親クラスで取りこぼしを防ぎます。

5xxへの指数バックオフ(GETは冪等なので有効)

import time, requests

def get_with_retry(url, tries=3, base=0.5):
    for i in range(tries):
        try:
            r = requests.get(url, timeout=5)
            r.raise_for_status()
            return r
        except requests.HTTPError as e:
            if 500 <= r.status_code < 600 and i < tries - 1:
                time.sleep(base * (2 ** i))  # 0.5,1.0,2.0s...
                continue
            raise
        except (requests.Timeout, requests.ConnectionError):
            if i < tries - 1:
                time.sleep(base * (2 ** i))
                continue
            raise

resp = get_with_retry("https://httpbin.org/status/503")
print(resp.status_code if resp else "失敗")
Python

冪等なGETは5xx限定でバックオフ付きリトライが有効です。POSTは副作用の重複に注意し、idempotency-key対応APIのみ慎重に。


応答本文の扱い(コードに合わせた読み方)

成功時と失敗時で読み方を変える

import requests

r = requests.get("https://httpbin.org/json", timeout=5)
if 200 <= r.status_code < 300:
    data = r.json()  # JSON応答
else:
    ctype = r.headers.get("Content-Type", "")
    if "application/json" in ctype:
        err = r.json()
        print("エラー詳細:", err)
    else:
        print("エラー本文:", r.text[:200])
Python

成功時は本文を通常処理。失敗時はContent-Typeを確認し、JSONなら構造化されたエラー、そうでなければ短縮テキストをログへ回します。


まとめ

レスポンスコードは「成功・移動・誤り・不調」の合図です。requestsではstatus_codeとokで判定、raise_for_statusで失敗を例外化。代表コードの初動(401は認証、403は権限、404はURL/ID確認、429は待機、5xxは短いバックオフ再試行)を型にしておくと迷いません。リダイレクトはhistoryとLocationで制御し、304はキャッシュ連携で効率化。成功・失敗で本文の扱いを変え、例外を網羅して「止めず・壊さず・見落とさない」API連携を実現しましょう。

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