概要(APIエラー処理は「失敗を見落とさない・止めない・壊さない」ための型づくり)
APIは必ず失敗します。通信が詰まる、サーバが落ちる、仕様が変わる、レート制限に当たる。だから「すべて成功前提」で書くのではなく、「失敗を検知して止めずに安全側へ倒す」型を最初から組み込みます。核心は、ステータスコードの検査と例外化、タイムアウト、応答形式の確認、メッセージのログ化、必要に応じた再試行(慎重に)、そして「非冪等な操作は二重実行させない」ことです。
ここが重要(最小セットの安全対策)
成功・失敗を例外で判定する
import requests
resp = requests.get("https://httpbin.org/status/500", timeout=5)
try:
resp.raise_for_status() # 4xx/5xx を HTTPError にする
except requests.HTTPError as e:
print("HTTPエラー:", e)
Pythonraise_for_status を必ず呼ぶと、失敗を見落とさずに「例外として扱う」ことができます。以降の処理が壊れたデータで続行される事故を防げます。
タイムアウトで「待ち続け」を止める
import requests
try:
r = requests.get("https://httpbin.org/delay/3", timeout=1.5)
except requests.Timeout:
print("タイムアウトにより中断")
Pythontimeout は毎回付けます。ネットワーク不調で処理が固まる問題を根本から避けられます。
応答形式を確認してから JSON を読む
import requests
r = requests.get("https://httpbin.org/anything", timeout=5)
r.raise_for_status()
ctype = r.headers.get("Content-Type", "")
if "application/json" in ctype:
data = r.json()
else:
print("JSON以外の応答:", ctype, r.text[:120])
PythonAPIがエラー時にHTMLやテキストを返すことは珍しくありません。Content-Type を見てから json() を呼ぶと安全です。
代表的な失敗と対策(実務でよく遭遇するもの)
クライアント側の例外を押さえる
import requests
url = "https://example.invalid"
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)
PythonTimeout、ConnectionError、HTTPError を個別に扱い、最後は親クラス RequestException で網羅します。
サーバ側のステータスに応じた分岐
import requests
r = requests.get("https://httpbin.org/status/429", timeout=5)
if r.status_code == 429:
retry_after = r.headers.get("Retry-After")
print("レート制限。待機指示:", retry_after)
elif 500 <= r.status_code < 600:
print("サーバ一時不調。後で再試行を検討")
else:
r.raise_for_status()
Python429 はレート制限、5xx はサーバの一時不調の可能性が高いので、再試行の対象になります。一方で 4xx はクライアントの指定が間違っていることが多く、再試行より原因修正が先です。
エラー応答のJSONから意味を取り出す
import requests
r = requests.get("https://httpbin.org/status/400", timeout=5)
if r.status_code >= 400:
try:
err = r.json()
print("エラー詳細:", err)
except ValueError:
print("テキストエラー:", r.text[:120])
PythonAPIはエラー時に JSON で code・message を返すことが多いので、そこから「ユーザへ見せる文言」「ログへ出す詳細」を分けて使います。
再試行(リトライ)の設計(副作用に配慮したやり方)
安全な自動リトライ(GETなど冪等な処理)
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retry = Retry(
total=3,
backoff_factor=0.5, # 0.5, 1.0, 2.0... と間隔を伸ばす
status_forcelist=[500, 502, 503, 504] # 一時不調のみ対象
)
session.mount("https://", HTTPAdapter(max_retries=retry))
resp = session.get("https://httpbin.org/status/503", timeout=5)
print("最終ステータス:", resp.status_code)
Python冪等(同じ要求を繰り返しても状態が変わらない)な GET は、5xx に限って指数バックオフでリトライすると安定します。
POST のリトライは慎重に(重複実行の回避)
課金や登録などの POST は非冪等です。再送すると二重実行になり得ます。サーバが idempotency-key(同一キーなら一度だけ処理)に対応している場合のみ、キーを付けて安全に再試行します。
予防と検証(壊れないための前工程)
入力の型と必須キーを軽くバリデート
def validate_payload(p):
assert isinstance(p, dict), "辞書が必要"
assert "user" in p and "score" in p, "必須キーが不足"
payload = {"user": "taro", "score": 88}
validate_payload(payload)
Python送る前に最小限の検証を入れると、400系のエラーを減らせます。重いスキーマ検証は後からでも良いですが、必須キーの確認だけは即効性があります。
クエリ・ヘッダー・Content-Type の整合を取る
import requests
params = {"q": "python", "page": 1}
headers = {"Accept": "application/json"}
r = requests.get("https://httpbin.org/get", params=params, headers=headers, timeout=5)
r.raise_for_status()
PythonJSON を期待するなら Accept を合わせ、送るときは json= を使い Content-Type を自動で正しく付与します。
ログと通知(原因追跡できる形を残す)
最低限のログ項目
URL、メソッド、ステータスコード、主要ヘッダー(Retry-After など)、短縮した応答本文、例外メッセージ。個人情報・秘密鍵はログに残さないことを徹底します。
失敗の見せ方
ユーザ向けには短く状況を伝えるメッセージ(「混雑しています。少し待って再度お試しください」)。ログには技術詳細(「HTTP 503、再試行3回後に失敗」)。同じメッセージを使い回すのではなく、状況に応じて切り替えます。
例題で身につける(定番から実務まで)
例題1 成功可否とJSON判定の基本線
import requests
r = requests.get("https://httpbin.org/json", timeout=5)
r.raise_for_status()
ctype = r.headers.get("Content-Type", "")
if "application/json" in ctype:
print("OK:", list(r.json().keys()))
else:
print("想定外の応答形式:", ctype)
Python例題2 例外を網羅して安全側へ倒す
import requests
url = "https://httpbin.org/status/500"
try:
resp = requests.get(url, timeout=3)
resp.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例題3 レート制限を尊重して待機
import time, requests
r = requests.get("https://httpbin.org/status/429", timeout=5)
if r.status_code == 429:
ra = r.headers.get("Retry-After")
wait = int(ra) if ra and ra.isdigit() else 2
print(f"{wait}秒待機後に再試行")
time.sleep(wait)
# 実際はここで安全に再試行
Python例題4 セッション+指数バックオフで安定化(GET)
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
sess = requests.Session()
sess.mount("https://", HTTPAdapter(max_retries=Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])))
resp = sess.get("https://httpbin.org/status/503", timeout=5)
print("ステータス:", resp.status_code)
Pythonまとめ
APIエラー処理の土台は、raise_for_statusで失敗を例外化し、timeoutで固まりを防ぎ、Content-Typeで応答形式を確認すること。429や5xxには慎重な再試行を、POSTなど非冪等操作には重複実行の回避策を。入力バリデーションで自分由来の 4xx を減らし、ログは原因追跡に十分でありつつ機密を残さない。これらを「型」として最初から組み込めば、初心者でも短く、壊れないAPI連携が自然に書けるようになります。
