- Python
- 技術 💻
Pythonのrequestsの使い方|タイムアウト・エラー処理・再試行まで
requestsは、PythonでHTTPリクエストを送るための定番のライブラリです。requests.get(URL) の1行でWebページやAPIの結果を取得でき、POSTやJSONの送信、ヘッダーや認証の指定も簡単に書けます。
ただし、実務で使うときには必ず押さえておきたい点があります。タイムアウトを指定しないと、応答がないサーバーに対して待ち続けること、そして404や500が返ってきても、例外にならないことです。この記事では、基本の使い方に加えて、タイムアウト・エラー処理・再試行といった実務で必要な書き方を、実際に動かした結果とあわせて解説します。
インストールと基本の使い方
pip install requests
import requests
res = requests.get("https://api.example.com/users/1", timeout=10)
print(res.status_code) # 200
print(res.json()) # JSONを辞書として取得
| 属性・メソッド | 内容 |
|---|---|
res.status_code |
ステータスコード(200、404など) |
res.ok |
ステータスが400未満なら True |
res.json() |
本文をJSONとして解析し、辞書やリストで返す |
res.text |
本文を文字列で返す |
res.content |
本文をバイト列で返す(画像など) |
res.headers |
レスポンスのヘッダー |
res.encoding |
文字コード(文字化けするときは設定し直す) |
よく使うリクエストの書き方
クエリパラメータ
res = requests.get("https://api.example.com/search", params={"q": "山田", "page": 2}, timeout=10)
print(res.url) # https://api.example.com/search?q=%E5%B1%B1%E7%94%B0&page=2
params に辞書で渡すと、日本語もURLに使える形に自動で変換されます。URLの文字列を自分で組み立てるより安全です。
POSTでJSONを送る
res = requests.post(
"https://api.example.com/users",
json={"name": "山田", "email": "yamada@example.com"},
timeout=10,
)
json= に辞書を渡すと、JSONに変換し、Content-Typeを application/json に設定して送ります。フォーム形式で送る場合は data= を使います。
| 引数 | 送られる形式 |
|---|---|
json={...} |
JSON(application/json) |
data={...} |
フォーム(application/x-www-form-urlencoded) |
files={...} |
ファイルのアップロード(multipart/form-data) |
ヘッダーと認証
import os
headers = {"Authorization": f"Bearer {os.environ['API_TOKEN']}"}
res = requests.get("https://api.example.com/me", headers=headers, timeout=10)
# Basic認証
res = requests.get("https://api.example.com/private", auth=("user", "password"), timeout=10)
APIキーやトークンは、コードに直接書かず、環境変数から読み込みます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Pythonのrequestsの使い方|タイムアウト・エラー処理・再試行まで」
最重要:タイムアウトを必ず指定する
requestsは、既定ではタイムアウトがありません。応答が返ってこないサーバーに接続すると、プログラムが止まったまま待ち続けます。バッチ処理やWebアプリの中で起きると、処理が終わらない・サーバーの処理が詰まる、といった障害につながります。
# 接続に3秒、応答の受信に10秒までを待つ
res = requests.get(url, timeout=(3, 10))
# 両方に同じ値を使う場合
res = requests.get(url, timeout=10)
| 指定 | 意味 |
|---|---|
timeout=10 |
接続と、応答の受信のそれぞれに10秒 |
timeout=(3, 10) |
接続に3秒、応答の受信に10秒 |
実際に、応答に5秒かかるサーバーへ timeout=(3, 1) で接続すると、約1秒で ReadTimeout の例外になりました。
注意:timeout は「全体の処理時間の上限」ではありません。データが少しずつ届き続ける場合は、全体でそれ以上かかることがあります。
404や500は、例外にならない
res = requests.get("https://api.example.com/404", timeout=10)
print(res.status_code) # 404
print(res.ok) # False
# ここで例外は発生しない
requestsは、通信自体が成功していれば、ステータスが404や500でも正常に結果を返します。そのまま res.json() の中身を使うと、エラーの内容を正常なデータとして扱ってしまいます。
res = requests.get(url, timeout=10)
res.raise_for_status() # 400以上なら例外(HTTPError)を発生させる
data = res.json()
実際に404の応答で実行すると、HTTPError: 404 Client Error: Not Found for url: ... の例外になりました。raise_for_status() を呼ぶ習慣をつけておくと、エラーの見落としを防げます。
例外の種類とエラー処理
| 例外 | 発生する場面 |
|---|---|
requests.ConnectionError |
サーバーに接続できない(起動していない、名前解決の失敗など) |
requests.Timeout |
タイムアウト(ConnectTimeout・ReadTimeout) |
requests.HTTPError |
raise_for_status() で、400以上のステータス |
requests.JSONDecodeError |
res.json() で、本文がJSONではない |
requests.RequestException |
上記すべての親。まとめて受け止めるとき |
import requests
def fetch_user(user_id: int) -> dict | None:
try:
res = requests.get(f"https://api.example.com/users/{user_id}", timeout=(3, 10))
res.raise_for_status()
return res.json()
except requests.Timeout:
print("タイムアウトしました")
except requests.HTTPError as e:
if e.response.status_code == 404:
return None # 存在しないのは想定内
print(f"HTTPエラー: {e}")
except requests.RequestException as e:
print(f"通信エラー: {e}")
return None
どのエラーは想定内で、どのエラーは異常かを区別して扱うのがポイントです(この例では、404は「ユーザーがいない」という想定内の結果として扱っています)。
Sessionで効率よく通信する
同じサーバーに何度もリクエストを送る場合は、Session を使います。
with requests.Session() as s:
s.headers.update({"Authorization": f"Bearer {token}"})
for user_id in range(1, 101):
res = s.get(f"https://api.example.com/users/{user_id}", timeout=10)
| Sessionを使う利点 | 内容 |
|---|---|
| 接続の再利用 | 毎回の接続・暗号化の準備を省けるため、速くなる |
| 共通の設定 | ヘッダーや認証を1回設定すれば、すべてのリクエストに付く |
| Cookieの保持 | ログイン後のCookieを、以降のリクエストで自動で送る |
再試行(リトライ)を設定する
一時的なエラー(503や接続の失敗)に備えて、自動で再試行する設定ができます。
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=3, # 最大3回まで再試行
backoff_factor=0.5, # 待ち時間を倍々に延ばす
status_forcelist=[429, 500, 502, 503, 504], # 再試行するステータス
allowed_methods=["GET"], # 再試行してよいメソッド
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))
session.mount("http://", HTTPAdapter(max_retries=retry))
res = session.get(url, timeout=10)
実際に、2回続けて503を返してから200を返すサーバーで試すと、自動で再試行し、最終的に200で成功しました。
backoff_factorを指定すると、再試行の間隔が指数的に延びます(指数バックオフ)- 429や503で
Retry-Afterヘッダーが返されると、既定ではその指示に従って待ちます - POSTは、既定では再試行されません。登録や決済が二重に実行されるのを防ぐためで、POSTを再試行する場合は、サーバー側で二重処理を防ぐ仕組み(冪等キー)が必要です
大きなファイルをダウンロードする
with requests.get(url, stream=True, timeout=(3, 30)) as res:
res.raise_for_status()
with open("large.zip", "wb") as f:
for chunk in res.iter_content(chunk_size=1024 * 1024): # 1MBずつ
f.write(chunk)
stream=True を付けないと、ファイル全体をメモリに読み込んでから処理するため、大きなファイルではメモリが不足します。
ファイルのアップロード
with open("report.pdf", "rb") as f:
res = requests.post(url, files={"file": f}, data={"title": "月次報告"}, timeout=30)
文字化けするとき
res = requests.get(url, timeout=10)
print(res.encoding) # 例:ISO-8859-1(誤った判定)
res.encoding = res.apparent_encoding # 本文から文字コードを推定し直す
print(res.text)
サーバーが文字コードを正しく返していないと、res.text が文字化けします。apparent_encoding で推定し直すか、res.encoding = "utf-8" のように明示します。
SSL証明書のエラー
requests.get(url, verify=False) # × 証明書の検証を無効にする(使わない)
requests.get(url, verify="/path/to/company-ca.pem") # ○ 社内の証明書を指定する
verify=False は、通信相手が本物かどうかを確認しないため、通信を盗み見られる危険があります。社内のプロキシなどで証明書のエラーが出る場合は、信頼する証明書のファイルを指定します。
requestsとhttpxの違い
| requests | httpx | |
|---|---|---|
| 非同期処理(async/await) | 対応していない | 対応している |
| HTTP/2 | 対応していない | 対応している(オプション) |
| 既定のタイムアウト | なし | 5秒 |
| 書き方 | ― | requestsとほぼ同じ |
| 実績・情報の多さ | 非常に多い | 増えている |
同期処理で十分なスクリプトやバッチならrequests、FastAPIなど非同期のアプリケーションの中でHTTPリクエストを送るならhttpxが向いています。
よくある質問
Q. timeoutはどれくらいに設定すべきですか? A. 接続は数秒、応答は相手のAPIの処理時間に合わせて10〜30秒程度が目安です。画面の操作に関わる処理なら短めにし、長い処理が必要なら非同期の仕組みを検討します。
Q. APIの回数制限(429エラー)に引っかかります。
A. レスポンスの Retry-After ヘッダーの秒数だけ待ってから再試行します。上記の Retry の設定でも対応できます。大量に呼ぶ場合は、呼び出しの間隔をあけるか、まとめて取得できるAPIがないか確認します。
Q. プロキシを経由したいです。
A. proxies={"https": "http://proxy.example.com:8080"} を指定するか、環境変数 HTTPS_PROXY を設定します。
Q. requests.get と Session.get の違いは何ですか?
A. requests.get は、毎回新しい接続を作って閉じます。同じサーバーに何度もアクセスする場合は、接続を再利用できる Session の方が効率的です。
まとめ
- 基本は
requests.get(url, timeout=...)、JSONを送るならjson=、クエリはparams= - タイムアウトは既定でなし。必ず
timeout=を指定する - 404や500でも例外にならない。
raise_for_status()でエラーにする - 例外は Timeout・ConnectionError・HTTPError、まとめて受けるなら RequestException
- 同じサーバーへの繰り返しは Session、一時的なエラーには Retry で再試行(POSTは慎重に)
- 大きなファイルは stream=True、verify=False は使わない
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Pythonのrequestsの使い方|タイムアウト・エラー処理・再試行まで」
株式会社RJCでは、AIを活用した個人開発や業務の自動化でも、エラーに強いコードを書く力を大切にしています。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。