0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】aiohttpを使ってみた

Last updated at Posted at 2025-05-31

はじめに

asyncio ライブラリはリクエストを非同期処理で行うため、リクエスト中にブロッキングが発生しません。一方で requests ライブラリはリクエストを同期処理で行うため、リクエストが完了するまで次の処理に進むことができません。
この記事では以下について書きます。

  • aiohttp を使ってみる
  • requests と aiohttp の動作の違いを確認する
  • 考察

バージョン:

Python: 3.10.12
aiohttp: 3.12.4
requests: 2.32.3

aiohttp を使ってみる

まずライブラリをインストールします。

pip install aiohttp

PyPI:

公式ドキュメント:

インストール後、以下のファイルを作成し、実行します。

import asyncio

import aiohttp

async def req_aiohttp(name):
    print(f"{name} のリクエストが開始しました。")

    async with aiohttp.ClientSession() as session:
        async with session.get("https://httpbin.org") as response:
            print("Status:", response.status)
            print("Content-type:", response.headers["content-type"])

            html = await response.text()
            print("Body:", html[:15], "...")

    print(f"{name} のリクエストが完了しました。")


async def main():
    await req_aiohttp("Alice")

if __name__ == "__main__":
    asyncio.run(main())

実行結果:

------ Alice のリクエストが開始しました。------
Status: 200
Content-type: text/html; charset=utf-8
Body: <!DOCTYPE html> ...
------ Alice のリクエストが完了しました。------

requsts と aiohttp の動作の違いを確認する

並列で2人のユーザーがリクエストを実行してみます。3 秒かかるリクエストを実行します。

以下のサイトはリクエスト送信からレスポンス受信まで 3 秒かかります。

aiohttp

検証コード:

import asyncio
from datetime import datetime

import aiohttp

async def req_aiohttp(name):
    print(f"\n------ {datetime.now().strftime('%H:%M:%S.%f')} - {name} のリクエストが開始しました。 ------\n")

    async with aiohttp.ClientSession() as session:
        async with session.get("https://httpbin.org/delay/3") as response:
            print(f"{name} Status: {response.status}")

    print(f"\n------ {datetime.now().strftime('%H:%M:%S.%f')} - {name} のリクエストが開始しました。 ------\n")

async def main():
    users = ["Alice", "Bob"]

    tasks = [asyncio.create_task(req_aiohttp(user)) for user in users]

    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

実行結果:

------ 00:28:44.004863 - Alice のリクエストが開始しました。 ------


------ 00:28:44.005018 - Bob のリクエストが開始しました。 ------

Bob Status: 200
Alice Status: 200

------ 00:28:48.030639 - Alice のリクエストが完了しました。 ------


------ 00:28:48.031345 - Bob のリクエストが完了しました。 ------

Alice、Bob のリクエストは同時に開始・完了しています。

requests

検証コード:

import asyncio
from datetime import datetime

import requests

async def req_requests(name):
    print(f"\n------ {datetime.now().strftime('%H:%M:%S.%f')} - {name} のリクエストが開始しました。 ------\n")

    with requests.Session() as session:
        with session.get("https://httpbin.org/delay/3") as response:
            print(f"{name} Status: {response.status_code}")

    print(f"\n------ {datetime.now().strftime('%H:%M:%S.%f')} - {name} のリクエストが完了しました。 ------\n")

async def main():
    users = ["Alice", "Bob"]

    tasks = [asyncio.create_task(req_requests(user)) for user in users]

    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

実行結果:

------ 01:14:16.735965 - Alice のリクエストが開始しました。 ------

Alice Status: 200

------ 01:14:20.595169 - Alice のリクエストが完了しました。 ------


------ 01:14:20.596051 - Bob のリクエストが開始しました。 ------

Bob Status: 200

------ 01:14:24.445815 - Bob のリクエストが完了しました。 ------

Alice のリクエスト完了後、Bob のリクエストが開始しています。

検証結果まとめ

以下のことが確認できました。

同期処理(requests):
[ Alice 開始 ] ----3秒---- [ Alice 終了 ] -> [ Bob 開始 ] ----3秒---- [ Bob 終了 ]

非同期処理(aiohttp):
[ Alice 開始 ] [ Bob 開始 ]
----3秒----
[ Alice 終了 ] [ Bob 終了 ]

考察

requests ではなぜブロッキングが発生するか

requests のコードでは、async with を使っていません。async with で実行すると以下のエラーが発生します。requests は同期処理のため使いたくても使えません。

------ 00:43:52.276705 - Alice のリクエストが開始しました。 ------


------ 00:43:52.276879 - Bob のリクエストが開始しました。 ------

Traceback (most recent call last):
 File "/home/oizumi/python-learning/asyncio/sample_requests.py", line 27, in <module>
   asyncio.run(main())
 File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
   return loop.run_until_complete(main)
 File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
   return future.result()
 File "/home/oizumi/python-learning/asyncio/sample_requests.py", line 24, in main
   await asyncio.gather(*tasks)
 File "/home/oizumi/python-learning/asyncio/sample_requests.py", line 9, in req_requests
   async with requests.Session() as session:
AttributeError: __aenter__

そのため、requests のコードでは以下と同じ状態になっています。

import asyncio
import time

async def wait_function(name):
   print(f"------ {name} は 3 秒待機します。 ------")
   # ここでブロッキング
   time.sleep(3)
   print(f"------ {name} の待機が終わりました。 ------")

async def main():
   users = ["Alice", "Bob"]

   tasks = [asyncio.create_task(wait_function(user)) for user in users]

   await asyncio.gather(*tasks)

if __name__ == '__main__':
   asyncio.run(main())

実行結果:

------ Alice は 3 秒待機します。 ------
------ Alice の待機が終わりました。 ------
------ Bob は 3 秒待機します。 ------
------ Bob の待機が終わりました。 ------

並列処理を行うためには以下のように await を使う必要があります。

import asyncio
import time

async def wait_function(name):
   print(f"------ {name} は 3 秒待機します。 ------")
   # ブロッキングは発生しない
   await asyncio.sleep(3)
   print(f"------ {name} の待機が終わりました。 ------")

async def main():
   users = ["Alice", "Bob"]

   tasks = [asyncio.create_task(wait_function(user)) for user in users]

   await asyncio.gather(*tasks)

if __name__ == '__main__':
   asyncio.run(main())

実行結果:

------ Alice は 3 秒待機します。 ------
------ Bob は 3 秒待機します。 ------
------ Alice の待機が終わりました。 ------
------ Bob の待機が終わりました。 ------

非同期関数内で同期リクエストを並列処理する

イベントループを使用します。以下の記事を参考にしました。

公式ドキュメント:

検証コード:

import asyncio
from datetime import datetime

import requests

def sync_request(name):
    print(f"\n------ {datetime.now().strftime('%H:%M:%S.%f')} - {name} のリクエストが開始しました。 ------\n")

    with requests.Session() as session:
        with session.get("https://httpbin.org/delay/3") as response:
            print("Status:", response.status_code)

    print(f"\n------ {datetime.now().strftime('%H:%M:%S.%f')} - {name} のリクエストが完了しました。 ------\n")

async def req_requests(name):
    loop = asyncio.get_running_loop()
    await loop.run_in_executor(None, sync_request, name)

async def main():
    users = ["Alice", "Bob"]

    tasks = [req_requests(user) for user in users]

    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

実行結果:

------ 01:15:44.411194 - Alice のリクエストが開始しました。 ------


------ 01:15:44.411489 - Bob のリクエストが開始しました。 ------

Alice Status: 200

------ 01:15:48.260434 - Alice のリクエストが完了しました。 ------

Bob Status: 200

------ 01:15:48.261894 - Bob のリクエストが完了しました。 ------

疑問点

aiohttp では以下のように完了メッセージが並んで表示されていました。

Bob Status: 200
Alice Status: 200

------ 00:28:48.030639 - Alice のリクエストが完了しました。 ------


------ 00:28:48.031345 - Bob のリクエストが完了しました。 ------

しかし run_in_executor の場合、以下のように完了メッセージが並んでいません。

Alice Status: 200

------ 01:15:48.260434 - Alice のリクエストが完了しました。 ------

Bob Status: 200

------ 01:15:48.261894 - Bob のリクエストが完了しました。 ------

この理由がわかっていません。

まとめ

  • aiohttp はリクエストを非同期処理で行うため、リクエスト中にブロッキングが発生しない
  • requests はリクエストを同期処理で行うため、並列処理を行うにはイベントループが必要

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?