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.

folheto
fonte
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.

cristão
fonte
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())
mestre da mente
fonte
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())
ospider
fonte
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.

Lukasa
fonte
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())
Ilya Rusin
fonte
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