web-dev-qa-db-ja.com

aiohttp:1秒あたりのリクエストの最大数を設定する

Aiohttpを使用してクライアント側で1秒あたりのリクエストの最大数を設定(制限)するにはどうすればよいですか?

20
pngnviko

私はここで1つの可能な解決策を見つけました: http://compiletoi.net/fast-scraping-in-python-with-asyncio.html

3つのリクエストを同時に実行するのはかっこいいですが、5000を実行するのはそれほどいいことではありません。同時に多くのリクエストを実行しようとすると、接続が切断され始めたり、Webサイトから禁止されたりする場合があります。

これを回避するには、セマフォを使用できます。これは、ある時点で何かを行うコルーチンの数を制限するために使用できる同期ツールです。ループを作成する前にセマフォを作成し、許可する同時リクエストの数を引数として渡します。

sem = asyncio.Semaphore(5)

次に、単に置き換えます:

page = yield from get(url, compress=True)

同じことですが、セマフォによって保護されています:

with (yield from sem):
    page = yield from get(url, compress=True)

これにより、最大5つのリクエストを同時に実行できます。

25
pngnviko

V2.0以降、 ClientSession を使用すると、aiohttpは同時接続数を自動的に100に制限します。

独自の TCPConnector を作成してClientSessionに渡すことにより、制限を変更できます。たとえば、50の同時リクエストに制限されたクライアントを作成するには:

import aiohttp

connector = aiohttp.TCPConnector(limit=50)
client = aiohttp.ClientSession(connector=connector)

ユースケースにより適している場合は、limit_per_Hostパラメーター(デフォルトではオフ)を渡して、同時接続数を同じ「エンドポイント」に制限できます。ドキュメントごと:

limit_per_Hostint)–同じエンドポイントへの同時接続の制限。 (Host, port, is_ssl)トリプルが等しい場合、エンドポイントは同じです。

使用例:

import aiohttp

connector = aiohttp.TCPConnector(limit_per_Host=50)
client = aiohttp.ClientSession(connector=connector)
25
Mark Amery

リクエストごとに遅延を設定したり、URLをバッチにグループ化したり、バッチを調整して目的の頻度に合わせたりできます。

1.リクエストごとの遅延

_asyncio.sleep_を使用して、スクリプトをリクエスト間で待機させます

_import asyncio
import aiohttp

delay_per_request = 0.5
urls = [
   # put some URLs here...
]

async def app():
    tasks = []
    for url in urls:
        tasks.append(asyncio.ensure_future(make_request(url)))
        await asyncio.sleep(delay_per_request)

    results = await asyncio.gather(*tasks)
    return results

async def make_request(url):
    print('$$$ making request')
    async with aiohttp.ClientSession() as sess:
        async with sess.get(url) as resp:
            status = resp.status
            text = await resp.text()
            print('### got page data')
            return url, status, text
_

これは、例えばresults = asyncio.run(app())

2.バッチスロットル

上記の_make_request_を使用して、次のようにURLのバッチを要求および抑制できます。

_import asyncio
import aiohttp
import time

max_requests_per_second = 0.5
urls = [[
   # put a few URLs here...
],[
   # put a few more URLs here...
]]

async def app():
    results = []
    for i, batch in enumerate(urls):
        t_0 = time.time()
        print(f'batch {i}')
        tasks = [asyncio.ensure_future(make_request(url)) for url in batch]
        for t in tasks:
            d = await t
            results.append(d)
        t_1 = time.time()

        # Throttle requests
        batch_time = (t_1 - t_0)
        batch_size = len(batch)
        wait_time = (batch_size / max_requests_per_second) - batch_time
        if wait_time > 0:
            print(f'Too fast! Waiting {wait_time} seconds')
            time.sleep(wait_time)

    return results
_

繰り返しますが、これはasyncio.run(app())で実行できます。

1
AlexG