Web APIを何度も呼ぶと、毎回の接続確立やTLSハンドシェイクが積み上がり、思った以上に遅くなります。
requests.Session
を使えば接続(Keep-Alive)やヘッダー、クッキーを再利用でき、通信の無駄を減らせます。
この記事では基本から実践まで丁寧に解説します。
requests.Sessionとは?接続再利用でAPIを高速化
同じホストへ繰り返しアクセスするならrequests.Session
を使うだけで速く、安定した通信が期待できます。
内部で接続をプーリングし、HTTPのKeep-Aliveを活用して無駄なハンドシェイクを減らすためです。
初心者の方でも、まずは「毎回のrequests.get
ではなく、1つのセッションでまとめる」と覚えておくと良いです。
セッションが速い理由(Keep-Alive)
HTTP/1.1では持続的接続(Keep-Alive)が標準で有効です。
requests.Session
を使うと、内部でコネクションプールが作られ、同じホストへの後続リクエストは既存のTCP接続を再利用します。
これにより以下のコストが減ります。
- TCPハンドシェイク(3-way handshake)
- TLSハンドシェイク(HTTPSの証明書検証など)
- 接続確立待ち時間
接続再利用は「1回あたりの数十ミリ秒〜数百ミリ秒」を積み重ねて短縮します。
大量のAPI呼び出しを行う場面で特に効果が大きくなります。
単発requests.getとの違い
requests.get(...)
を直接呼ぶたびに内部ではSession
が都度作られ、利用後に破棄されます。
そのため、呼び出しごとに新しい接続を張り直す可能性が高くなります。
一方で、requests.Session
を明示的に使うと、接続・クッキー・ヘッダーなどが継続して利用されます。
以下に違いを簡単にまとめます。
比較項目 | 単発のrequests.get | requests.Session |
---|---|---|
接続確立 | 毎回やり直す傾向 | 再利用(プール) |
パフォーマンス | 遅くなりやすい | 速くなりやすい |
Cookie保持 | 呼び出し間で失われる | 自動で保持 |
共通ヘッダー | 毎回指定が必要 | 1回設定で使い回し |
ベストプラクティス | 周回処理では非推奨 | 周回処理で推奨 |
初心者が押さえるメリット
速い、書きやすい、安定しやすいの3点が要点です。
具体的には、共通ヘッダーや認証トークンを一度設定して使い回せるため、コードが読みやすくなり、また接続再利用で高速化されます。
Cookieを跨いで維持できるため、ログイン後の連続アクセスなども簡単です。
基本の使い方
ここでは最小限のパターンから段階的に使い方を解説します。
まずは1つのSession
を作り、使い終わったら閉じる、という流れを習得しましょう。
Sessionを作成する
requests.Session()
でセッションを作成し、同じオブジェクトで複数回APIを呼びます。
# 基本: Sessionを作ってGETする
import requests
# セッションを作成
s = requests.Session()
# サンプルAPIにアクセス
resp = s.get("http://httpbin.org/get", timeout=10)
# ステータスや一部ヘッダーを確認
print("status:", resp.status_code)
print("connection:", resp.headers.get("Connection"))
# 使い終わったら明示的にクローズ
s.close()
status: 200
connection: keep-alive
with文で自動でcloseする
Pythonのコンテキストマネージャを使えば、ブロックを抜けたときに自動でクローズされます。
これが最も安全で簡単なパターンです。
# with文で使うと自動でcloseされます
import requests
url = "http://httpbin.org/get"
with requests.Session() as s:
r = s.get(url, timeout=10)
print("ok?", r.ok)
# withブロックを出ると自動でs.close()が呼ばれる
ok? True
GETやPOSTの呼び出し例(API)
同じセッションでGETとPOSTを続けて呼ぶ例です。
クッキーや接続が自動で引き継がれます。
# GETとPOSTを同じセッションで呼ぶ例
import requests
with requests.Session() as s:
# GET: クエリ文字列を付与
r1 = s.get("https://httpbin.org/get", params={"query": "apple"}, timeout=10)
data1 = r1.json()
print("GET args:", data1["args"])
# POST: JSONボディを送信
r2 = s.post("https://httpbin.org/post", json={"name": "Alice"}, timeout=10)
data2 = r2.json()
print("POST JSON:", data2["json"])
GET args: {'query': 'apple'}
POST JSON: {'name': 'Alice'}
共通ヘッダーを一度だけ設定する
s.headers.update(...)
でセッション共通のヘッダーを付与できます。
毎回同じヘッダーを書く必要がなくなり、ミスの防止にもつながります。
# 共通ヘッダーをセッションに設定
import requests
with requests.Session() as s:
s.headers.update({
"User-Agent": "my-app/1.0",
"X-API-Client": "session-demo"
})
r = s.get("https://httpbin.org/headers", timeout=10)
h = r.json()["headers"]
print("User-Agent:", h.get("User-Agent"))
print("X-API-Client:", h.get("X-Api-Client"))
User-Agent: my-app/1.0
X-API-Client: session-demo
すぐ使えるパターン
ここでは実務や学習でそのまま役立つ具体例を取り上げます。
連続リクエストをまとめて送る
同じホストへ複数回アクセスする処理では、1つのSessionを使い回すだけで時間短縮が期待できます。
目安として、手元の環境の一例を示します(値は環境で変わります)。
# 単発requestsとSession使い回しの簡易計測
import time
import requests
URL = "https://httpbin.org/get"
N = 10
def no_session():
start = time.perf_counter()
for _ in range(N):
requests.get(URL, timeout=10)
return time.perf_counter() - start
def with_session():
start = time.perf_counter()
with requests.Session() as s:
for _ in range(N):
s.get(URL, timeout=10)
return time.perf_counter() - start
t1 = no_session()
t2 = with_session()
print(f"no_session: {t1:.3f}s")
print(f"with_session: {t2:.3f}s")
print("speedup x", round(t1 / t2, 2))
no_session: 9.075s
with_session: 2.520s
speedup x 3.6
ネットワーク環境によって差は大きく異なりますが、多数回の呼び出しほど差が広がる傾向があります。
認証トークンをSessionに保持する
BearerトークンやAPIキーを1回設定しておけば、以降の全リクエストに適用できます。
安全な保管と最小権限の原則は別途守ってください。
# 認証トークンをセッションに設定して使い回す
import requests
token = "abc123-example-token" # 実際は安全な保管・取得方法を用いてください
with requests.Session() as s:
s.headers.update({"Authorization": f"Bearer {token}"})
r = s.get("https://httpbin.org/bearer", timeout=10)
data = r.json()
print("authenticated:", data.get("authenticated"))
print("token echoed:", data.get("token"))
authenticated: True
token echoed: abc123-example-token
JSON APIを何度も呼ぶときに効果大
JSON APIを短時間に何度も呼ぶ処理は、接続再利用の恩恵を受けやすいです。
下記はUUIDを複数回取得する例です。
# JSON APIを繰り返し呼ぶ例(接続再利用で効率化)
import requests
with requests.Session() as s:
uuids = []
for _ in range(5):
r = s.get("https://httpbin.org/uuid", timeout=10)
uuids.append(r.json()["uuid"])
print("uuids:", uuids[:3], "...") # 先頭3つだけ表示
uuids: ['2af3...', '9c41...', '8b27...'] ...
注意点とベストプラクティス
Sessionは作りっぱなしでも毎回作り直しでも良くありません。
以下のポイントを押さえて、安全かつ効率的に使いましょう。
毎回Sessionを新規作成しない
ループ内でrequests.Session()
を毎回生成すると、接続再利用ができず遅いだけでなく、リソースリークの原因にもなります。
処理単位(アプリ全体・バッチ1回分など)で1つを長めに使うのが基本です。
使い終わったら必ずcloseする
with
の利用が最も簡単です。
明示的に管理する場合はtry/finally
で閉じるのが安全です。
# 明示的にcloseを保証するパターン
import requests
s = requests.Session()
try:
r = s.get("https://httpbin.org/get", timeout=10)
print("status:", r.status_code)
finally:
s.close() # ここで確実に解放
status: 200
セッションを広い範囲で使い回す(関数外で保持)
モジュールレベルに1つのセッションを用意し、関数から参照するパターンは実務でよく使われます。
アプリ終了時にclose
する工夫も入れておきます。
# モジュール全体で1つのSessionを共有する例
import atexit
import requests
session = requests.Session()
session.headers.update({"User-Agent": "my-batch/2.0"})
atexit.register(session.close) # プロセス終了時に自動close
def fetch_json(url: str):
# ここでは同じsessionを使い回す
resp = session.get(url, timeout=10)
resp.raise_for_status()
return resp.json()
if __name__ == "__main__":
data = fetch_json("https://httpbin.org/get")
print("url:", data["url"])
url: https://httpbin.org/get
高負荷環境ではプールサイズを増やす必要があるかもしれません。
HTTPAdapter
で調整できます(出力なしの設定例)。
# コネクションプールのサイズ調整(高度な例)
import requests
from requests.adapters import HTTPAdapter
s = requests.Session()
adapter = HTTPAdapter(pool_connections=100, pool_maxsize=100) # 目安。要件に合わせて調整
s.mount("https://", adapter)
s.mount("http://", adapter)
# 以降、sで高並列・高頻度の呼び出しに対応しやすくなる
並列処理での共有は避ける
Sessionはスレッドセーフであると明示されていません。
1つのSessionを複数スレッドで同時共有するのは避け、スレッド(またはプロセス)ごとにSessionを作るのが基本です。
# 良い例: ワーカーごとにSessionを作成する
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
def get_uuid():
# 各ワーカー内でSessionを作成して破棄
with requests.Session() as s:
r = s.get("https://httpbin.org/uuid", timeout=10)
return r.json()["uuid"]
uuids = []
with ThreadPoolExecutor(max_workers=3) as ex:
futures = [ex.submit(get_uuid) for _ in range(5)]
for f in as_completed(futures):
uuids.append(f.result())
print("count:", len(uuids))
print("sample:", uuids[0][:8], "...")
count: 5
sample: a1b2c3d4 ...
注意: マルチプロセスでも同様に、プロセスごとにSessionを生成してください。
フォーク後に親プロセスのSessionを使い回すのは避けましょう。
まとめ
requests.Session
は「接続・ヘッダー・クッキーの再利用」によりAPI通信を高速化し、コードを簡潔にします。
ポイントは次の通りです。
まず、同じホストに繰り返しアクセスする処理では必ずSessionを使うこと。
次に、使い終わったら必ずcloseすること。
そして、並列処理では共有せず、ワーカーごとにSessionを持つことです。
これらを守るだけで、多くのWeb・API開発において、パフォーマンスと可読性の両方を手に入れることができます。