Requêtes asynchrones avec requêtes Python

143

J'ai essayé l'exemple fourni dans la documentation de la bibliothèque de requêtes pour python.

Avec async.map(rs), j'obtiens les codes de réponse, mais je souhaite obtenir le contenu de chaque page demandée. Cela, par exemple, ne fonctionne pas:

out = async.map(rs)
print out[0].content
trbck
la source
Peut-être que les réponses que vous obtenez ont un corps vide?
Mariusz Jamro
Travaille pour moi. Veuillez publier l'erreur complète que vous obtenez.
Chewie
il n'y a pas d'erreur. il s'exécute pour toujours par les URL de test fournies.
trbck
il apparaît évidemment lorsque j'utilise des URL sur https. http fonctionne très bien
trbck
On dirait qu'il requests-threadsexiste maintenant.
OrangeDog

Réponses:

154

Remarque

La réponse ci-dessous ne s'applique pas aux demandes v0.13.0 +. La fonctionnalité asynchrone a été déplacée vers les grequests après la rédaction de cette question. Cependant, vous pouvez simplement remplacer requestspar grequestsci-dessous et cela devrait fonctionner.

J'ai laissé cette réponse telle quelle pour refléter la question d'origine qui portait sur l'utilisation des requêtes <v0.13.0.


Pour effectuer plusieurs tâches de async.map manière asynchrone, vous devez:

  1. Définissez une fonction pour ce que vous voulez faire avec chaque objet (votre tâche)
  2. Ajoutez cette fonction en tant que hook d'événement dans votre demande
  3. Appel async.mapsur une liste de toutes les demandes / actions

Exemple:

from requests import async
# If using requests > v0.13.0, use
# from grequests import async

urls = [
    'http://python-requests.org',
    'http://httpbin.org',
    'http://python-guide.org',
    'http://kennethreitz.com'
]

# A simple task to do to each response object
def do_something(response):
    print response.url

# A list to hold our things to do via async
async_list = []

for u in urls:
    # The "hooks = {..." part is where you define what you want to do
    # 
    # Note the lack of parentheses following do_something, this is
    # because the response will be used as the first argument automatically
    action_item = async.get(u, hooks = {'response' : do_something})

    # Add the task to our list of things to do via async
    async_list.append(action_item)

# Do our list of things to do via async
async.map(async_list)
Jeff
la source
2
Bonne idée d'avoir laissé votre commentaire: en raison de problèmes de compatibilité entre les dernières requêtes et les grequests (manque d'option max_retries dans les requêtes 1.1.0) j'ai dû rétrograder les requêtes pour récupérer async et j'ai constaté que la fonctionnalité asynchrone avait été déplacée avec les versions 0.13+ ( pypi.python.org/pypi/requests )
outforawhile
1
Question idiote: quelle est l'augmentation de la vitesse d'utilisation des grequests par rapport aux simples requêtes? Quelles limites y a-t-il concernant les demandes? par exemple, mettre 3500 requêtes dans async.map serait-il OK?
droope
10
from grequests import asyncne fonctionne pas .. et cette définition de dosomething fonctionne pour moi def do_something(response, **kwargs):, je la trouve sur stackoverflow.com/questions/15594015/…
Allan Ruin
3
si l'appel async.map bloque toujours, comment est-ce asynchrone? Outre les demandes elles-mêmes envoyées de manière asynchrone, la récupération est toujours synchrone?
bryanph
3
Remplacer from requests import asyncpar a import grequests as asyncfonctionné pour moi.
Martin Thoma
80

asyncest maintenant un module indépendant: grequests.

Voir ici: https://github.com/kennethreitz/grequests

Et là: Méthode idéale pour envoyer plusieurs requêtes HTTP via Python?

installation:

$ pip install grequests

usage:

construire une pile:

import grequests

urls = [
    'http://www.heroku.com',
    'http://tablib.org',
    'http://httpbin.org',
    'http://python-requests.org',
    'http://kennethreitz.com'
]

rs = (grequests.get(u) for u in urls)

envoyer la pile

grequests.map(rs)

le résultat ressemble à

[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]

Les grequests ne semblent pas définir de limitation pour les requêtes simultanées, c'est-à-dire lorsque plusieurs requêtes sont envoyées au même serveur.

outforawhile
la source
11
En ce qui concerne la limitation des requêtes simultanées, vous pouvez spécifier une taille de pool lors de l'exécution de map () / imap (). ie grequests.map (rs, size = 20) pour avoir 20 saisies simultanées.
synthesizerpatel
1
Pour l'instant, ce n'est pas compatible avec python3 (gevent ne parvient pas à construire la v2.6 sur py3.4).
saarp
1
Je ne comprends pas très bien la partie asynchrone. si je laisse results = grequests.map(rs)le code après que cette ligne soit bloqué, je peux voir l'effet asynchrone?
Allan Ruin
47

J'ai testé à la fois les requêtes-futures et les grequests . Grequests est plus rapide mais apporte des correctifs de singe et des problèmes supplémentaires avec les dépendances. requêtes-futurs est plusieurs fois plus lent que les grequests. J'ai décidé d'écrire mes propres requêtes simplement encapsulées dans ThreadPoolExecutor et c'était presque aussi rapide que les grequests, mais sans dépendances externes.

import requests
import concurrent.futures

def get_urls():
    return ["url1","url2"]

def load_url(url, timeout):
    return requests.get(url, timeout = timeout)

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

    future_to_url = {executor.submit(load_url, url, 10): url for url in     get_urls()}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            resp_err = resp_err + 1
        else:
            resp_ok = resp_ok + 1
Hodza
la source
Quel type d'exception est possible ici?
Slow Harry
demandes.exceptions.Timeout
Hodza
2
Désolé, je ne comprends pas votre question. N'utiliser qu'une seule URL dans plusieurs threads? Un seul cas d'attaques DDoS))
Hodza
1
Je ne comprends pas pourquoi cette réponse a reçu autant de votes positifs. La question OP concernait les demandes asynchrones. ThreadPoolExecutor exécute des threads. Oui, vous pouvez faire des requêtes dans plusieurs threads, mais ce ne sera jamais un programme asynchrone, alors je comment pourrait-il être une réponse à la question d'origine?
nagylzs le
1
En fait, la question était de savoir comment charger des URL en parallèle. Et oui, l'exécuteur de pool de threads n'est pas la meilleure option, il est préférable d'utiliser async io, mais cela fonctionne bien en Python. Et je ne comprends pas pourquoi les threads ne peuvent pas être utilisés pour l'asynchrone? Que faire si vous devez exécuter une tâche liée au processeur de manière asynchrone?
Hodza
29

peut - être que les demandes d'avenir sont un autre choix.

from requests_futures.sessions import FuturesSession

session = FuturesSession()
# first request is started in background
future_one = session.get('http://httpbin.org/get')
# second requests is started immediately
future_two = session.get('http://httpbin.org/get?foo=bar')
# wait for the first request to complete, if it hasn't already
response_one = future_one.result()
print('response one status: {0}'.format(response_one.status_code))
print(response_one.content)
# wait for the second request to complete, if it hasn't already
response_two = future_two.result()
print('response two status: {0}'.format(response_two.status_code))
print(response_two.content)

Il est également recommandé dans le document du bureau . Si vous ne voulez pas impliquer gevent, c'est une bonne solution.

Dreampuf
la source
1
Une des solutions les plus simples. Le nombre de requêtes simultanées peut être augmenté en définissant le paramètre max_workers
Jose Cherian
1
Ce serait bien de voir un exemple de cette mise à l'échelle afin que nous n'utilisions pas un nom de variable par élément pour boucler.
user1717828
avoir un thread par demande est un véritable gaspillage de ressources! il n'est pas possible de faire par exemple 500 requêtes simultanément, cela tuera votre cpu. cela ne devrait jamais être considéré comme une bonne solution.
Corneliu Maftuleac
@CorneliuMaftuleac bon point. En ce qui concerne l'utilisation des threads, vous devez absolument vous en soucier et la bibliothèque fournit une option pour activer le pool de threads ou le pool de traitement. ThreadPoolExecutor(max_workers=10)
Dreampuf
@Dreampuf traitement pool Je pense que c'est encore pire?
Corneliu Maftuleac
12

J'ai beaucoup de problèmes avec la plupart des réponses publiées - soit elles utilisent des bibliothèques obsolètes qui ont été portées avec des fonctionnalités limitées, soit fournissent une solution avec trop de magie sur l'exécution de la demande, ce qui rend difficile la gestion des erreurs. S'ils n'entrent pas dans l'une des catégories ci-dessus, ce sont des bibliothèques tierces ou obsolètes.

Certaines des solutions fonctionnent bien uniquement dans les requêtes http, mais les solutions sont insuffisantes pour tout autre type de requête, ce qui est ridicule. Une solution hautement personnalisée n'est pas nécessaire ici.

Le simple fait d'utiliser la bibliothèque intégrée python asyncioest suffisant pour effectuer des requêtes asynchrones de tout type, ainsi que pour fournir suffisamment de fluidité pour la gestion des erreurs complexes et spécifiques à des cas d'utilisation.

import asyncio

loop = asyncio.get_event_loop()

def do_thing(params):
    async def get_rpc_info_and_do_chores(id):
        # do things
        response = perform_grpc_call(id)
        do_chores(response)

    async def get_httpapi_info_and_do_chores(id):
        # do things
        response = requests.get(URL)
        do_chores(response)

    async_tasks = []
    for element in list(params.list_of_things):
       async_tasks.append(loop.create_task(get_chan_info_and_do_chores(id)))
       async_tasks.append(loop.create_task(get_httpapi_info_and_do_chores(ch_id)))

    loop.run_until_complete(asyncio.gather(*async_tasks))

Comment cela fonctionne est simple. Vous créez une série de tâches que vous souhaitez exécuter de manière asynchrone, puis vous demandez à une boucle d'exécuter ces tâches et de quitter à la fin. Pas de bibliothèques supplémentaires soumises à un manque de maintenance, aucun manque de fonctionnalités requises

arshbot
la source
2
Si je comprends bien, cela bloquera la boucle d'événements lors de l'appel GRPC et HTTP? Donc, si ces appels prennent quelques secondes, toute votre boucle d'événements se bloquera pendant quelques secondes? Pour éviter cela, vous devez utiliser les bibliothèques GRPC ou HTTP qui sont async. Ensuite, vous pouvez par exemple faire await response = requests.get(URL). Non?
Coder Nr 23
Malheureusement, en essayant cela, j'ai trouvé que créer un wrapper requestsest à peine plus rapide (et dans certains cas plus lent) que d'appeler simplement une liste d'URL de manière synchrone. Par exemple, demander un point de terminaison qui prend 3 secondes pour répondre 10 fois en utilisant la stratégie ci-dessus prend environ 30 secondes. Si vous voulez de vraies asyncperformances, vous devez utiliser quelque chose comme aiohttp.
DragonBobZ
@DragonBobZ Dans mon cas, j'ai constaté une réduction d'environ 40% du temps. Le principal avantage était de pouvoir effectuer les tâches nécessaires en attendant le prochain appel. Dans mon ensemble de données, je faisais des centaines d'appels, donc l'échelle pourrait également être un facteur.
arshbot il y a
@ CoderNr23 Quelqu'un peut me corriger là-dessus, mais même avec cette syntaxe, l'exécution des tâches est fondamentalement synchrone - l'ordre des tâches quand est ce qui est rendu asynchrone. En python, vous empaquetez simplement un arbre d'exécution synchrone qui sera exécuté avec un démarreur comme run_until_complete- c'est à moins que vous n'utilisiez le module de threading, qui délègue l'async à la couche OS. Lisez le problème GIL en python pour plus d'informations
arshbot il y a
@arshbot Oui, si vos tâches sont asynchrones, vous verrez des accélérations, malgré l'attente d'appels synchrones à requests.get. Mais la question est de savoir comment effectuer des requêtes asynchrones avec la requestsbibliothèque python . Cette réponse ne fait pas cela, donc ma critique tient.
DragonBobZ
8

Je sais que cela a été fermé pendant un certain temps, mais j'ai pensé qu'il pourrait être utile de promouvoir une autre solution asynchrone basée sur la bibliothèque de requêtes.

list_of_requests = ['http://moop.com', 'http://doop.com', ...]

from simple_requests import Requests
for response in Requests().swarm(list_of_requests):
    print response.content

Les documents sont ici: http://pythonhosted.org/simple-requests/

Singe Boson
la source
@YSY N'hésitez pas à poster un numéro: github.com/ctheiss/simple-requests/issues ; J'utilise littéralement cette bibliothèque des milliers de fois par jour.
Monkey Boson
Boston, comment gérez-vous les erreurs 404/500? qu'en est-il des URL https? apprécieront un extrait qui prend en charge des milliers d'urls. pouvez-vous s'il vous plaît coller un exemple? merci
YSY
@YSY Par défaut, les erreurs 404/500 génèrent une exception. Ce comportement peut être remplacé (voir pythonhosted.org/simple-requests/… ). Les URL HTTPS sont délicates en raison de la dépendance à l'égard de gevent, qui a actuellement un bogue exceptionnel à ce sujet ( github.com/gevent/gevent/issues/477 ). Il y a une cale dans le billet , vous pouvez courir, mais il faudra encore jeter des avertissements pour les serveurs SNI (mais il va travailler). Quant au snipping, j'ai peur que tous mes usages soient dans mon entreprise et fermés. Mais je vous assure que nous exécutons des milliers de demandes sur des dizaines de travaux.
Monkey Boson
La bibliothèque a l'air élégante en ce qui concerne l'interaction. Python3 + est-il utilisable? Désolé, je n'ai vu aucune mention.
Isaac Philip
@Jethro tout à fait raison, la bibliothèque aurait besoin d'une réécriture totale puisque les technologies sous-jacentes sont assez différentes en Python 3. Pour l'instant, la bibliothèque est "complète" mais ne fonctionne que pour Python 2.
Monkey Boson
4
threads=list()

for requestURI in requests:
    t = Thread(target=self.openURL, args=(requestURI,))
    t.start()
    threads.append(t)

for thread in threads:
    thread.join()

...

def openURL(self, requestURI):
    o = urllib2.urlopen(requestURI, timeout = 600)
    o...
Jason Pump
la source
4
il s'agit de requêtes "normales" dans les threads. n'est pas un mauvais exemple acheter est hors sujet.
Nick
4

Si vous souhaitez utiliser asyncio, requests-asyncfournit la fonctionnalité async / await pour requests- https://github.com/encode/requests-async

Tom Christie
la source
2
confirmé, fonctionne très bien. Sur la page du projet, il est indiqué que ce travail a été dépassé par le projet suivant github.com/encode/httpx
nurettin
2

J'utilise des requêtes python pour les appels asynchrones contre l'API gist de github depuis un certain temps.

Pour un exemple, consultez le code ici:

https://github.com/davidthewatson/flasgist/blob/master/views.py#L60-72

Ce style de python n'est peut-être pas l'exemple le plus clair, mais je peux vous assurer que le code fonctionne. Faites-moi savoir si cela vous dérange et je vais le documenter.

David Watson
la source
2

Vous pouvez utiliser httpxpour cela.

import httpx

async def get_async(url):
    async with httpx.AsyncClient() as client:
        return await client.get(url)

urls = ["http://google.com", "http://wikipedia.org"]

# Note that you need an async context to use `await`.
await asyncio.gather(*map(get_async, urls))

si vous voulez une syntaxe fonctionnelle, la lib gamla englobe cela get_async.

Alors tu peux faire


await gamla.map(gamla.get_async(10), ["http://google.com", "http://wikipedia.org"])

Le 10est le délai en secondes.

(avertissement: je suis son auteur)

Uri
la source
Et respxpour se moquer / tester :)
rlat le
0

J'ai également essayé certaines choses en utilisant les méthodes asynchrones en python, mais j'ai eu beaucoup plus de chance en utilisant Twisted pour la programmation asynchrone. Il a moins de problèmes et est bien documenté. Voici un lien entre quelque chose de similaire à ce que vous essayez de tordu.

http://pythonquirks.blogspot.com/2011/04/twisted-asynchronous-http-request.html

Sam
la source
Twisted est démodé. Utilisez plutôt HTTPX.
AmirHossein le
0

Malheureusement, pour autant que je sache, la bibliothèque de requêtes n'est pas équipée pour effectuer des requêtes asynchrones. Vous pouvez encapsuler la async/awaitsyntaxe requests, mais cela ne rendra pas les requêtes sous-jacentes moins synchrones. Si vous voulez de vraies requêtes asynchrones, vous devez utiliser d'autres outils qui les fournissent. Une de ces solutions est aiohttp(Python 3.5.3+). Cela fonctionne bien d'après mon expérience de son utilisation avec la async/awaitsyntaxe Python 3.7 . Ci-dessous, j'écris trois implémentations de l'exécution de n requêtes Web en utilisant

  1. Requêtes purement synchrones ( sync_requests_get_all) utilisant la requestsbibliothèque Python
  2. Requêtes synchrones ( async_requests_get_all) utilisant la requestsbibliothèque Python encapsulée dans la async/awaitsyntaxe Python 3.7 etasyncio
  3. Une implémentation vraiment asynchrone ( async_aiohttp_get_all) avec la aiohttpbibliothèque Python enveloppée dans la async/awaitsyntaxe Python 3.7 etasyncio
import time
import asyncio
import requests
import aiohttp

from types import SimpleNamespace

durations = []


def timed(func):
    """
    records approximate durations of function calls
    """
    def wrapper(*args, **kwargs):
        start = time.time()
        print(f'{func.__name__:<30} started')
        result = func(*args, **kwargs)
        duration = f'{func.__name__:<30} finsished in {time.time() - start:.2f} seconds'
        print(duration)
        durations.append(duration)
        return result
    return wrapper


async def fetch(url, session):
    """
    asynchronous get request
    """
    async with session.get(url) as response:
        response_json = await response.json()
        return SimpleNamespace(**response_json)


async def fetch_many(loop, urls):
    """
    many asynchronous get requests, gathered
    """
    async with aiohttp.ClientSession() as session:
        tasks = [loop.create_task(fetch(url, session)) for url in urls]
        return await asyncio.gather(*tasks)


@timed
def asnyc_aiohttp_get_all(urls):
    """
    performs asynchronous get requests
    """
    loop = asyncio.get_event_loop()
    return loop.run_until_complete(fetch_many(loop, urls))


@timed
def sync_requests_get_all(urls):
    """
    performs synchronous get requests
    """
    # use session to reduce network overhead
    session = requests.Session()
    return [SimpleNamespace(**session.get(url).json()) for url in urls]


@timed
def async_requests_get_all(urls):
    """
    asynchronous wrapper around synchronous requests
    """
    loop = asyncio.get_event_loop()
    # use session to reduce network overhead
    session = requests.Session()

    async def async_get(url):
        return session.get(url)

    async_tasks = [loop.create_task(async_get(url)) for url in urls]
    return loop.run_until_complete(asyncio.gather(*async_tasks))


if __name__ == '__main__':
    # this endpoint takes ~3 seconds to respond,
    # so a purely synchronous implementation should take
    # little more than 30 seconds and a purely asynchronous
    # implementation should take little more than 3 seconds.
    urls = ['https://postman-echo.com/delay/3']*10

    sync_requests_get_all(urls)
    async_requests_get_all(urls)
    asnyc_aiohttp_get_all(urls)
    print('----------------------')
    [print(duration) for duration in durations]

Sur ma machine, voici la sortie:

sync_requests_get_all          started
sync_requests_get_all          finsished in 30.92 seconds
async_requests_get_all         started
async_requests_get_all         finsished in 30.87 seconds
asnyc_aiohttp_get_all          started
asnyc_aiohttp_get_all          finsished in 3.22 seconds
----------------------
sync_requests_get_all          finsished in 30.92 seconds
async_requests_get_all         finsished in 30.87 seconds
asnyc_aiohttp_get_all          finsished in 3.22 seconds
DragonBobZ
la source