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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?