Come potrei usare le richieste in asyncio?


127

Voglio svolgere attività di richiesta http parallele asyncio, ma trovo che python-requestsbloccherebbe il ciclo degli eventi di asyncio. Ho trovato aiohttp ma non è stato in grado di fornire il servizio di richiesta http utilizzando un proxy http.

Quindi voglio sapere se c'è un modo per fare richieste http asincrone con l'aiuto di asyncio.


1
Se stai solo inviando richieste, puoi usare subprocessper parallelizzare il tuo codice.
WeaselFox

Questo metodo non sembra elegante ...
flyer

Ora c'è un porto di richieste asyncio. github.com/rdbhost/yieldfromRequests
Rdbhost

Risposte:


181

Per utilizzare le richieste (o qualsiasi altra libreria di blocco) con asyncio, è possibile utilizzare BaseEventLoop.run_in_executor per eseguire una funzione in un altro thread e cedere da essa per ottenere il risultato. Per esempio:

import asyncio
import requests

@asyncio.coroutine
def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = yield from future1
    response2 = yield from future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Ciò otterrà entrambe le risposte in parallelo.

Con Python 3.5 è possibile utilizzare la sintassi new await/ async:

import asyncio
import requests

async def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = await future1
    response2 = await future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Vedi PEP0492 per di più.


5
Puoi spiegare come funziona esattamente? Non capisco come questo non blocchi.
Scott Coates,

32
@Christian, ma se sta correndo contemporaneamente in un altro thread, non è quello di sconfiggere il punto di asyncio?
Scott Coates,

21
@scoarescoare È qui che entra in gioco la parte 'se lo fai nel modo giusto' - il metodo che esegui nell'esecutore dovrebbe essere autonomo ((principalmente) come request.get nell'esempio sopra). In questo modo non dovrai occuparti di memoria condivisa, blocco, ecc. E le parti complesse del tuo programma sono ancora a thread singolo grazie ad asyncio.
christian

5
@scoarescoare Il caso d'uso principale è l'integrazione con le librerie IO che non hanno il supporto per asyncio. Ad esempio, sto facendo un po 'di lavoro con un'interfaccia SOAP veramente antica e sto usando la libreria suds-jurko come la soluzione "meno male". Sto cercando di integrarlo con un server asyncio, quindi sto usando run_in_executor per effettuare le chiamate di blocco del sud in un modo che sembra asincrono.
Lucretiel,

10
Davvero bello che funzioni e che sia così facile per cose legacy, ma va sottolineato che utilizza un threadpool del sistema operativo e quindi non si scala come una vera lib orientata all'asyncio come fa
aiohttp

78

aiohttp può essere già utilizzato con proxy HTTP:

import asyncio
import aiohttp


@asyncio.coroutine
def do_request():
    proxy_url = 'http://localhost:8118'  # your proxy address
    response = yield from aiohttp.request(
        'GET', 'http://google.com',
        proxy=proxy_url,
    )
    return response

loop = asyncio.get_event_loop()
loop.run_until_complete(do_request())

Cosa fa qui il connettore?
Markus Meskanen,

Fornisce una connessione tramite server proxy
mindmaster

16
Questa è una soluzione molto migliore quindi utilizzare le richieste in un thread separato. Dal momento che è veramente asincrono ha un sovraccarico più basso e un uso di mem inferiore.
Thom,

14
per python> = 3.5 sostituisci @ asyncio.coroutine con "asincrono" e "cedere da" con "attendi"
James

40

Le risposte sopra stanno ancora usando le vecchie coroutine in stile Python 3.4. Ecco cosa scriveresti se avessi Python 3.5+.

aiohttp supporta ora il proxy http

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
            'http://python.org',
            'https://google.com',
            'http://yifei.me'
        ]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))
        htmls = await asyncio.gather(*tasks)
        for html in htmls:
            print(html[:100])

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

1
potresti elaborare con più URL? Non ha senso avere un solo URL quando la domanda riguarda una richiesta http parallela.
anonimo

Leggenda. Grazie! Funziona alla grande
Adam

@ospider Come può essere modificato questo codice per fornire URL 10k dire usando 100 richieste in parallelo? L'idea è di usare tutti e 100 gli slot contemporaneamente, non aspettare che vengano consegnati 100 per iniziare i prossimi 100.
Antoan Milkov

@AntoanMilkov Questa è una domanda diversa a cui non è possibile rispondere nell'area dei commenti.
ospitante,

@ospider Hai ragione, ecco la domanda: stackoverflow.com/questions/56523043/…
Antoan Milkov,

11

Le richieste al momento non supportano asyncioe non ci sono piani per fornire tale supporto. È probabile che tu possa implementare un "adattatore di trasporto" personalizzato (come discusso qui ) che sa come usare asyncio.

Se mi ritrovo con un po 'di tempo è qualcosa che potrei davvero esaminare, ma non posso promettere nulla.


Il collegamento porta a un 404.
CodeBiker

8

C'è un buon caso di loop asincroni / wait e threading in un articolo di Pimin Konstantin Kefaloukos Richieste HTTP parallele facili con Python e asyncio :

Per ridurre al minimo il tempo totale di completamento, è possibile aumentare le dimensioni del pool di thread in modo che corrispondano al numero di richieste da effettuare. Fortunatamente, questo è facile da fare come vedremo dopo. Il seguente elenco di codice è un esempio di come effettuare venti richieste HTTP asincrone con un pool di thread di venti thread di lavoro:

# Example 3: asynchronous requests with larger thread pool
import asyncio
import concurrent.futures
import requests

async def main():

    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

        loop = asyncio.get_event_loop()
        futures = [
            loop.run_in_executor(
                executor, 
                requests.get, 
                'http://example.org/'
            )
            for i in range(20)
        ]
        for response in await asyncio.gather(*futures):
            pass


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

2
problema con questo è che se devo eseguire 10000 richieste con blocchi di 20 esecutori, devo aspettare che finiscano tutti i 20 esecutori per iniziare con i prossimi 20, giusto? Non posso farlo for i in range(10000)perché una richiesta potrebbe non riuscire o timeout, giusto?
Sanandrea,

1
Puoi spiegare perché hai bisogno di asyncio quando puoi fare lo stesso solo usando ThreadPoolExecutor?
Asaf Pinhassi,

@lya Rusin In base a cosa, impostiamo il numero di max_worker? Ha a che fare con il numero di CPU e thread?
alt-f4
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.