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クライアントを作れること
を、コード例とともに整理しました。
ここから先は、
- ログ出力
- アラート通知
- エラー内容に応じた分岐(再試行・中断・代替処理)
などを組み合わせて、 より「現場でそのまま使える」エラー処理へと育てていくことができます。
