Requests、HTTPX、AIOHTTP の詳細な比較

Python で最も広く使われている HTTP クライアント、Requests、HTTPX、AIOHTTP を比較し、データ収集のニーズにどれが最適かを検証します。
5 min read
Requests vs. HTTPX vs. AIOHTTP blog image

Python には多種多様な HTTP クライアントがあります。HTTP (ハイパーテキストトランスファープロトコル) についてご存じでない方のために説明すると、HTTP はウェブ全体の基盤となるフレームワークです。

今回は、Python で最も広く使われている 3 つの HTTP クライアント、Requests、HTTPX、AIOHTTP を比較します。他に利用できるクライアントについて知りたい場合は、こちらをご覧ください。

簡単な概要

Requests は Python の標準 HTTP クライアントです。使いやすいように、ブロッキングと同期操作を利用しています。HTTPX は、速度と使いやすさを両立させるように設計された新しい非同期クライアントです。AIOHTTPは 10 年以上前から存在していて、Python で使用できる最初で最もサポートされている非同期 HTTP クライアントの 1 つです。

特徴 Requests HTTPX AIOHTTP
構造 同期/ブロッキング 非同期/ノンブロッキング 非同期/ノンブロッキング
セッション あり あり あり
並行処理 なし あり あり
HTTP/2 サポート なし あり あり
パフォーマンス
再試行 自動 自動 手動
タイムアウトサポート 1 リクエストあたり 完全サポート 完全サポート
プロキシサポート あり あり あり
使いやすさ 簡単 難しい 難しい
ユースケース 簡単なプロジェクトやプロトタイピング ハイパフォーマンス ハイパフォーマンス

Python Requests

Python Requests は非常に直感的で使いやすく、最先端のパフォーマンスが求められない Python での HTTP リクエストにお勧めです。広く使用されており、理解しやすく、世界で最も多くのドキュメントが利用できる Python HTTP クライアントです。

以下のコマンドでインストールできます。

インストール

pip install requests

Requests は標準の HTTP プロトコルと一部のセッション管理をサポートしています。セッションを使用すると、サーバーへの永続的な接続を確立できます。これにより、単一のリクエストを行うよりもはるかに迅速かつ効率的にデータを取得できます。基本的な (GET、POST、PUT、DELETE) リクエストを行うだけで、高いパフォーマンスが必要ない場合は、Requests ライブラリで HTTP のニーズをすべて満たすことができます。

ウェブ上のいたるところで、プロキシ統合ユーザーエージェントに関するものなど、あらゆる種類のガイドが提供されています。

基本的な使用例を以下に示します。

import requests

# make a simple request
response = requests.get("https://jsonplaceholder.typicode.com/posts")
print(response.status_code)

# use a session for multiple requests to the same server
with requests.Session() as client:
    for get_request in range(1000):
        response = client.get("https://jsonplaceholder.typicode.com/posts")
        print(response.status_code)

非同期操作を行う場合、Requests では不十分です。非同期がサポートされていると、多数のリクエストをまとめて行うことができます。そして、それらすべてに await を使用できます。同期リクエストでは一度に 1 つのリクエストしか実行できず、別のリクエストを行うには、サーバーから応答が返されるのを待つ必要があります。プログラムで大量の HTTP リクエストを行う必要がある場合、Requests を使用するとコードに固有の制限が生じます。

HTTPX

HTTPX は、この 3 つのライブラリの中で最も新しくモダンなクライアントで、設定なしでそのまま非同期操作が完全にサポートされています。そのうえ、ユーザーフレンドリーで直感的な構文が備わっています。Requests では不十分で、あまり習得に時間をかけずにパフォーマンスを向上させる必要がある場合は、HTTPX を使用します。

asyncio (非同期入出力) により、await キーワードを使って非同期応答を最大限に活用するコードを作成できます。これにより、応答を待っている間、コード内の他のすべてをブロックすることなく操作を続けることができます。このような非同期操作により、大量のリクエストを一斉に実行できます。一度に 1 つのリクエストを行うのではなく、5 件、50 件、さらには 100 件のリクエストを行うこともできます!

インストール

pip install httpx

HTTPX を使い始めるための例をいくつか示します。

import httpx
import asyncio

# synchronous response
response = httpx.get("https://jsonplaceholder.typicode.com/posts")
print(response.status_code)

# basic async session usage
async def main():
    async with httpx.AsyncClient() as client:
        for get_request in range(1000):
            response = await client.get("https://jsonplaceholder.typicode.com/posts")
            print(response.status_code)
asyncio.run(main())

HTTPX は新しいコードを書く際に最適な選択肢ですが、HTTPX 固有の制限があります。構文の性質上、既存の Requests コードベースを HTTPX に置き換えるのが難しい場合があります。非同期コードを書くには多少のボイラープレートが必要となり、多くの場合、何千ものリクエストを行わない限り、開発に余分な時間を費やす価値がありません。

AIOHTTP と比較すると、HTTPX はそれほど高速ではありません (AIOHTTP についてはこの後すぐ解説します)。ウェブサーバーや複雑なネットワークを構築する場合、HTTPX はエコシステムが未成熟であるため賢い選択肢とは言えません。HTTPX は、HTTP/2 などの最新機能を備えた新しいクライアントサイドのアプリケーションに最適です。

AIOHTTP

非同期プログラミングでは、AIOHTTP が長い間 Python で広く使用されてきました。サーバー、クライアントサイドのアプリケーション、分散ネットワークにかかわらず、AIOHTTP はこれらすべてのニーズを満たすことができます。ただし、これら 3 つ (Requests 、HTTPX、AIOHTTP) のうち、最も習得に時間がかかるのが AIOHTTP です。

この記事では、AIOHTTP について深く掘り下げて解説するのではなく、シンプルなクライアントサイドでの使用方法に焦点を当てます。HTTPX と同様に、AIOHTTP でも一斉にリクエストを実行できます。ただし、HTTPX とは異なり、AIOHTTP は同期リクエストをサポートしていません。リクエストを一度にたくさん実行しすぎると、サーバーによりアクセスが禁止される可能性があるため注意が必要です。

インストール

pip install aiohttp

以下の基本的な使い方をご覧ください。

import aiohttp
import asyncio

# make a single request
async def main():
    async with aiohttp.ClientSession() as client:
        response = await client.get("https://jsonplaceholder.typicode.com/posts")
        print(response)
        
asyncio.run(main())


# basic async session usage
async def main():
    with aiohttp.ClientSession() as client:
        for response in range(1000):
            response = await client.get("https://jsonplaceholder.typicode.com/posts")
            print(response)
            
asyncio.run(main())

AIOHTTP は非同期リクエストのみをサポートしています。上のコードからわかるように、単一リクエストの例では、最初の 2 つの例よりもはるかに多くのコードが必要になります。必要なリクエストの数に関係なく非同期セッションを設定する必要があるため、プロキシと AIOHTTP を組み合わせることをお勧めします。1 つのリクエストのためにここまでするのは、やり過ぎです。

AIOHTTP には多くのメリットがありますが、大量のインバウンドとアウトバウンドのリクエストを一度に処理する必要がない限り、Python Requests を完全に置き換えるものではありません。大量のリクエストを一度に処理する場合は、パフォーマンスが大幅に向上します。複雑なアプリケーションを開発していて、非常に高速な通信が必要な場合は、AIOHTTP を使用します。このライブラリは、サーバー、分散ネットワーク、非常に複雑なウェブスクレイピングの用途に最適です。

パフォーマンスの比較

次に、それぞれのライブラリを使用して小規模なプログラムを作成します。要件は簡単です。クライアントセッションを開いて 1,000 件の API リクエストを実行することです。

まず、サーバーとのセッションを作成する必要があります。次に、1,000 件のリクエストを実行します。配列は 2 つになります。1 つは有効な応答用、もう 1 つは無効な応答用です。実行が完了したら、有効なリクエストと無効なリクエストの全数を出力します。受信したリクエストが無効な場合は、そのステータスコードをコンソールに出力します。

非同期の例 (HTTPX と AIOHTTP) では、chunkify() 関数を使用します。これは配列をチャンクに分割するために使用します。その後、リクエストを一斉に実行します。たとえば、リクエストを 50 件のバッチで実行する場合は、chunkify() を使用してバッチを作成し、process_chunk() を使用して 50 件のリクエストをすべて一度に実行します。

以下の関数をご覧ください。

def chunkify(iterable, size):
    iterator = iter(iterable)
    while chunk := list(islice(iterator, size)):
        yield chunk
        
        
async def process_chunk(client, urls, retries=3):
    tasks = [fetch(client, url, retries) for url in urls]
    return await asyncio.gather(*tasks)

Requests

これが Requests を使う場合のコードです。後で使う 2 つの非同期の例と比べると、とてもシンプルです。セッションを開き、for ループを使用してリクエストを繰り返します。

import requests
import json
from datetime import datetime

start_time = datetime.now()
good_responses = []
bad_responses = []

with requests.Session() as client:

    for get_request in range(1000):
        response = client.get("https://jsonplaceholder.typicode.com/posts")
        status_code = response.status_code
        if status_code  == 200:
            good_responses.append(status_code)
        else:
            bad_responses.append(status_code)

end_time = datetime.now()

print("----------------Requests------------------")
print(f"Time elapsed: {end_time - start_time}")
print(f"Good Responses: {len(good_responses)}")
print(f"Bad Responses: {len(bad_responses)}")

for status_code in set(bad_responses):
    print(status_code)
Python リクエストの結果

Requests では、51 秒ちょっとでジョブが終了しました。これは、1 秒あたり約 20 件のリクエストが完了したことになります。セッションを使用しない場合、1 つのリクエストにつき 2 秒かかることが予想されます。これはなかなかのパフォーマンスです。

HTTPX

これが HTTPX のコードです。前述のとおり、chunkify()process_chunk() を使用しています。

import httpx
import asyncio
from datetime import datetime
from itertools import islice

def chunkify(iterable, size):
    iterator = iter(iterable)
    while chunk := list(islice(iterator, size)):
        yield chunk

async def fetch(client, url, retries=3):
    """Fetch a URL with retries."""
    for attempt in range(retries):
        try:
            response = await client.get(url)
            return response.status_code
        except httpx.RequestError as e:
            if attempt < retries - 1:
                await asyncio.sleep(1)
            else:
                return f"Error: {e}"

async def process_chunk(client, urls, retries=3):
    tasks = [fetch(client, url, retries) for url in urls]
    return await asyncio.gather(*tasks)

async def main():
    url = "https://jsonplaceholder.typicode.com/posts"
    total_requests = 1000
    chunk_size = 50

    good_responses = []
    bad_responses = []

    async with httpx.AsyncClient(timeout=10) as client:
        start_time = datetime.now()

        urls = [url] * total_requests
        for chunk in chunkify(urls, chunk_size):
            results = await process_chunk(client, chunk)
            for status in results:
                if isinstance(status, int) and status == 200:
                    good_responses.append(status)
                else:
                    bad_responses.append(status)

        end_time = datetime.now()

        print("----------------HTTPX------------------")
        print(f"Time elapsed: {end_time - start_time}")
        print(f"Good Responses: {len(good_responses)}")
        print(f"Bad Responses: {len(bad_responses)}")

        if bad_responses:
            print("Bad Status Codes or Errors:")
            for error in set(bad_responses):
                print(error)

asyncio.run(main())

HTTPX を使用した場合の出力は次のとおりです。Requests と比較すると、驚くべき結果です。合計時間は 7 秒ちょっとでした。これは 1 秒あたり 139.47 件のリクエストという計算になります。HTTPX は Requests の約 7 倍のパフォーマンスを発揮しました。

HTTPX の結果

AIOHTTP

次に、AIOHTTP を使用して同じ演習を行います。HTTPX の例で使用したのと同じ基本構造に従います。この場合の唯一の大きな違いは、HTTPX クライアントの代わりに AIOHTTP クライアントを使用する点です。

import aiohttp
import asyncio
from datetime import datetime
from itertools import islice

def chunkify(iterable, size):
    iterator = iter(iterable)
    while chunk := list(islice(iterator, size)):
        yield chunk

async def fetch(session, url, retries=3):
    for attempt in range(retries):
        try:
            async with session.get(url) as response:
                return response.status
        except aiohttp.ClientError as e:
            if attempt < retries - 1:
                await asyncio.sleep(1)
            else:
                return f"Error: {e}"

async def process_chunk(session, urls):
    tasks = [fetch(session, url) for url in urls]
    return await asyncio.gather(*tasks)

async def main():
    url = "https://jsonplaceholder.typicode.com/posts"
    total_requests = 1000
    chunk_size = 50

    good_responses = []
    bad_responses = []

    async with aiohttp.ClientSession() as session:
        start_time = datetime.now()

        urls = [url] * total_requests
        for chunk in chunkify(urls, chunk_size):
            results = await process_chunk(session, chunk)
            for status in results:
                if isinstance(status, int) and status == 200:
                    good_responses.append(status)
                else:
                    bad_responses.append(status)

        end_time = datetime.now()

        print("----------------AIOHTTP------------------")
        print(f"Time elapsed: {end_time - start_time}")
        print(f"Good Responses: {len(good_responses)}")
        print(f"Bad Responses: {len(bad_responses)}")

        if bad_responses:
            print("Bad Status Codes or Errors:")
            for error in set(bad_responses):
                print(error)

asyncio.run(main())

AIOHTTP は 4 秒ちょっとという非常に短い時間で完了しました。この HTTP クライアントは、1 秒あたり 241 件を超えるリクエストを実行しました。AIOHTTP は Requests の約 10 倍、そして HTTPX よりもほぼ 50% 高速です。Python において、AIOHTTP のパフォーマンスは群を抜いています。

AIOHTTP の結果

Bright Data の製品がどのように役立つか

Bright Data は、HTTP クライアントベースのワークフロー、特にウェブスクレイピング、API リクエスト、ハイパフォーマンスインテグレーションなどのデータ量の多い操作を強化できる、さまざまなソリューションを提供しています。各製品の用途は次のとおりです:

  • 住宅用プロキシ – Bright Data の住宅用プロキシは、AIOHTTP や HTTPX などの Python HTTP クライアントを使用してウェブサイトをスクレイピングする際に、ブロックやアクセス禁止を回避するのに役立ちます。これらのプロキシは実際のユーザーの行動を模倣するため、地理的に制限されたコンテンツや動的コンテンツに簡単にアクセスできます。
  • Web Scraper API – 独自のスクレイピングインフラストラクチャを構築して維持する代わりに、Bright Data の Web Scraper API を使用すると、何百もの人気のウェブサイトへの事前に構成済みのアクセスが利用できます。これにより、リクエストや再試行、アクセス禁止への対応ではなく、データの分析に集中できます。API 呼び出しを使用するだけで、構造化データを直接取得できます。
  • 既製のデータセット – 特定のデータポイントが必要だがスクレイピングは完全に避けたいという場合、Bright Data はお客様のニーズに合わせた既製のデータセットを提供しています。これらのデータセットには、製品の詳細、価格、レビューが含まれており、e コマースの分析や市場調査にすぐに使用できます。
  • Web Unlocker – Web Unlocker は、CAPTCHA、ボット対策メカニズム、複雑なリクエストパターンなどの課題に自動的に対処します。HTTPX や AIOHTTP などのライブラリと組み合わせると、アクセスが難しいウェブサイトのスクレイピングプロセスを効率化できます。
  • SERP API – 検索エンジンからデータを抽出する場合、Bright Data の SERP API を使用するとプロセスが簡素化され、インフラストラクチャやブロックを気にすることなく、検索結果、広告、ランキングへのリアルタイムでの信頼性の高いアクセスが可能になります。

Bright Data のツールを Python HTTP クライアントと統合することで、ウェブスクレイピングやデータ収集の一般的な課題を克服すると同時に、データ収集を簡素化する強力で高性能なシステムを構築できます。

まとめ

HTTP クライアントの世界では、単純にその使いやすさから Requests が標準となっています。Requests と比較すると、HTTPX は馬車から現代の自動車に進化したようなものです。これにより、高性能と使いやすさのバランスが取れます。それに比べ、AIOHTTP は宇宙船のようなものです。どうしても必要な場合以外はお勧めしませんが、現在利用できる中で断トツの速度を誇る HTTP クライアントです。

今すぐ Bright Data に登録して、強力なデータソリューションを活用し、ビジネスに必要な競争力をつけましょう。すべての製品で無料トライアルが利用できます!

クレジットカードは必要ありません