Solicitações assíncronas com solicitações Python


142

Eu tentei a amostra fornecida na documentação da biblioteca de solicitações para python.

Com async.map(rs), recebo os códigos de resposta, mas quero obter o conteúdo de cada página solicitada. Isso, por exemplo, não funciona:

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

Talvez as respostas que você está recebendo tenham corpo vazio?
Mariusz Jamro

Funciona para mim. Poste o erro completo que você está recebendo.
Chewie

não há erro. apenas funciona para sempre pelos URLs de teste fornecidos.
trbck

Obviamente, aparece quando eu uso URLs em https. http está funcionando muito bem
trbck 02/02

Parece que requests-threadsexiste agora.
OrangeDog

Respostas:


154

Nota

A resposta abaixo não é aplicável aos pedidos v0.13.0 +. A funcionalidade assíncrona foi movida para grequests depois que essa pergunta foi escrita. No entanto, você pode simplesmente substituir requestspor grequestsabaixo e deve funcionar.

Eu deixei esta resposta como é para refletir a pergunta original que era sobre o uso de solicitações <v0.13.0.


Para executar várias tarefas de async.map forma assíncrona, é necessário:

  1. Defina uma função para o que você deseja fazer com cada objeto (sua tarefa)
  2. Adicione essa função como um gancho de evento em sua solicitação
  3. Chame async.mapuma lista de todos os pedidos / ações

Exemplo:

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
Boa ideia ter deixado seu comentário: devido a problemas de compatibilidade entre solicitações mais recentes e grequests (falta de opção max_retries nas solicitações 1.1.0), tive que fazer o downgrade das solicitações para recuperar async e descobri que a funcionalidade assíncrona foi movida nas versões 0.13+ ( pypi.python.org/pypi/requests )
outforawhile 20/02

1
Pergunta idiota: Qual é o aumento da velocidade do uso de grequests em vez de simplesmente pedidos? Que limites existem em relação a solicitações? por exemplo, colocar 3500 solicitações no async.map seria bom?
droope

10
from grequests import asyncnão funciona .. e esta definição de algo funciona para mim def do_something(response, **kwargs):, acho-a de stackoverflow.com/questions/15594015/…
Allan Ruin

3
se a chamada async.map ainda bloquear, como isso é assíncrono? Além dos pedidos serem enviados de forma assíncrona, a recuperação ainda é síncrona?
Bryanph 27/05

3
Substituir from requests import asyncpor import grequests as asyncfuncionou para mim.
Martin Thoma 5/05

80

asyncagora é um módulo independente: grequests.

Veja aqui: https://github.com/kennethreitz/grequests

E aí: Método ideal para enviar várias solicitações HTTP através de Python?

instalação:

$ pip install grequests

uso:

construa uma pilha:

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)

envie a pilha

grequests.map(rs)

resultado parece

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

grequests não parecem definir uma limitação para solicitações simultâneas, ou seja, quando várias solicitações são enviadas para o mesmo servidor.


11
Com relação à limitação de solicitações simultâneas - você pode especificar um tamanho de pool ao executar o map () / imap (). ou seja, grequests.map (rs, size = 20) para ter 20 capturas simultâneas.
Synthesizerpatel

1
No momento, isso não é compatível com python3 (o gevent falha ao criar a v2.6 no py3.4).
saarp

1
Não entendo bem a parte assíncrona. se eu deixar results = grequests.map(rs)o código após esta linha ser bloqueada, posso ver o efeito assíncrono?
Allan Ruin

47

Eu testei os pedidos de futuros e grequests . O Grequests é mais rápido, mas traz o patch para macacos e problemas adicionais com as dependências. orders-futures é várias vezes mais lento que grequests. Decidi escrever minhas próprias solicitações e simplesmente encapsulá-las no ThreadPoolExecutor, e era quase tão rápido quanto as grequests, mas sem dependências 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

Que tipo de exceção é possível aqui?
Slow Harry

requests.exceptions.Timeout
Hodza

2
Desculpe, não entendi sua pergunta. Usar apenas um URL em vários threads? Apenas um caso de ataques DDoS))
Hodza 27/11/2015

1
Não entendo por que essa resposta recebeu tantos votos positivos. A pergunta do OP era sobre solicitações assíncronas. ThreadPoolExecutor executa threads. Sim, você pode fazer solicitações em vários threads, mas isso nunca será um programa assíncrono. Por isso, como poderia ser uma resposta para a pergunta original?
Nagylzs

1
Na verdade, a pergunta era sobre como carregar URLs em paralelo. E sim, o executor do pool de threads não é a melhor opção, é melhor usar o async io, mas funciona bem em Python. E eu não entendo por que threads não podem ser usados ​​para assíncrono? E se você precisar executar a tarefa vinculada à CPU de forma assíncrona?
Hodza 15/02/19

29

talvez pedidos-futuros seja outra opção.

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)

Também é recomendado no documento do escritório . Se você não quer envolver gevent, é uma boa.


1
Uma das soluções mais fáceis. Número de solicitações simultâneas pode ser aumentada através da definição de max_workers parâmetro
Jose Cherian

1
Seria bom ver um exemplo disso em escala, por isso não estamos usando um nome de variável por item para fazer o loop.
user1717828

Ter um encadeamento por solicitação é um grande desperdício de recursos! não é possível fazer, por exemplo, 500 solicitações simultaneamente, isso mata o seu processador. isso nunca deve ser considerado uma boa solução.
Corneliu Maftuleac

@CorneliuMaftuleac good point. Com relação ao uso do encadeamento, você definitivamente precisa se preocupar com isso, e a biblioteca fornece uma opção para ativar o pool de threading ou de processamento. ThreadPoolExecutor(max_workers=10)
Dreampuf 13/02/19

@Dreampuf pool de processamento Eu acredito que é ainda pior?
Corneliu Maftuleac

11

Eu tenho muitos problemas com a maioria das respostas postadas - eles usam bibliotecas obsoletas que foram portadas com recursos limitados ou fornecem uma solução com muita mágica na execução da solicitação, dificultando a manipulação de erros. Se eles não se enquadram em uma das categorias acima, são bibliotecas de terceiros ou estão obsoletas.

Algumas das soluções funcionam bem puramente em solicitações http, mas as soluções ficam aquém de qualquer outro tipo de solicitação, o que é ridículo. Uma solução altamente personalizada não é necessária aqui.

Simplesmente usar a biblioteca asynciointerna python é suficiente para executar solicitações assíncronas de qualquer tipo, além de fornecer fluidez suficiente para manipulação de erros complexa e específica de uso do usuário.

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

Como funciona é simples. Você está criando uma série de tarefas que gostaria de ocorrer de forma assíncrona e solicitando um loop para executar essas tarefas e sair após a conclusão. Nenhuma biblioteca extra sujeita a falta de manutenção, não há funcionalidade necessária.


2
Se bem entendi, isso bloqueará o loop de eventos ao fazer a chamada GRPC e HTTP? Então, se essas chamadas demorarem alguns segundos para serem concluídas, todo o seu loop de eventos será bloqueado por segundos? Para evitar isso, você precisa usar as bibliotecas GRPC ou HTTP que são async. Então você pode, por exemplo, fazer await response = requests.get(URL). Não?
Coder Nr 23/02

Infelizmente, ao tentar fazer isso, descobri que criar um invólucro requestsé pouco mais rápido (e, em alguns casos, mais lento) do que chamar uma lista de URLs de forma síncrona. Por exemplo, solicitar um ponto de extremidade que leva 3 segundos para responder 10 vezes usando a estratégia acima leva cerca de 30 segundos. Se você quer um asyncdesempenho verdadeiro , precisa usar algo parecido aiohttp.
DragonBobZ 6/06

8

Sei que isso foi encerrado por um tempo, mas achei que poderia ser útil promover outra solução assíncrona criada na biblioteca de solicitações.

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

Os documentos estão aqui: http://pythonhosted.org/simple-requests/


@YSY Sinta-se à vontade para postar um problema: github.com/ctheiss/simple-requests/issues ; Eu literalmente uso essa biblioteca milhares de vezes por dia.
Monkey Boson

Boston, como você lida com erros 404/500? e quanto a URLs https? apreciará um recorte que suporta milhares de URLs. você pode colar um exemplo? agradecimento
YSY

@YSY Por padrão, os erros 404/500 geram uma exceção. Esse comportamento pode ser substituído (consulte pythonhosted.org/simple-requests/… ). Os URLs HTTPS são complicados devido à dependência do gevent, que atualmente possui um bug pendente nisso ( github.com/gevent/gevent/issues/477 ). Há um calço no bilhete você pode correr, mas ainda irá lançar avisos para servidores SNI (mas irá funcionar). Quanto ao recorte, receio que todos os meus usos estejam na minha empresa e fechados. Mas garanto que executamos milhares de solicitações em dezenas de empregos.
Monkey Boson

A biblioteca parece elegante em relação à interação. Python3 + é utilizável? Desculpe, não vi nenhuma menção.
Isaac Philip

@ Jethro, absolutamente certo, a biblioteca precisaria de uma reescrita total, pois as tecnologias subjacentes são bem diferentes no Python 3. Por enquanto, a biblioteca está "completa", mas funciona apenas para o 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...

4
isso é solicitações "normais" em threads. não é mau exemplo de compra é fora de tópico.
Nick



2

Você pode usar httpxpara isso.

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

se você quer uma sintaxe funcional, a gamla lib envolve isso get_async.

Então você pode fazer


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

O 10tempo limite é em segundos.

(aviso: sou o autor)


E respxpara zombar / testar :)
rlat 29/06

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.