web-dev-qa-db-fra.com

aiohttp: définit le nombre maximum de requêtes par seconde

Comment définir le nombre maximal de demandes par seconde (les limiter) côté client à l'aide de aiohttp?

15
pngnviko

J'ai trouvé une solution possible ici: http://compiletoi.net/fast-scraping-in-python-with-asyncio.html

Faire 3 demandes en même temps, c'est cool, faire 5000, cependant, n'est pas si gentil. Si vous essayez de faire trop de demandes en même temps, les connexions pourraient commencer à se fermer, voire vous être bannies du site Web.

Pour éviter cela, vous pouvez utiliser un sémaphore. C'est un outil de synchronisation qui peut être utilisé pour limiter le nombre de routines faisant quelque chose à un moment donné. Nous allons simplement créer le sémaphore avant de créer la boucle, en passant comme argument le nombre de requêtes simultanées que nous voulons autoriser:

sem = asyncio.Semaphore(5)

Ensuite, nous remplaçons simplement:

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

par la même chose, mais protégé par un sémaphore:

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

Cela garantira que 5 demandes au maximum peuvent être effectuées en même temps.

20
pngnviko

Depuis la version 2.0, lorsque vous utilisez un ClientSession , aiohttp limite automatiquement le nombre de connexions simultanées à 100.

Vous pouvez modifier la limite en créant votre propre TCPConnector et en le passant dans la ClientSession. Par exemple, pour créer un client limité à 50 demandes simultanées:

import aiohttp

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

Si cela convient mieux à votre cas d'utilisation, il existe également un paramètre limit_per_Host (désactivé par défaut) que vous pouvez passer pour limiter le nombre de connexions simultanées au même "noeud final". Selon les documents:

limit_per_Host (int) - limite pour les connexions simultanées au même noeud final. Les points d'extrémité sont les mêmes s'ils ont le même (Host, port, is_ssl) triple.

Exemple d'utilisation:

import aiohttp

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

Vous pouvez définir un délai par demande ou regrouper les URL par lots et limiter les lots à la fréquence souhaitée.

1. Délai par demande

Forcer le script à attendre entre les demandes en utilisant 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

Ceci peut être exécuté avec par exemple results = asyncio.run(app()).

2. Accélérateur de lot

En utilisant make_request ci-dessus, vous pouvez demander et limiter des lots d'URL comme ceci:

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

De nouveau, ceci peut être exécuté avec asyncio.run(app()).

0
AlexG