Como eu poderia usar solicitações no assíncio?


126

Quero executar tarefas de solicitação http paralela asyncio, mas acho que python-requestsisso bloquearia o loop de eventos de asyncio. Encontrei o aiohttp, mas ele não podia fornecer o serviço de solicitação http usando um proxy http.

Então, eu quero saber se há uma maneira de fazer solicitações http assíncronas com a ajuda de asyncio.


1
Se você está apenas enviando solicitações, pode usar subprocesspara fazer um paralelo com seu código.
— WeaselFox

Este método parece não ser elegante ……
— flyer

Agora existe uma porta assíncrona de solicitações. github.com/rdbhost/yieldfromRequests
— Rdbhost 23/03

Respostas:


181

Para usar solicitações (ou quaisquer outras bibliotecas de bloqueio) com assíncrono, você pode usar BaseEventLoop.run_in_executor para executar uma função em outro encadeamento e obter resultados para obter o resultado. Por exemplo:

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

Isso obterá as duas respostas em paralelo.

Com o python 3.5, você pode usar a nova await/ asyncsintaxe:

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

Veja PEP0492 para mais.


5
Você pode explicar exatamente como isso funciona? Eu não entendo como isso não bloqueia.
— Scott Coates

32
@christian, mas se estiver sendo executado simultaneamente em outro segmento, isso não está prejudicando o ponto de assíncio?
— Scott Coates

21
@scoarescoare É aí que entra a parte 'se você fizer direito' - o método que você executa no executor deve ser independente ((principalmente) como orders.get no exemplo acima). Dessa forma, você não precisa lidar com a memória compartilhada, o bloqueio etc., e as partes complexas do seu programa ainda são únicas, graças ao asyncio.
— cristão

5
@scoarescoare O principal caso de uso é a integração com bibliotecas de E / S que não têm suporte para assíncio. Por exemplo, estou trabalhando com uma interface SOAP verdadeiramente antiga e usando a biblioteca suds-jurko como a solução "menos ruim". Estou tentando integrá-lo a um servidor assíncrono, portanto, estou usando run_in_executor para fazer chamadas de suds de bloqueio de uma maneira que pareça assíncrona.
— Lucretiel

10
Muito legal que isso funciona e por isso é tão fácil para o material legado, mas deve ser enfatizado isso usa um pool de threads OS e que por isso não escalar como um verdadeiro orientada asyncio lib como aiohttp faz
— jsalter

78

o aiohttp já pode ser usado com o 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())

O que o conector faz aqui?
— Markus Meskanen 07/10/2015

Ele fornece uma ligação através de servidor proxy
— MindMaster

16
Essa é uma solução muito melhor do que usar solicitações em um thread separado. Por ser verdadeiramente assíncrono, possui menor sobrecarga e menor uso de mem.
— Thom

14
para python> = 3.5, substitua @ asyncio.coroutine por "async" e "yield from" com "waitit"
— James

40

As respostas acima ainda estão usando as antigas corotinas do estilo Python 3.4. Aqui está o que você escreveria se tivesse o Python 3.5+.

aiohttp suporta proxy http agora

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
você poderia elaborar com mais URLs? Não faz sentido ter apenas um URL quando a pergunta é sobre solicitação http paralela.
— anônimo

Lenda. Obrigado! Funciona muito bem
— Adam

@ospider Como esse código pode ser modificado para fornecer, digamos, 10k URLs usando 100 solicitações em paralelo? A ideia é usar todos os 100 slots de, simultaneamente, não esperar por 100 a ser entregue, a fim de começar a próxima 100.
— Antoan Milkov

@AntoanMilkov Essa é uma pergunta diferente que não pode ser respondida na área de comentários.
— ospider

@ospider Você está certo, aqui está a pergunta: stackoverflow.com/questions/56523043/…
— Antoan Milkov

11

Atualmente, os pedidos não suportam asyncioe não há planos para fornecer esse suporte. É provável que você possa implementar um "Adaptador de transporte" personalizado (como discutido aqui ) que saiba como usarasyncio .

Se eu me encontrar com algum tempo, é algo que eu realmente poderia investigar, mas não posso prometer nada.


O link leva a um 404.
— CodeBiker

8

Há um bom caso de loops e rosqueamento async / waitit em um artigo de Pimin Konstantin Kefaloukos Solicitações fáceis de HTTP paralelas com Python e asyncio :

Para minimizar o tempo total de conclusão, podemos aumentar o tamanho do conjunto de encadeamentos para corresponder ao número de solicitações que precisamos fazer. Felizmente, isso é fácil de fazer, como veremos a seguir. A lista de códigos abaixo é um exemplo de como fazer vinte solicitações HTTP assíncronas com um conjunto de threads de vinte threads de trabalho:

# 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
O problema é que, se eu precisar executar 10000 solicitações com pedaços de 20 executores, tenho que esperar que todos os 20 executadores terminem para começar com os próximos 20, certo? Não posso fazer isso for i in range(10000)porque uma solicitação pode falhar ou atingir o tempo limite, certo?
— precisa

1
Você pode explicar por que você precisa de assíncrono quando pode fazer o mesmo usando ThreadPoolExecutor?
— Asaf Pinhassi

@lya Rusin Com base em quê, definimos o número de max_workers? Isso tem a ver com o número de CPUs e threads?
— alt-f4
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.