Comment puis-je utiliser les requêtes asyncio?

127

Je veux faire des tâches de requête http parallèles dans asyncio, mais je trouve que python-requestscela bloquerait la boucle d'événements de asyncio. J'ai trouvé aiohttp mais il n'a pas pu fournir le service de requête http en utilisant un proxy http.

Je veux donc savoir s'il existe un moyen de faire des requêtes http asynchrones à l'aide de asyncio.

prospectus
la source
1
Si vous envoyez simplement des demandes, vous pouvez utiliser subprocesspour mettre en parallèle votre code.
WeaselFox
Cette méthode ne semble pas élégante ……
flyer
Il existe maintenant un port asyncio de requêtes. github.com/rdbhost/yieldfromRequests
Rdbhost

Réponses:

181

Pour utiliser des requêtes (ou toute autre bibliothèque de blocage) avec asyncio, vous pouvez utiliser BaseEventLoop.run_in_executor pour exécuter une fonction dans un autre thread et en tirer le résultat pour obtenir le résultat. Par exemple:

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())

Cela obtiendra les deux réponses en parallèle.

Avec python 3.5, vous pouvez utiliser la nouvelle syntaxe 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())

Voir PEP0492 pour plus.

Christian
la source
5
Pouvez-vous expliquer comment cela fonctionne exactement? Je ne comprends pas comment cela ne bloque pas.
Scott Coates
32
@christian mais si cela fonctionne simultanément dans un autre thread, n'est-ce pas vaincre le point d'asyncio?
Scott Coates
21
@scoarescoare C'est là qu'intervient la partie «si vous le faites correctement» - la méthode que vous exécutez dans l'exécuteur doit être autonome ((principalement) comme requests.get dans l'exemple ci-dessus). De cette façon, vous n'avez pas à gérer la mémoire partagée, le verrouillage, etc., et les parties complexes de votre programme sont toujours à un seul thread grâce à asyncio.
christian
5
@scoarescoare Le cas d'utilisation principal est l'intégration avec des bibliothèques IO qui ne prennent pas en charge asyncio. Par exemple, je travaille avec une interface SOAP vraiment ancienne, et j'utilise la bibliothèque suds-jurko comme la solution «la moins mauvaise». J'essaie de l'intégrer à un serveur asyncio, donc j'utilise run_in_executor pour effectuer les appels de blocage de mousse d'une manière qui semble asynchrone.
Lucretiel
10
Vraiment cool que cela fonctionne et donc si facile pour les éléments hérités, mais il convient de souligner que cela utilise un threadpool du système d'exploitation et ne se transforme donc pas en une véritable bibliothèque orientée asyncio comme le fait aiohttp
jsalter
78

aiohttp peut déjà être utilisé avec le 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())
Mindmaster
la source
Que fait le connecteur ici?
Markus Meskanen
Il fournit une connexion via un serveur proxy
mindmaster
16
C'est une bien meilleure solution que d'utiliser les requêtes dans un thread séparé. Comme il est vraiment asynchrone, il a une surcharge et une utilisation des memes plus faibles.
Thom
14
pour python> = 3,5 remplacer @ asyncio.coroutine par "async" et "yield from" par "await"
James
40

Les réponses ci-dessus utilisent toujours les anciennes coroutines de style Python 3.4. Voici ce que vous écririez si vous aviez Python 3.5+.

aiohttp prend en charge le proxy http maintenant

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())
ospider
la source
1
pourriez-vous élaborer avec plus d'URL? Cela n'a pas de sens de n'avoir qu'une seule URL lorsque la question concerne une requête http parallèle.
anonyme
Légende. Je vous remercie! Fonctionne très bien
Adam
@ospider Comment ce code peut-il être modifié pour fournir par exemple 10 000 URL en utilisant 100 requêtes en parallèle? L'idée est d'utiliser les 100 emplacements simultanément, de ne pas attendre que 100 soient livrés pour commencer les 100 prochains.
Antoan Milkov
@AntoanMilkov C'est une question différente à laquelle on ne peut pas répondre dans la zone de commentaires.
ospider
@ospider Vous avez raison, voici la question: stackoverflow.com/questions/56523043/…
Antoan Milkov
11

Requests ne prend pas en charge actuellement asyncioet il n'est pas prévu de fournir un tel support. Il est probable que vous puissiez implémenter un «adaptateur de transport» personnalisé (comme indiqué ici ) qui sait comment l'utiliser asyncio.

Si je me retrouve avec un peu de temps, c'est quelque chose que je pourrais en fait examiner, mais je ne peux rien promettre.

Lukasa
la source
Le lien mène à un 404.
CodeBiker
8

Il y a un bon cas de boucles async / d'attente et de threading dans un article de Pimin Konstantin Kefaloukos Requêtes HTTP parallèles faciles avec Python et asyncio :

Pour minimiser le temps d'achèvement total, nous pourrions augmenter la taille du pool de threads pour correspondre au nombre de requêtes que nous devons effectuer. Heureusement, c'est facile à faire comme nous le verrons ensuite. La liste de code ci-dessous est un exemple de la façon de faire vingt requêtes HTTP asynchrones avec un pool de threads de vingt threads de travail:

# 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())
Ilya Rusin
la source
2
Le problème avec ceci est que si je dois exécuter 10000 demandes avec des morceaux de 20 exécuteurs, je dois attendre que les 20 exécuteurs se terminent pour commencer avec les 20 suivants, non? Je ne peux pas faire pour for i in range(10000)parce qu'une demande peut échouer ou expirer, non?
Sanandrea
1
Pouvez-vous expliquer pourquoi vous avez besoin d'asyncio alors que vous pouvez faire la même chose en utilisant simplement ThreadPoolExecutor?
Asaf Pinhassi
@lya Rusin En fonction de quoi, définissons-nous le nombre de max_workers? Cela a-t-il à voir avec le nombre de processeurs et de threads?
alt-f4