はじめに
Python で HTTP リクエストを送信するライブラリは requests
が有名です。
しかし requests
は同期 I/O のため、asyncio
との相性が悪いです。
デフォルトのトランスポートアダプタが実装されている場合、Requestsは非ブロッキングIOを一切提供しません。Response.content レスポンス全体がダウンロードされるまで、このプロパティはブロックされます。より細かい粒度が必要な場合は、ライブラリのストリーミング機能( ストリーミングリクエストを参照)を使用することで、レスポンスを一度に少量ずつ取得できます。ただし、これらの呼び出しは依然としてブロックされます。
ブロッキングIOの使用に不安がある場合は、RequestsとPythonの非同期フレームワークを組み合わせたプロジェクトが数多くあります。優れた例としては、requests-threads、grequests、requests-futures、httpxなどがあります。
引用元:
この記事では、requests
ライクな API を持ちつつ、同期・非同期の両方に対応したモダンな HTTP クライアントである httpx
を紹介します。
サンプルコード
以下はコルーチン(async/await)でリクエストを送信する方法です。
エントリーポイントで asyncio.run
を使用して async 関数を実行します。
httpx の GET リクエストで await しています。
import asyncio
import httpx
class Fetcher:
"""非同期HTTPリクエストクラス"""
def __init__(self):
self.client = self._create_client()
def _create_client(self):
"""httpxクライアントを作成する"""
client = httpx.AsyncClient()
return client
async def close_connection(self):
"""クライアントの接続を閉じる"""
await self.client.aclose()
async def fetch(self, url):
"""指定されたURLにGETリクエストを送信する"""
response = await self.client.get(url)
print(f"Status Code: {response.status_code}")
response.raise_for_status()
async def main():
url = "https://httpbin.org/get"
fetcher = Fetcher()
# GETリクエストを実行
await fetcher.fetch(url)
# クライアントの接続を閉じる
await fetcher.close_connection()
if __name__ == "__main__":
asyncio.run(main())
※ サンプルコードでは エラーを raise していますがキャッチしていません。以下は例外クラスの参考リンクです。
asyncio と httpx を組み合わせる
以下は 5 つの GET リクエストを並列で行います。/delay/3
にリクエストを送り、ブロッキングが発生していないことを確認します。
import asyncio
import logging
import httpx
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - [%(levelname)s] - %(message)s',
)
logging.getLogger("httpx").setLevel(logging.INFO)
logger = logging.getLogger(__name__)
class Fetcher:
def __init__(self):
self.client = self._create_client()
def _create_client(self):
"""httpxクライアントを作成する"""
client = httpx.AsyncClient()
return client
async def close_connection(self):
"""クライアントの接続を閉じる"""
await self.client.aclose()
async def fetch(self, url):
"""指定されたURLにGETリクエストを送信する"""
response = await self.client.get(url)
response.raise_for_status()
async def main():
url = "https://httpbin.org/delay/3"
fetcher = Fetcher()
# GETリクエストタスクをスケジューリング
tasks = [
asyncio.create_task(fetcher.fetch(url))
for _ in range(5)
]
# タスクを並列実行
await asyncio.gather(*tasks)
await fetcher.close_connection()
if __name__ == "__main__":
asyncio.run(main())
実行結果
レスポンスを得るまで 3 秒待機するエンドポイントへのリクエストですが、ほぼ同時刻にリクエストが行われており、並列でリクエストが実行されていることが確認できます。
2025-08-09 21:18:52,416 - [INFO] - HTTP Request: GET https://httpbin.org/delay/3 "HTTP/1.1 200 OK"
2025-08-09 21:18:52,540 - [INFO] - HTTP Request: GET https://httpbin.org/delay/3 "HTTP/1.1 200 OK"
2025-08-09 21:18:52,777 - [INFO] - HTTP Request: GET https://httpbin.org/delay/3 "HTTP/1.1 200 OK"
2025-08-09 21:18:52,870 - [INFO] - HTTP Request: GET https://httpbin.org/delay/3 "HTTP/1.1 200 OK"
2025-08-09 21:18:53,083 - [INFO] - HTTP Request: GET https://httpbin.org/delay/3 "HTTP/1.1 200 OK"
クライアント
requests
ライブラリには requests.Session()
によりセッションを作成できます。httpx
では httpx.AsyncClient
が相当します。これを利用することでタイムアウトやヘッダーの設定が可能です。
class Fetcher:
def __init__(self):
self.headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/58.0.3029.110 Safari/537.3"
)
}
self.client = self._create_client()
def _create_client(self):
client = httpx.AsyncClient(
headers=self.headers,
timeout=httpx.Timeout(5.0, 10.0)
)
return client
参考