¿Cómo podría usar solicitudes en asyncio?

127

Quiero hacer tareas paralelas de solicitud http asyncio, pero encuentro que python-requestsbloquearía el bucle de eventos de asyncio. Encontré aiohttp pero no podía proporcionar el servicio de solicitud http utilizando un proxy http.

Así que quiero saber si hay una manera de hacer solicitudes http asíncronas con la ayuda de asyncio.

volantes
fuente
1
Si solo está enviando solicitudes, puede usarlo subprocesspara poner en paralelo su código.
WeaselFox
Este método no parece elegante ......
folleto
Ahora hay un puerto asyncio de solicitudes. github.com/rdbhost/yieldfromRequests
Rdbhost

Respuestas:

181

Para usar solicitudes (o cualquier otra biblioteca de bloqueo) con asyncio, puede usar BaseEventLoop.run_in_executor para ejecutar una función en otro hilo y obtener el resultado para obtener el resultado. Por ejemplo:

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

Esto obtendrá ambas respuestas en paralelo.

Con python 3.5 puede usar la nueva await/ asyncsintaxis:

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

Ver PEP0492 para más.

cristiano
fuente
55
¿Puedes explicar cómo funciona exactamente esto? No entiendo cómo esto no bloquea.
Scott Coates
32
@christian pero si se está ejecutando simultáneamente en otro hilo, ¿no es eso derrotar el punto de asyncio?
Scott Coates
21
@scoarescoare Ahí es donde entra la parte 'si lo haces bien': el método que ejecutas en el ejecutor debe ser autónomo ((principalmente) como request.get en el ejemplo anterior). De esta forma, no tiene que lidiar con memoria compartida, bloqueo, etc., y las partes complejas de su programa todavía son de un solo subproceso gracias a asyncio.
Christian
55
@scoarescoare El caso de uso principal es para integrarse con bibliotecas IO que no tienen soporte para asyncio. Por ejemplo, estoy trabajando con una interfaz SOAP verdaderamente antigua, y estoy usando la biblioteca suds-jurko como la solución "menos mala". Estoy tratando de integrarlo con un servidor asincio, así que estoy usando run_in_executor para hacer que las llamadas de bloqueo de espuma se vean de una manera que se vea asíncrona.
Lucretiel
10
Realmente genial que esto funcione y que sea tan fácil para las cosas heredadas, pero debe enfatizarse que usa un conjunto de hilos del sistema operativo y no se escala como una verdadera biblioteca orientada a asincio como lo hace
aiohttp
78

aiohttp ya se puede usar con el 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())
mente maestra
fuente
¿Qué hace el conector aquí?
Markus Meskanen
Proporciona una conexión a través del servidor proxy
mindmaster
16
Esta es una solución mucho mejor que usar solicitudes en un hilo separado. Como es realmente asíncrono, tiene una sobrecarga más baja y un menor uso de memoria.
Thom
14
para python> = 3.5 reemplace @ asyncio.coroutine con "async" y "ceder desde" con "wait"
James
40

Las respuestas anteriores todavía usan las antiguas corutinas de estilo Python 3.4. Esto es lo que escribiría si tuviera Python 3.5+.

aiohttp ahora es compatible con http proxy

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
fuente
1
¿podrías elaborar con más urls? No tiene sentido tener solo una URL cuando la pregunta es sobre una solicitud http paralela.
anónimo
Leyenda. ¡Gracias! Funciona muy bien
Adam
@ospider ¿Cómo se puede modificar este código para entregar, por ejemplo, 10k URL utilizando 100 solicitudes en paralelo? La idea es utilizar las 100 máquinas tragamonedas simultáneamente, no esperar a que se entreguen 100 para comenzar las siguientes 100.
Antoan Milkov
@AntoanMilkov Esa es una pregunta diferente que no se puede responder en el área de comentarios.
Ospider
@ospider Tienes razón, aquí está la pregunta: stackoverflow.com/questions/56523043/…
Antoan Milkov
11

Actualmente, las solicitudes no son compatibles asyncioy no hay planes para proporcionar dicho soporte. Es probable que pueda implementar un "Adaptador de transporte" personalizado (como se describe aquí ) que sabe cómo usar asyncio.

Si me encuentro con algo de tiempo, es algo que realmente podría investigar, pero no puedo prometer nada.

Lukasa
fuente
El enlace conduce a un 404.
CodeBiker
8

Hay un buen caso de bucles asincrónicos / en espera y subprocesos en un artículo de Pimin Konstantin Kefaloukos Solicitudes HTTP sencillas en paralelo con Python y asyncio :

Para minimizar el tiempo total de finalización, podríamos aumentar el tamaño del grupo de subprocesos para que coincida con la cantidad de solicitudes que tenemos que hacer. Afortunadamente, esto es fácil de hacer, como veremos a continuación. La lista de códigos a continuación es un ejemplo de cómo realizar veinte solicitudes HTTP asincrónicas con un grupo de subprocesos de veinte subprocesos de trabajo:

# 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
fuente
2
El problema con esto es que si necesito ejecutar 10000 solicitudes con trozos de 20 ejecutores, tengo que esperar a que finalicen los 20 ejecutores para comenzar con los próximos 20, ¿verdad? No puedo hacerlo for i in range(10000)porque una solicitud puede fallar o agotar el tiempo de espera, ¿verdad?
Sanandrea
1
¿Puede explicar por qué necesita asyncio cuando puede hacer lo mismo solo con ThreadPoolExecutor?
Asaf Pinhassi
@lya Rusin Basado en qué, ¿establecemos el número de max_workers? ¿Tiene que ver con el número de CPU y subprocesos?
alt-f4