Solicitudes asincrónicas con solicitudes de Python


142

Probé la muestra proporcionada en la documentación de la biblioteca de solicitudes para python.

Con async.map(rs), obtengo los códigos de respuesta, pero quiero obtener el contenido de cada página solicitada. Esto, por ejemplo, no funciona:

out = async.map(rs)
print out[0].content

¿Quizás las respuestas que recibes tienen el cuerpo vacío?
Mariusz Jamro

Funciona para mi. Publique el error completo que está recibiendo.
Chewie

No hay error. simplemente se ejecuta para siempre por las URL de prueba proporcionadas.
trbck

obviamente aparece cuando uso URL sobre https. http está funcionando bien
trbck 02 de

Parece que requests-threadsexiste ahora.
OrangeDog

Respuestas:


154

Nota

La siguiente respuesta no es aplicable a las solicitudes v0.13.0 +. La funcionalidad asincrónica se trasladó a grequests después de escribir esta pregunta. Sin embargo, podría reemplazar requestscon el grequestssiguiente y debería funcionar.

He dejado esta respuesta como para reflejar la pregunta original sobre el uso de solicitudes <v0.13.0.


Para realizar múltiples tareas de async.map forma asincrónica debes:

  1. Defina una función para lo que desea hacer con cada objeto (su tarea)
  2. Agregue esa función como un enlace de evento en su solicitud
  3. Llame async.mapa una lista de todas las solicitudes / acciones

Ejemplo:

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)

2
Buena idea haber dejado su comentario: debido a problemas de compatibilidad entre las últimas solicitudes y grequests (falta de la opción max_retries en las solicitudes 1.1.0) tuve que degradar las solicitudes para recuperar asíncrono y descubrí que la funcionalidad asincrónica se movió con las versiones 0.13+ ( pypi.python.org/pypi/requests )
antes del

1
Pregunta tonta: ¿Cuál es el aumento de velocidad de usar grequests en lugar de simplemente solicitudes? ¿Qué límites existen con respecto a las solicitudes? por ejemplo, ¿estaría bien poner 3500 solicitudes en async.map?
droope

10
from grequests import asyncno funciona ... y esta definición de algo me funciona def do_something(response, **kwargs):, lo encuentro en stackoverflow.com/questions/15594015/…
Allan Ruin

3
si la llamada async.map aún se bloquea, entonces, ¿cómo es esto asíncrono? Además de que las solicitudes se envían de forma asíncrona, ¿la recuperación sigue siendo sincrónica?
bryanph

3
Reemplazar from requests import asyncpor import grequests as asynctrabajado para mí.
Martin Thoma

80

asynces ahora un módulo independiente: grequests.

Ver aquí: https://github.com/kennethreitz/grequests

Y allí: ¿ Método ideal para enviar múltiples solicitudes HTTP a través de Python?

instalación:

$ pip install grequests

uso:

construir una pila:

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)

envía la pila

grequests.map(rs)

el resultado parece

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

grequests no parece establecer una limitación para las solicitudes concurrentes, es decir, cuando se envían varias solicitudes al mismo servidor.


11
Con respecto a la limitación de las solicitudes simultáneas, puede especificar un tamaño de grupo al ejecutar el mapa () / imap (). es decir, grequests.map (rs, size = 20) para tener 20 capturas simultáneas.
sintetizadorpatel

1
A partir de ahora, esto no es compatible con python3 (gevent no puede construir v2.6 en py3.4).
saarp

1
No entiendo muy bien la parte asíncrona. si dejo que results = grequests.map(rs)el código después de esta línea sea bloqueado, ¿puedo ver el efecto asíncrono?
Allan Ruin

47

Probé ambas solicitudes de futuros y grequests . Grequests es más rápido pero trae parches de mono y problemas adicionales con las dependencias. request-futuros es varias veces más lento que grequests. Decidí escribir mis propias solicitudes y simplemente envolverlas en ThreadPoolExecutor y fue casi tan rápido como grequests, pero sin dependencias externas.

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

¿Qué tipo de excepción es posible aquí?
Lento Harry

requests.exceptions.Timeout
Hodza

2
Lo siento, no entiendo tu pregunta. ¿Usar solo una URL en múltiples hilos? Solo un caso de ataques DDoS))
Hodza

1
No entiendo por qué esta respuesta obtuvo tantos votos positivos. La pregunta de OP era sobre solicitudes asíncronas. ThreadPoolExecutor ejecuta hilos. Sí, puede realizar solicitudes en varios subprocesos, pero eso nunca será un programa asíncrono, así que ¿cómo podría ser una respuesta a la pregunta original?
nagylzs

1
En realidad, la pregunta era sobre cómo cargar URL en paralelo. Y sí, el ejecutor de grupo de subprocesos no es la mejor opción, es mejor usar async io, pero funciona bien en Python. ¿Y no entiendo por qué los hilos no pueden usarse para asíncrono? ¿Qué sucede si necesita ejecutar una tarea vinculada a la CPU de forma asincrónica?
Hodza

29

tal vez las solicitudes de futuros es otra opción.

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)

También se recomienda en el documento de la oficina . Si no quieres involucrar a gevent, es bueno.


1
Una de las soluciones más fáciles. El número de solicitudes simultáneas se puede aumentar definiendo el parámetro max_workers
Jose Cherian

1
Sería bueno ver un ejemplo de esta escala, por lo que no estamos usando un nombre de variable por elemento para recorrer.
user1717828

¡Tener un hilo por solicitud es una gran pérdida de recursos! no es posible hacer, por ejemplo, 500 solicitudes simultáneamente, matará su CPU. Esto nunca debe considerarse una buena solución.
Corneliu Maftuleac

@CorneliuMaftuleac buen punto. Con respecto al uso de subprocesos, definitivamente debe preocuparse por ello y la biblioteca proporciona una opción para habilitar el grupo de subprocesos o el grupo de procesamiento. ThreadPoolExecutor(max_workers=10)
Dreampuf

@Dreampuf grupo de procesamiento creo que es aún peor?
Corneliu Maftuleac

11

Tengo muchos problemas con la mayoría de las respuestas publicadas: o usan bibliotecas obsoletas que se han portado con funciones limitadas o proporcionan una solución con demasiada magia en la ejecución de la solicitud, lo que dificulta el manejo de errores. Si no entran en una de las categorías anteriores, son bibliotecas de terceros o están en desuso.

Algunas de las soluciones funcionan bien únicamente en solicitudes http, pero las soluciones se quedan cortas para cualquier otro tipo de solicitud, lo cual es ridículo. Aquí no es necesaria una solución altamente personalizada.

El simple uso de la biblioteca incorporada de Python asyncioes suficiente para realizar solicitudes asíncronas de cualquier tipo, además de proporcionar suficiente fluidez para el manejo de errores complejos y de casos de uso específicos.

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

El funcionamiento es simple. Está creando una serie de tareas que le gustaría que ocurrieran de forma asíncrona y luego solicita un ciclo para ejecutar esas tareas y salir al finalizar. No hay bibliotecas adicionales sujetas a falta de mantenimiento, no se requiere falta de funcionalidad.


2
Si entiendo correctamente, ¿esto bloqueará el bucle de eventos mientras realizo la llamada GRPC y HTTP? Entonces, si estas llamadas tardan segundos en completarse, ¿se bloqueará todo el bucle de eventos durante segundos? Para evitar esto, debe usar las bibliotecas GRPC o HTTP que están async. Entonces puedes, por ejemplo, hacer await response = requests.get(URL). ¿No?
Codificador Nr 23

Desafortunadamente, al probar esto, descubrí que hacer una envoltura requestses apenas más rápido (y en algunos casos más lento) que simplemente llamar una lista de URL sincrónicamente. Por ejemplo, solicitar un punto final que demore 3 segundos en responder 10 veces usando la estrategia anterior toma aproximadamente 30 segundos. Si quieres un asyncrendimiento real , necesitas usar algo como aiohttp.
DragonBobZ

8

Sé que esto ha estado cerrado por un tiempo, pero pensé que podría ser útil promover otra solución asíncrona construida en la biblioteca de solicitudes.

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

Los documentos están aquí: http://pythonhosted.org/simple-requests/


@YSY Siéntase libre de publicar un problema: github.com/ctheiss/simple-requests/issues ; Literalmente uso esta biblioteca miles de veces al día.
Monkey Boson

Boston, ¿cómo manejas los errores 404/500? ¿Qué pasa con las URL https? apreciará un recorte que admite miles de URL. ¿Puedes pegar un ejemplo? gracias
YSY

@YSY Por defecto, los errores 404/500 generan una excepción. Este comportamiento puede ser anulado (ver pythonhosted.org/simple-requests/… ). Las URL de HTTPS son complicadas debido a la dependencia de gevent, que actualmente tiene un error sobresaliente en esto ( github.com/gevent/gevent/issues/477 ). Hay una cuña en el billete puede ejecutar, pero todavía va a tirar advertencias para los servidores SNI (pero va a funcionar). En cuanto al recorte, me temo que todos mis usos están en mi compañía y cerrados. Pero le aseguro que ejecutamos miles de solicitudes en decenas de trabajos.
Monkey Boson

La biblioteca se ve elegante con respecto a la interacción. ¿Se puede usar Python3 +? Lo siento, no pude ver ninguna mención.
Isaac Philip

@Jethro tiene toda la razón, la biblioteca necesitaría una reescritura total ya que las tecnologías subyacentes son bastante diferentes en Python 3. Por ahora, la biblioteca está "completa" pero solo funciona para 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...

44
Esto es solicitudes "normales" en subprocesos. No está mal ejemplo, comprar está fuera de tema.
Nick



2

Puedes usar httpxpara eso.

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 desea una sintaxis funcional, la gamla lib lo envuelve get_async.

Entonces puedes hacer


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

El 10es el tiempo de espera en segundos.

(descargo de responsabilidad: soy su autor)


Y respxpara burlarse / probar :)
rlat

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.