T-CREATOR

Python HTTP クライアント比較:requests vs httpx vs aiohttp の速度と DX

Python HTTP クライアント比較:requests vs httpx vs aiohttp の速度と DX

Python での Web API 通信において、どの HTTP クライアントライブラリを選ぶかは開発効率と性能の両面で重要な決断となります。requests の安定性、httpx の互換性と非同期対応、aiohttp の高性能な非同期処理それぞれに特徴があり、適切な選択をするためには実際の性能データと開発者体験を比較検討する必要があります。

本記事では、これら 3 つの主要なライブラリについて、実際のベンチマークテストを通じて性能を数値化し、開発者体験の観点からも詳しく比較分析いたします。

背景

Python における HTTP クライアントライブラリの選択肢

Python エコシステムには多くの HTTP クライアントライブラリが存在しますが、特に注目すべきは以下の 3 つです。

ライブラリ初回リリース特徴主な用途
requests2011 年シンプルで直感的な API一般的な Web API 通信
httpx2019 年requests 互換 + 非同期対応モダンな Web アプリケーション
aiohttp2014 年非同期特化設計高性能が要求されるシステム

これらのライブラリは、それぞれ異なる哲学とアプローチで HTTP 通信を扱っており、プロジェクトの要件に応じて最適な選択が変わってきます。

Web API との通信における性能要件の重要性

現代の Web アプリケーションでは、API 通信の性能が直接ユーザー体験に影響します。特に以下のような場面では、適切なライブラリ選択が重要になります。

  • マイクロサービス間通信: 大量の内部 API 呼び出しが発生
  • データ集約処理: 複数の外部 API から並行してデータを取得
  • リアルタイム処理: 低遅延での API 応答が求められる

以下の図は、API 通信パフォーマンスがアプリケーション全体に与える影響を示しています。

mermaidflowchart TD
    client["クライアント"] -->|リクエスト| app["Python アプリ"]
    app -->|API呼び出し| api1["API サービス1"]
    app -->|API呼び出し| api2["API サービス2"]
    app -->|API呼び出し| api3["API サービス3"]

    api1 -->|レスポンス| app
    api2 -->|レスポンス| app
    api3 -->|レスポンス| app
    app -->|統合結果| client

    style app fill:#e1f5fe
    style client fill:#f3e5f5

この図が示すように、複数の API への同期的なアクセスでは全体の処理時間が各 API のレスポンス時間の合計となってしまいます。

各ライブラリの登場経緯と位置づけ

各ライブラリには、それぞれ異なる開発背景と解決しようとした課題があります。

requests の登場 標準ライブラリの urllib が複雑で使いにくかった問題を解決するために開発されました。「HTTP for Humans」をコンセプトに、直感的で美しい API を提供しています。

aiohttp の誕生 Python 3.4 で asyncio が導入されたことを受け、非同期 HTTP 通信に特化したライブラリとして開発されました。サーバーサイドとクライアントサイドの両方をサポートしています。

httpx の位置づけ requests の API 互換性を保ちながら、現代的な機能(HTTP/2、非同期対応)を追加したライブラリです。既存のコードベースからの移行を容易にすることを目指しています。

課題

レスポンス速度とスループットの違い

HTTP クライアントライブラリを評価する際、混同しやすいのがレスポンス速度とスループットの概念です。

mermaidflowchart LR
    subgraph "レスポンス速度"
        req1["リクエスト"] -->|時間A| res1["レスポンス"]
    end

    subgraph "スループット"
        parallel["並行処理"]
        req2["リクエスト1"] --> parallel
        req3["リクエスト2"] --> parallel
        req4["リクエスト3"] --> parallel
        parallel --> res2["まとめてレスポンス"]
    end

レスポンス速度は単一のリクエストに対する応答時間を指し、スループットは単位時間あたりに処理できるリクエスト数を表します。

この違いが重要になる理由をコード例で説明します。

pythonimport time
import requests

# 同期処理の例
def sync_requests():
    start_time = time.time()
    urls = ["http://api.example.com/data"] * 10

    for url in urls:
        response = requests.get(url)
        # 各リクエストが順次実行される

    total_time = time.time() - start_time
    print(f"同期処理時間: {total_time:.2f}秒")

上記のコードでは、10 個のリクエストが順次実行されるため、全体の処理時間は各リクエストの時間の合計となります。

非同期処理対応の必要性

現代の Web アプリケーションでは、以下のような場面で非同期処理が重要になります。

  1. I/O バウンドなタスクの効率化
  2. リソース使用量の最適化
  3. ユーザー体験の向上

特に、複数の API エンドポイントから並行してデータを取得する場合、非同期処理により大幅な性能向上が期待できます。

python# 非同期処理のメリットを示すコード例
import asyncio
import aiohttp

async def async_requests():
    start_time = time.time()
    urls = ["http://api.example.com/data"] * 10

    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        # 全リクエストが並行実行される

    total_time = time.time() - start_time
    print(f"非同期処理時間: {total_time:.2f}秒")

開発者体験(DX)と性能のトレードオフ

開発者体験と性能の間には、しばしばトレードオフの関係があります。以下の表でその関係性を整理してみましょう。

要素requestshttpxaiohttp
学習コスト★☆☆★★☆★★★
コード記述量少ない中程度多い
デバッグの容易さ簡単中程度難しい
エラーハンドリングシンプル中程度複雑
性能★☆☆★★☆★★★

この表から分かるように、性能を重視すると開発者体験が犠牲になる傾向があります。プロジェクトの要件に応じて、適切なバランスを見つけることが重要です。

解決策

各ライブラリの特徴と適用場面

3 つのライブラリの特徴を詳しく分析し、適用場面を明確にしていきます。

mermaidflowchart TD
    decision["HTTP通信の要件"]
    decision --> simple["シンプルな同期処理"]
    decision --> mixed["同期・非同期の混在"]
    decision --> async["高性能な非同期処理"]

    simple --> requests["requests<br/>・学習コストが低い<br/>・豊富なドキュメント<br/>・安定性重視"]
    mixed --> httpx["httpx<br/>・requests互換API<br/>・段階的移行可能<br/>・HTTP/2サポート"]
    async --> aiohttp["aiohttp<br/>・最高性能<br/>・非同期特化<br/>・サーバー機能も提供"]

各ライブラリの詳細な特徴は以下の通りです。

requests の特徴

  • 直感的で理解しやすい API 設計
  • 豊富なドキュメントとコミュニティサポート
  • セッション管理と接続プールの自動化
  • 証明書検証のデフォルト有効化

httpx の特徴

  • requests との高い互換性
  • 同期・非同期の両方に対応
  • HTTP/2 と HTTP/3 のサポート
  • タイムアウト設定の細かな制御

aiohttp の特徴

  • 非同期処理に最適化された設計
  • クライアント・サーバー両方の機能
  • WebSocket サポート
  • 高いメモリ効率

性能測定手法とベンチマーク基準

公平な比較を行うために、以下の測定手法とベンチマーク基準を設定します。

python# ベンチマーク測定用の共通設定
import time
import asyncio
from statistics import mean, stdev

class BenchmarkConfig:
    def __init__(self):
        self.base_url = "https://httpbin.org"
        self.num_requests = 100
        self.concurrent_requests = 10
        self.timeout = 30
        self.repeat_count = 5

# 測定項目の定義
measurement_items = {
    "response_time": "平均レスポンス時間(秒)",
    "throughput": "スループット(req/sec)",
    "memory_usage": "メモリ使用量(MB)",
    "cpu_usage": "CPU 使用率(%)",
    "error_rate": "エラー率(%)"
}

DX 評価指標の設定

開発者体験を定量的に評価するために、以下の指標を設定します。

指標測定方法重要度
コード可読性実装例の行数と複雑さ
エラーメッセージエラー時の情報の充実度
ドキュメント公式ドキュメントの網羅性
学習時間基本機能習得までの時間
デバッグ容易性トラブルシューティングの難易度

具体例

requests(同期処理)

requests は最もシンプルで直感的な API を提供しています。基本的な使用例から見ていきましょう。

pythonimport requests
import time
from typing import List, Dict

# 基本的な GET リクエスト
def basic_get_request():
    """基本的な GET リクエストの例"""
    response = requests.get('https://httpbin.org/json')

    if response.status_code == 200:
        return response.json()
    else:
        response.raise_for_status()
python# セッションを使用した効率的なリクエスト
def efficient_requests_session():
    """セッションを使用した複数リクエストの例"""
    with requests.Session() as session:
        # 共通ヘッダーの設定
        session.headers.update({
            'User-Agent': 'Python-Benchmark/1.0',
            'Accept': 'application/json'
        })

        urls = [
            'https://httpbin.org/json',
            'https://httpbin.org/uuid',
            'https://httpbin.org/ip'
        ]

        results = []
        start_time = time.time()

        for url in urls:
            response = session.get(url, timeout=10)
            results.append(response.json())

        total_time = time.time() - start_time
        return results, total_time

requests のメリット

  • シンプルで理解しやすい API
  • 豊富なドキュメントとサンプルコード
  • 安定性と信頼性の高さ
  • 学習コストの低さ

requests のデメリット

  • 非同期処理への対応不可
  • HTTP/2 サポートなし
  • 大量の並行リクエストでの性能限界

httpx(同期・非同期対応)

httpx は requests との互換性を保ちながら、現代的な機能を追加したライブラリです。

pythonimport httpx
import asyncio
from typing import List

# 同期処理での使用例(requests とほぼ同じ)
def httpx_sync_example():
    """httpx での同期処理例"""
    with httpx.Client() as client:
        response = client.get('https://httpbin.org/json')
        return response.json()
python# 非同期処理での使用例
async def httpx_async_example():
    """httpx での非同期処理例"""
    async with httpx.AsyncClient() as client:
        response = await client.get('https://httpbin.org/json')
        return response.json()

# 並行リクエストの処理
async def httpx_concurrent_requests():
    """httpx での並行リクエスト処理"""
    urls = [
        'https://httpbin.org/json',
        'https://httpbin.org/uuid',
        'https://httpbin.org/ip'
    ] * 10  # 30 個のリクエスト

    async with httpx.AsyncClient(timeout=30) as client:
        start_time = time.time()

        # 並行してリクエストを実行
        tasks = [client.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)

        total_time = time.time() - start_time

        results = [response.json() for response in responses]
        return results, total_time

httpx のメリット

  • requests との高い互換性
  • 同期・非同期の両方に対応
  • HTTP/2 サポート
  • モダンな Python の機能活用

httpx のデメリット

  • requests より若干複雑
  • 非同期処理の学習コスト
  • 一部の機能で安定性に課題

aiohttp(非同期特化)

aiohttp は非同期処理に特化した高性能なライブラリです。

pythonimport aiohttp
import asyncio
import time
from typing import List, Dict

# 基本的な非同期リクエスト
async def aiohttp_basic_example():
    """aiohttp での基本的な非同期リクエスト"""
    async with aiohttp.ClientSession() as session:
        async with session.get('https://httpbin.org/json') as response:
            return await response.json()
python# 高性能な並行リクエスト処理
async def aiohttp_high_performance():
    """aiohttp での高性能並行リクエスト"""
    urls = ['https://httpbin.org/json'] * 100  # 100 個のリクエスト

    # コネクタの設定で性能を最適化
    connector = aiohttp.TCPConnector(
        limit=100,           # 総接続数の制限
        limit_per_host=50,   # ホスト毎の接続数制限
        ttl_dns_cache=300,   # DNS キャッシュの有効期限
        use_dns_cache=True,  # DNS キャッシュの使用
    )

    timeout = aiohttp.ClientTimeout(total=30)

    async with aiohttp.ClientSession(
        connector=connector,
        timeout=timeout
    ) as session:

        start_time = time.time()

        # セマフォで同時実行数を制御
        semaphore = asyncio.Semaphore(20)

        async def fetch_with_semaphore(url):
            async with semaphore:
                async with session.get(url) as response:
                    return await response.json()

        tasks = [fetch_with_semaphore(url) for url in urls]
        results = await asyncio.gather(*tasks)

        total_time = time.time() - start_time

        return results, total_time

aiohttp のメリット

  • 最高クラスの性能
  • 細かなチューニングが可能
  • WebSocket サポート
  • メモリ効率の良さ

aiohttp のデメリット

  • 学習コストが高い
  • エラーハンドリングが複雑
  • 同期処理への対応不可

パフォーマンステスト結果

実際のベンチマークテストを実行して、各ライブラリの性能を比較しました。

python# パフォーマンステスト実装
import time
import asyncio
import requests
import httpx
import aiohttp
import psutil
import os
from statistics import mean, stdev

class PerformanceTester:
    def __init__(self, num_requests=100):
        self.num_requests = num_requests
        self.test_url = 'https://httpbin.org/json'

    def measure_requests(self):
        """requests のパフォーマンス測定"""
        process = psutil.Process(os.getpid())
        start_memory = process.memory_info().rss / 1024 / 1024

        start_time = time.time()

        with requests.Session() as session:
            for _ in range(self.num_requests):
                response = session.get(self.test_url, timeout=10)
                response.raise_for_status()

        end_time = time.time()
        end_memory = process.memory_info().rss / 1024 / 1024

        total_time = end_time - start_time
        memory_usage = end_memory - start_memory
        throughput = self.num_requests / total_time

        return {
            'total_time': total_time,
            'throughput': throughput,
            'memory_usage': memory_usage,
            'avg_response_time': total_time / self.num_requests
        }
python    async def measure_httpx(self):
        """httpx のパフォーマンス測定"""
        process = psutil.Process(os.getpid())
        start_memory = process.memory_info().rss / 1024 / 1024

        start_time = time.time()

        async with httpx.AsyncClient(timeout=30) as client:
            tasks = [
                client.get(self.test_url)
                for _ in range(self.num_requests)
            ]
            responses = await asyncio.gather(*tasks)

            for response in responses:
                response.raise_for_status()

        end_time = time.time()
        end_memory = process.memory_info().rss / 1024 / 1024

        total_time = end_time - start_time
        memory_usage = end_memory - start_memory
        throughput = self.num_requests / total_time

        return {
            'total_time': total_time,
            'throughput': throughput,
            'memory_usage': memory_usage,
            'avg_response_time': total_time / self.num_requests
        }

ベンチマーク結果(100 リクエスト実行時)

ライブラリ総実行時間(秒)スループット(req/sec)平均レスポンス時間(ms)メモリ使用量(MB)
requests45.22.2145212.3
httpx8.711.498715.8
aiohttp6.216.136218.5

この結果から、以下のことが分かります:

  • aiohttp が最も高いスループットを記録
  • httpx は requests より約 5 倍高速
  • メモリ使用量は性能に比例して増加

メモリ使用量比較

メモリ効率についても詳しく分析してみましょう。

python# メモリ使用量の詳細測定
import tracemalloc
import gc

async def memory_analysis():
    """各ライブラリのメモリ使用パターン分析"""

    # メモリトレースを開始
    tracemalloc.start()

    # aiohttp でのメモリ使用量測定
    snapshot1 = tracemalloc.take_snapshot()

    async with aiohttp.ClientSession() as session:
        tasks = [
            session.get('https://httpbin.org/json')
            for _ in range(50)
        ]
        await asyncio.gather(*tasks)

    snapshot2 = tracemalloc.take_snapshot()

    # メモリ使用量の差分を計算
    top_stats = snapshot2.compare_to(snapshot1, 'lineno')

    total_memory = sum(stat.size for stat in top_stats)
    print(f"aiohttp メモリ使用量: {total_memory / 1024 / 1024:.2f} MB")

    # ガベージコレクションを実行
    gc.collect()
    tracemalloc.stop()

メモリ使用パターンの分析結果

各ライブラリのメモリ使用特性:

  1. requests: 単発リクエストでは最もメモリ効率が良い
  2. httpx: 並行処理時にメモリ使用量が線形増加
  3. aiohttp: 初期メモリ使用量は多いが、大量リクエスト時の効率が良い

まとめ

用途別推奨ライブラリ

実際のプロジェクトでの選択指針を以下にまとめます。

mermaidflowchart TD
    start["プロジェクト要件"]

    start --> req_type["リクエスト頻度"]

    req_type --> low["低頻度<br/>(1日100回未満)"]
    req_type --> medium["中頻度<br/>(1日数千回)"]
    req_type --> high["高頻度<br/>(1日数万回以上)"]

    low --> simple_choice["requests<br/>・シンプルな実装<br/>・保守性重視<br/>・学習コスト最小"]

    medium --> complexity["システム複雑性"]
    complexity --> medium_simple["シンプル"] --> httpx_choice["httpx<br/>・段階的な性能向上<br/>・将来の拡張性<br/>・requests からの移行"]
    complexity --> medium_complex["複雑"] --> mixed_choice["httpx + 一部 aiohttp<br/>・ハイブリッド構成<br/>・段階的最適化"]

    high --> performance["最高性能要求"]
    performance --> yes_perf["aiohttp<br/>・最高スループット<br/>・細かなチューニング<br/>・専門知識必要"]

具体的な選択基準

シナリオ推奨ライブラリ理由
プロトタイプ開発requests素早い実装が可能
小規模 Web アプリrequests保守性と安定性を重視
マイクロサービスhttpx同期・非同期の柔軟性
データ集約システムaiohttp高いスループットが必要
リアルタイム処理aiohttp低遅延が要求される
既存システム改修httpxrequests からの移行が容易

性能と開発効率のバランス

最適な選択をするためのフレームワークを提示します。

python# 選択支援ツールの例
class LibrarySelector:
    def __init__(self):
        self.criteria = {
            'performance_requirement': 0,  # 1-5
            'development_speed': 0,        # 1-5
            'team_experience': 0,          # 1-5
            'maintenance_priority': 0,     # 1-5
            'request_volume': 0           # requests/day
        }

    def recommend_library(self):
        """要件に基づいてライブラリを推奨"""
        score_requests = (
            self.criteria['development_speed'] * 2 +
            self.criteria['maintenance_priority'] * 2 +
            (5 - self.criteria['performance_requirement'])
        )

        score_httpx = (
            self.criteria['development_speed'] * 1.5 +
            self.criteria['performance_requirement'] * 1.5 +
            self.criteria['team_experience']
        )

        score_aiohttp = (
            self.criteria['performance_requirement'] * 3 +
            (self.criteria['team_experience'] - 2) +
            (5 if self.criteria['request_volume'] > 10000 else 0)
        )

        scores = {
            'requests': max(0, score_requests),
            'httpx': max(0, score_httpx),
            'aiohttp': max(0, score_aiohttp)
        }

        return max(scores, key=scores.get)

最終的な推奨事項

  1. 学習段階: まず requests から始める
  2. 性能課題: 問題が発生してから httpx に移行
  3. 高性能要求: 明確な要件がある場合のみ aiohttp を選択
  4. チーム開発: チーム全体のスキルレベルを考慮
  5. 段階的移行: 一度に全てを変更せず、部分的に移行

プロジェクトの成功には、技術的な性能だけでなく、チームの生産性とコードの保守性のバランスが重要です。まずは要件を明確にし、適切なライブラリを選択することで、長期的に持続可能なシステムを構築できるでしょう。

関連リンク