Aiohttp: установить максимальное количество запросов в секунду

Как установить максимальное количество запросов в секунду (ограничить их) на стороне клиента с помощью aiohttp?

Ответ 1

Я нашел одно возможное решение здесь: http://compiletoi.net/fast-scraping-in-python-with-asyncio.html

Выполнение 3 запросов в то же время классно, но 5000, однако, не так приятно. Если вы попытаетесь сделать слишком много запросов одновременно, соединения могут начать закрываться, или вы даже можете быть заблокированы с веб-сайта.

Чтобы этого избежать, вы можете использовать семафор. Это инструмент синхронизации, который можно использовать для ограничения количества сопрограмм, которые что-то делают в какой-то момент. Мы просто создадим семафор перед созданием цикла, передавая в качестве аргумента количество одновременных запросов, которые хотим разрешить:

sem = asyncio.Semaphore(5)

Тогда мы просто заменим:

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

тем же, но защищенным семафором:

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

Это обеспечит одновременное выполнение не более 5 запросов.

Ответ 2

Так как 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_host (int) - предел для одновременных подключений к одной и той же конечной точке. Конечные точки одинаковы, если они имеют равную (host, port, is_ssl) тройку.

Пример использования:

import aiohttp

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

Ответ 3

Вы можете установить задержку для каждого запроса или сгруппировать URL-адреса в пакеты и регулировать пакеты в соответствии с желаемой частотой.

1. Задержка на запрос

asyncio.sleep скрипт ждать между запросами используя 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()).