Tempo limite para solicitações python. Obter resposta inteira

169

Estou coletando estatísticas em uma lista de sites e usando solicitações para simplificar. Aqui está o meu código:

data=[]
websites=['http://google.com', 'http://bbc.co.uk']
for w in websites:
    r= requests.get(w, verify=False)
    data.append( (r.url, len(r.content), r.elapsed.total_seconds(), str([(l.status_code, l.url) for l in r.history]), str(r.headers.items()), str(r.cookies.items())) )

Agora, quero requests.getexpirar após 10 segundos para que o loop não fique preso.

Essa pergunta também foi interessante antes, mas nenhuma das respostas é clara. Vou dar uma recompensa por isso para obter uma boa resposta.

Ouvi dizer que talvez não usar solicitações seja uma boa ideia, mas como devo obter as coisas boas que as solicitações oferecem. (os da tupla)

Kiarash
fonte
1
Que tipo de resposta você está procurando? (ou, em outras palavras, porque são as respostas atuais não o suficiente para você?)
Yuvi
Estamos no período de graça da recompensa. Hora de escolher uma resposta?
totokaka
Ainda estou decidindo entre a solução e os sinais do eventlet. Vou premiar a pergunta hoje à noite.
Kiarash

Respostas:

138

Que tal usar o eventlet? Se você deseja exceder o tempo limite da solicitação após 10 segundos, mesmo se os dados estiverem sendo recebidos, esse snippet funcionará para você:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)
Alvaro
fonte
114
Certamente isso é desnecessariamente complicado.
holdenweb 28/02
7
Obrigado. Agora entendo a superioridade técnica da sua solução (que você declarou de forma bastante sucinta no início de sua resposta) e a votei de forma positiva. O problema com os módulos de terceiros não é importá-los, mas garantir que eles estejam lá para serem importados; portanto, minha própria preferência por usar a biblioteca padrão sempre que possível.
holdenweb
9
É eventlet.monkey_patch()obrigatório?
Utilizador
3
Sim, o socketmódulo precisa ser macaco remendado, assim pelo menos você vai precisar de umaeventlet.monkey_patch(socket=True)
Alvaro
51
A partir de 2018, esta resposta está desatualizada. Userequests.get('https://github.com', timeout=5)
CONvid19
312

Defina o parâmetro timeout :

r = requests.get(w, verify=False, timeout=10) # 10 seconds

Contanto que você não defina stream=Trueessa solicitação, o requests.get()tempo limite da chamada será excedido se a conexão demorar mais de dez segundos ou se o servidor não enviar dados por mais de dez segundos.

Lukasa
fonte
31
Isso não é para toda a resposta. requests.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash
1
Sim, é, em algumas circunstâncias. Uma dessas circunstâncias é sua. =) Convido você a examinar o código, se não estiver convencido.
Lukasa 23/02
quais são as circunstâncias?
Kiarash 23/02
1
Acabei de verificar isso e ele nunca parava: r = orders.get (' ipv4.download.thinkbroadband.com/1GB.zip ', timeout = 20) #
306 Kiarash
5
Ah, desculpe, eu não entendi o que você quis dizer quando disse 'toda a resposta'. Sim, você está certo: não é um limite máximo para o tempo total de espera.
Lukasa 24/02
85

ATUALIZAÇÃO: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

Na nova versão do requests:

Se você especificar um único valor para o tempo limite, assim:

r = requests.get('https://github.com', timeout=5)

O valor do tempo limite será aplicado aos tempos limite connecte ao readtempo limite. Especifique uma tupla se desejar definir os valores separadamente:

r = requests.get('https://github.com', timeout=(3.05, 27))

Se o servidor remoto estiver muito lento, você pode pedir para as solicitações aguardarem uma resposta eternamente, passando None como um valor de tempo limite e, em seguida, recuperando uma xícara de café.

r = requests.get('https://github.com', timeout=None)

Minha resposta antiga (provavelmente desatualizada) (postada há muito tempo):

Existem outras maneiras de superar esse problema:

1. Use a TimeoutSauceclasse interna

De: https://github.com/kennethreitz/requests/issues/1928#issuecomment-35811896

import requests from requests.adapters import TimeoutSauce

class MyTimeout(TimeoutSauce):
    def __init__(self, *args, **kwargs):
        connect = kwargs.get('connect', 5)
        read = kwargs.get('read', connect)
        super(MyTimeout, self).__init__(connect=connect, read=read)

requests.adapters.TimeoutSauce = MyTimeout

Esse código deve fazer com que definamos o tempo limite de leitura como igual ao tempo limite de conexão, que é o valor do tempo limite que você passa na sua chamada Session.get (). (Observe que eu realmente não testei esse código, por isso pode precisar de alguma depuração rápida, apenas o escrevi diretamente na janela do GitHub.)

2. Use uma bifurcação de solicitações da kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

A partir de sua documentação: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Se você especificar um único valor para o tempo limite, assim:

r = requests.get('https://github.com', timeout=5)

O valor do tempo limite será aplicado aos tempos limite de conexão e leitura. Especifique uma tupla se desejar definir os valores separadamente:

r = requests.get('https://github.com', timeout=(3.05, 27))

O kevinburke solicitou que ele fosse mesclado no projeto de solicitações principais, mas ainda não foi aceito.

Hieu
fonte
opção 1 não funciona. se você continuar lendo esse segmento, outras pessoas disseram "isso não funcionará para o seu caso de uso, receio. A função de tempo limite de leitura está no escopo de uma chamada individual de recv (), de modo que, se o servidor pára de enviar dados por mais do que o tempo limite de leitura que abortamos ".
Kiarash
Existe outra solução interessante nesse segmento usando o Signal, que também não funcionaria para mim, porque eu uso o Windows e o signal.alarm é apenas linux.
Kiarash
@Kiarash Ainda não testei. No entanto, como eu entendo quando Lukasa disse this won't work for you use-case. Ele quis dizer que não funciona com mp3 stream que é procurado pelo outro cara.
Hieu
1
@Hieu - isso foi mesclado em outra solicitação de recebimento
github.com/kennethreitz/requests/pull/…
timeout = Nenhum não está bloqueando a chamada.
crazydan
49

timeout = int(seconds)

Desde requests >= 2.4.0, você pode usar o timeoutargumento, ou seja:

requests.get('https://duckduckgo.com/', timeout=10)

Nota:

timeoutnão é um limite de tempo para o download completo da resposta; em vez disso, exceptionserá gerado se o servidor não emitir uma resposta por segundos de tempo limite (mais precisamente, se nenhum bytes tiver sido recebido no soquete subjacente por segundos de tempo limite). Se nenhum tempo limite for especificado explicitamente, as solicitações não atingirão o tempo limite.

CONvid19
fonte
Qual versão das solicitações possui o novo parâmetro de tempo limite?
Rusty
1
Parece ser desde a versão 2.4.0: Suporte para tempos limite de conexão! O tempo limite agora aceita uma tupla (conexão, leitura) que é usada para definir tempos limite de conexão e leitura individuais . pypi.org/project/requests/2.4.0
CONvid19
23

Para criar um tempo limite, você pode usar sinais .

A melhor maneira de resolver esse caso é provavelmente

  1. Defina uma exceção como manipulador para o sinal de alarme
  2. Ligue para o sinal de alarme com um atraso de dez segundos
  3. Chame a função dentro de um try-except-finallybloco.
  4. O bloco de exceção é alcançado se a função expirar.
  5. No bloco final, você cancela o alarme, para que não seja chamado mais tarde.

Aqui está um exemplo de código:

import signal
from time import sleep

class TimeoutException(Exception):
    """ Simple Exception to be called on timeouts. """
    pass

def _timeout(signum, frame):
    """ Raise an TimeoutException.

    This is intended for use as a signal handler.
    The signum and frame arguments passed to this are ignored.

    """
    # Raise TimeoutException with system default timeout message
    raise TimeoutException()

# Set the handler for the SIGALRM signal:
signal.signal(signal.SIGALRM, _timeout)
# Send the SIGALRM signal in 10 seconds:
signal.alarm(10)

try:    
    # Do our code:
    print('This will take 11 seconds...')
    sleep(11)
    print('done!')
except TimeoutException:
    print('It timed out!')
finally:
    # Abort the sending of the SIGALRM signal:
    signal.alarm(0)

Existem algumas advertências para isso:

  1. Não é seguro para threads, os sinais são sempre entregues ao thread principal, portanto você não pode colocar isso em nenhum outro thread.
  2. Há um pequeno atraso após o agendamento do sinal e a execução do código real. Isso significa que o exemplo expiraria, mesmo que dormisse apenas por dez segundos.

Mas, está tudo na biblioteca python padrão! Exceto pela importação da função sleep, é apenas uma importação. Se você vai usar timeouts em muitos lugares, você pode facilmente colocar TimeoutException, _timeout e singaling em uma função e apenas chamar isso. Ou você pode criar um decorador e colocá-lo em funções, veja a resposta abaixo.

Você também pode configurá-lo como um "gerenciador de contexto" para poder usá-lo com a withinstrução:

import signal
class Timeout():
    """ Timeout for use with the `with` statement. """

    class TimeoutException(Exception):
        """ Simple Exception to be called on timeouts. """
        pass

    def _timeout(signum, frame):
        """ Raise an TimeoutException.

        This is intended for use as a signal handler.
        The signum and frame arguments passed to this are ignored.

        """
        raise Timeout.TimeoutException()

    def __init__(self, timeout=10):
        self.timeout = timeout
        signal.signal(signal.SIGALRM, Timeout._timeout)

    def __enter__(self):
        signal.alarm(self.timeout)

    def __exit__(self, exc_type, exc_value, traceback):
        signal.alarm(0)
        return exc_type is Timeout.TimeoutException

# Demonstration:
from time import sleep

print('This is going to take maximum 10 seconds...')
with Timeout(10):
    sleep(15)
    print('No timeout?')
print('Done')

Uma possível desvantagem dessa abordagem do gerenciador de contexto é que você não pode saber se o código realmente expirou ou não.

Fontes e leitura recomendada:

totokaka
fonte
3
Os sinais são entregues apenas no thread principal, portanto, definitivamente não funcionará em outros threads, provavelmente não .
Dima Tisnek
1
O pacote timeout-decorator fornece um decorador de tempo limite que usa sinais (ou opcionalmente multiprocessamento).
Christian Long
13

Tente esta solicitação com tempo limite e tratamento de erros:

import requests
try: 
    url = "http://google.com"
    r = requests.get(url, timeout=10)
except requests.exceptions.Timeout as e: 
    print e
DaWe
fonte
5

Definir stream=Truee usar r.iter_content(1024). Sim, de eventlet.Timeoutalguma forma, não funciona para mim.

try:
    start = time()
    timeout = 5
    with get(config['source']['online'], stream=True, timeout=timeout) as r:
        r.raise_for_status()
        content = bytes()
        content_gen = r.iter_content(1024)
        while True:
            if time()-start > timeout:
                raise TimeoutError('Time out! ({} seconds)'.format(timeout))
            try:
                content += next(content_gen)
            except StopIteration:
                break
        data = content.decode().split('\n')
        if len(data) in [0, 1]:
            raise ValueError('Bad requests data')
except (exceptions.RequestException, ValueError, IndexError, KeyboardInterrupt,
        TimeoutError) as e:
    print(e)
    with open(config['source']['local']) as f:
        data = [line.strip() for line in f.readlines()]

A discussão está aqui https://redd.it/80kp1h

Polv
fonte
É um pedido vergonha não suporta parâmetros MaxTime, esta solução é a única trabalhou com asyncio
Wukong
4

Isso pode ser um exagero, mas a fila de tarefas distribuídas do Celery tem um bom suporte para tempos limite.

Em particular, você pode definir um limite de tempo flexível que apenas gera uma exceção em seu processo (para que você possa limpar) e / ou um limite de tempo difícil que encerra a tarefa quando o limite de tempo é excedido.

Nos bastidores, isso usa a mesma abordagem de sinais como mencionado na sua postagem "antes", mas de uma maneira mais utilizável e gerenciável. E se a lista de sites que você está monitorando for longa, você poderá se beneficiar do seu principal recurso - todos os tipos de maneiras de gerenciar a execução de um grande número de tarefas.

Chris Johnson
fonte
Esta poderia ser uma boa solução. O problema do tempo limite total não está diretamente relacionado a ( python-requestsmas httplibusado por solicitações do Python 2.7). O pacote transmite tudo relacionado timeoutdiretamente ao httplib. Eu acho que nada pode ser corrigido no pedido, porque o processo pode permanecer por muito tempo no httplib.
Hynekcer
@ hynekcer, acho que você está certo. É por isso que detectar timeouts fora de processo e fazer cumprir com a eliminação limpa de processos, como o Celery, pode ser uma boa abordagem.
Chris Johnson
3

Eu acredito que você pode usar multiprocessinge não depender de um pacote de terceiros:

import multiprocessing
import requests

def call_with_timeout(func, args, kwargs, timeout):
    manager = multiprocessing.Manager()
    return_dict = manager.dict()

    # define a wrapper of `return_dict` to store the result.
    def function(return_dict):
        return_dict['value'] = func(*args, **kwargs)

    p = multiprocessing.Process(target=function, args=(return_dict,))
    p.start()

    # Force a max. `timeout` or wait for the process to finish
    p.join(timeout)

    # If thread is still active, it didn't finish: raise TimeoutError
    if p.is_alive():
        p.terminate()
        p.join()
        raise TimeoutError
    else:
        return return_dict['value']

call_with_timeout(requests.get, args=(url,), kwargs={'timeout': 10}, timeout=60)

O tempo limite passado kwargsé o tempo limite para obter qualquer resposta do servidor, o argumento timeouté o tempo limite para obter a resposta completa .

Jorge Leitao
fonte
Isso pode ser aprimorado com uma tentativa / exceção genérica na função privada que captura todos os erros e os coloca em return_dict ['error']. Então, no final, antes de retornar, verifique se 'error' em return_dict e, em seguida, aumente. Torna muito mais fácil testar também.
dialt0ne 12/05
2

timeout = (tempo limite de conexão, tempo limite de leitura de dados) ou fornecer um único argumento (tempo limite = 1)

import requests

try:
    req = requests.request('GET', 'https://www.google.com',timeout=(1,1))
    print(req)
except requests.ReadTimeout:
    print("READ TIME OUT")
Fayzan qureshi
fonte
1

este código funcionando para socketError 11004 e 10060 ......

# -*- encoding:UTF-8 -*-
__author__ = 'ACE'
import requests
from PyQt4.QtCore import *
from PyQt4.QtGui import *


class TimeOutModel(QThread):
    Existed = pyqtSignal(bool)
    TimeOut = pyqtSignal()

    def __init__(self, fun, timeout=500, parent=None):
        """
        @param fun: function or lambda
        @param timeout: ms
        """
        super(TimeOutModel, self).__init__(parent)
        self.fun = fun

        self.timeer = QTimer(self)
        self.timeer.setInterval(timeout)
        self.timeer.timeout.connect(self.time_timeout)
        self.Existed.connect(self.timeer.stop)
        self.timeer.start()

        self.setTerminationEnabled(True)

    def time_timeout(self):
        self.timeer.stop()
        self.TimeOut.emit()
        self.quit()
        self.terminate()

    def run(self):
        self.fun()


bb = lambda: requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip")

a = QApplication([])

z = TimeOutModel(bb, 500)
print 'timeout'

a.exec_()
ACEE
fonte
Promovendo a criatividade
JSmyth 3/16/16
1

Apesar da pergunta ser sobre solicitações, acho isso muito fácil de fazer com o pycurl CURLOPT_TIMEOUT ou CURLOPT_TIMEOUT_MS.

Não é necessário rosquear ou sinalizar:

import pycurl
import StringIO

url = 'http://www.example.com/example.zip'
timeout_ms = 1000
raw = StringIO.StringIO()
c = pycurl.Curl()
c.setopt(pycurl.TIMEOUT_MS, timeout_ms)  # total timeout in milliseconds
c.setopt(pycurl.WRITEFUNCTION, raw.write)
c.setopt(pycurl.NOSIGNAL, 1)
c.setopt(pycurl.URL, url)
c.setopt(pycurl.HTTPGET, 1)
try:
    c.perform()
except pycurl.error:
    traceback.print_exc() # error generated on timeout
    pass # or just pass if you don't want to print the error
John Smith
fonte
1

Caso esteja usando a opção, stream=Truevocê pode fazer o seguinte:

r = requests.get(
    'http://url_to_large_file',
    timeout=1,  # relevant only for underlying socket
    stream=True)

with open('/tmp/out_file.txt'), 'wb') as f:
    start_time = time.time()
    for chunk in r.iter_content(chunk_size=1024):
        if chunk:  # filter out keep-alive new chunks
            f.write(chunk)
        if time.time() - start_time > 8:
            raise Exception('Request took longer than 8s')

A solução não precisa de sinais ou multiprocessamento.

ub_marco
fonte
1

Apenas mais uma solução (obtida em http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Antes do upload, você pode descobrir o tamanho do conteúdo:

TOO_LONG = 10*1024*1024  # 10 Mb
big_url = "http://ipv4.download.thinkbroadband.com/1GB.zip"
r = requests.get(big_url, stream=True)
print (r.headers['content-length'])
# 1073741824  

if int(r.headers['content-length']) < TOO_LONG:
    # upload content:
    content = r.content

Mas tenha cuidado, um remetente pode configurar um valor incorreto no campo de resposta 'comprimento do conteúdo'.

Denis Kuzin
fonte
Obrigado. Solução limpa e simples. Funciona para mim.
petezurich 22/06/19
0

Se for o caso, crie um thread de watchdog que atrapalhe o estado interno das solicitações após 10 segundos, por exemplo:

  • fecha o soquete subjacente e, idealmente,
  • aciona uma exceção se os pedidos tentarem novamente a operação

Observe que, dependendo das bibliotecas do sistema, talvez você não consiga definir o prazo na resolução do DNS.

Dima Tisnek
fonte
0

Bem, tentei muitas soluções nesta página e ainda enfrentava instabilidades, interrupções aleatórias, baixo desempenho das conexões.

Agora estou usando o Curl e estou muito feliz com a funcionalidade de "tempo máximo" e com as performances globais, mesmo com uma implementação tão ruim:

content=commands.getoutput('curl -m6 -Ss "http://mywebsite.xyz"')

Aqui, eu defini um parâmetro de tempo máximo de 6 segundos, englobando o tempo de conexão e transferência.

Tenho certeza que o Curl tem uma boa ligação python, se você preferir manter a sintaxe python :)

técnico
fonte
0

Existe um pacote chamado timeout-decorator que você pode usar para atingir o tempo limite de qualquer função python.

@timeout_decorator.timeout(5)
def mytest():
    print("Start")
    for i in range(1,10):
        time.sleep(1)
        print("{} seconds have passed".format(i))

Ele usa a abordagem de sinais sugerida por algumas respostas. Como alternativa, você pode dizer para usar o multiprocessamento em vez de sinais (por exemplo, se você estiver em um ambiente com vários threads).

Christian Long
fonte
0

Estou usando as solicitações 2.2.1 e o eventlet não funcionou para mim. Em vez disso, consegui usar o tempo limite do gevent, pois o gevent é usado no meu serviço para o gunicorn.

import gevent
import gevent.monkey
gevent.monkey.patch_all(subprocess=True)
try:
    with gevent.Timeout(5):
        ret = requests.get(url)
        print ret.status_code, ret.content
except gevent.timeout.Timeout as e:
    print "timeout: {}".format(e.message)

Observe que gevent.timeout.Timeout não é capturado pelo tratamento geral de exceções. Portanto, capture gevent.timeout.Timeout ou passe explicitamente uma exceção diferente para ser usada da seguinte maneira: with gevent.Timeout(5, requests.exceptions.Timeout):embora nenhuma mensagem seja passada quando essa exceção for gerada.

xsdf
fonte
-1

Eu vim com uma solução mais direta que é reconhecidamente feia, mas resolve o problema real. É mais ou menos assim:

resp = requests.get(some_url, stream=True)
resp.raw._fp.fp._sock.settimeout(read_timeout)
# This will load the entire response even though stream is set
content = resp.content

Você pode ler a explicação completa aqui

Realista
fonte