aiohttp: ограничение скорости параллельных запросов


API часто имеют ограничения скорости, которые пользователи должны соблюдать. В качестве примера возьмем 50 запросов в секунду. Последовательные запросы занимают 0,5-1 секунду и поэтому слишком медленны, чтобы приблизиться к этому пределу. Параллельные запросы с aiohttp, однако, превышают ограничение скорости.

Чтобы опросить API настолько быстро,насколько это возможно, нужно ограничить скорость параллельных вызовов.

Примеры, которые я нашел до сих пор украшают session.get, примерно так:

session.get = rate_limited(max_calls_per_second)(session.get)

Это хорошо работает для последовательных вызовов. Пытаюсь реализация этого в параллельных вызовах не работает должным образом.

Вот пример кода:

async with aiohttp.ClientSession() as session:
    session.get = rate_limited(max_calls_per_second)(session.get)
    tasks = (asyncio.ensure_future(download_coroutine(  
          timeout, session, url)) for url in urls)
    process_responses_function(await asyncio.gather(*tasks))
Проблема с этим заключается в том, что он будет ограничивать скорость очереди задач. Выполнение с gather все равно произойдет более или менее одновременно. Худший из обоих миров ; -).

Да, я нашел аналогичный вопрос прямо здесь aiohttp: установите максимальное количество запросов в секунду , но ни один из ответов не отвечает на фактический вопрос об ограничении скорости запросы. Такжесообщение в блоге от Quentin Pradet работает только на ограничение скорости очереди.

Подводить итоги: как можно ограничить количество запросов в секунду для параллельного aiohttp просит?

1 2

1 ответ:

Если я вас хорошо понимаю, вы хотите ограничить количество одновременных запросов?

Внутри asyncio есть объект с именем Semaphore, он работает как асинхронный RLock.

semaphore = asyncio.Semaphore(50)
#...
async def limit_wrap(url):
    async with semaphore:
        # do what you want
#...
results = asyncio.gather([limit_wrap(url) for url in urls])

Обновлено

Предположим, я делаю 50 одновременных запросов, и все они заканчиваются за 2 секунды. Таким образом, это не касается ограничения(только 25 запросов в секунду). Это означает, что я должен сделать 100 одновременных запросов, и все они заканчиваются через 2 секунды(50 запросов в секунду). Но перед тобой на самом деле сделайте эти запросы, как вы можете определить, как долго они закончатся?

Или, если вы не возражаете, законченные запросы в секунду , но запросы, сделанные в секунду. Вы можете:

async def loop_wrap(urls):
    for url in urls:
        asyncio.ensure_future(download(url))
        await asyncio.sleep(1/50)

asyncio.ensure_future(loop_wrap(urls))
loop.run_forever()

Приведенный выше код будет создавать экземпляр Future каждые 1/50 секунды.