Тайм-аут для запросов Python. Получить весь ответ

169

Я собираю статистику по списку сайтов и использую запросы для простоты. Вот мой код:

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

Теперь я хочу requests.getсделать тайм-аут через 10 секунд, чтобы цикл не застрял.

Этот вопрос был интересен , прежде чем тоже , но ни один из ответов не являются чистыми. Я возложу на это награду, чтобы получить хороший ответ.

Я слышал, что, возможно, не использовать запросы - это хорошая идея, но тогда как мне получить хорошие предложения запросов? (те, в кортеже)

Kiarash
источник
1
Какой ответ вы ищете? (или, другими словами, почему вам не хватает текущих ответов?)
yuvi
Мы находимся в льготном периоде щедрости. Время выбрать ответ?
Totokaka
Я все еще выбираю между решением Eventlet и сигналами. Я передам вопрос сегодня вечером.
Киараш

Ответы:

137

Как насчет использования eventlet? Если вы хотите тайм-аут запроса через 10 секунд, даже если данные получены, этот фрагмент кода будет работать для вас:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)
Alvaro
источник
115
Конечно, это излишне сложно.
Holdenweb
7
Спасибо. Теперь я понимаю техническое превосходство вашего решения (о котором вы довольно кратко заявили в начале своего ответа) и проголосовало за него. Проблема со сторонними модулями заключается не в их импорте, а в том, что они должны быть импортированы, поэтому я предпочитаю использовать стандартную библиотеку, где это возможно.
Holdenweb
9
Является ли eventlet.monkey_patch()требуется?
Пользователь
3
Да, socketмодуль должен быть исправлен обезьяной, так что, по крайней мере, вам понадобитсяeventlet.monkey_patch(socket=True)
Альваро
53
По состоянию на 2018 год этот ответ устарел. Использованиеrequests.get('https://github.com', timeout=5)
CONvid19
315

Установите параметр времени ожидания :

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

Пока вы не настроите stream=Trueэтот запрос, это приведет requests.get()к истечению времени ожидания вызова, если соединение займет более десяти секунд или если сервер не отправляет данные более десяти секунд.

Лукаса
источник
31
Это не для всего ответа. requests.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash
1
Да, это так, в некоторых обстоятельствах. Одно из тех обстоятельств случается с тобой. =) Я приглашаю вас взглянуть на код, если вы не уверены.
Лукаса
каковы обстоятельства?
Киараш
1
Я только что проверил это, и оно никогда не останавливалось: r = запросы.get (' ipv4.download.thinkbroadband.com/1GB.zip ', тайм-аут = 20)
Киараш
5
Ах, извините, я неправильно понял, что вы имели в виду, когда сказали «весь ответ». Да, вы правы: это не верхний предел общего времени ожидания.
Лукаса
86

ОБНОВЛЕНИЕ: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

В новой версии requests:

Если вы указываете одно значение для тайм-аута, например так:

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

Значение тайм-аута будет применено как connectк readтайм-аутам, так и к тайм-аутам. Укажите кортеж, если вы хотите установить значения отдельно:

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

Если удаленный сервер работает очень медленно, вы можете попросить Requests вечно ждать ответа, передав None в качестве значения тайм-аута, а затем получив чашку кофе.

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

Мой старый (вероятно, устаревший) ответ (который был опубликован давно):

Есть и другие способы преодоления этой проблемы:

1. Используйте TimeoutSauceвнутренний класс

От: 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

Этот код должен заставить нас установить тайм-аут чтения равным тайм-ауту соединения, который является значением тайм-аута, которое вы передаете при вызове Session.get (). (Обратите внимание, что я на самом деле не тестировал этот код, поэтому может потребоваться небольшая отладка, я просто написал его прямо в окне GitHub.)

2. Используйте форк запросов от kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Из его документации: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Если вы указываете одно значение для тайм-аута, например так:

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

Значение тайм-аута будет применено как к тайм-аутам подключения, так и к тайм-ауту чтения. Укажите кортеж, если вы хотите установить значения отдельно:

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

kevinburke попросил включить его в проект основных запросов, но он еще не принят.

Хьеу
источник
Вариант 1 не работает. если вы продолжите читать этот поток, другие люди скажут: «Это не сработает для вашего варианта использования, я боюсь. Функция тайм-аута чтения находится в рамках отдельного вызова сокета recv (), так что если сервер прекращает отправку данных на время, превышающее тайм-аут чтения, который мы прервем. "
Киараш
В этом потоке есть еще одно приятное решение с использованием Signal, которое мне тоже не подойдет, потому что я использую Windows, а signal.alarm - только linux.
Киараш
@Kiarash Я еще не проверял это. Однако, как я понимаю, когда сказал Лукаса this won't work for you use-case. Он имел в виду, что он не работает с mp3 потоком, который хочет другой парень.
Hieu
1
@Hieu - это было объединено в запросе другой тяговой - github.com/kennethreitz/requests/pull/...
yprez
timeout = None не блокирует вызов.
Crazydan
49

timeout = int(seconds)

Так как requests >= 2.4.0вы можете использовать timeoutаргумент, а именно:

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

Примечание:

timeoutнет ограничения по времени загрузки всего ответа; скорее, возникает, exceptionесли сервер не выдал ответ в течение секунд ожидания (точнее, если в базовом сокете не было получено ни одного байта в течение секунд ожидания). Если тайм-аут не указан явно, запросы не истекают.

CONvid19
источник
Какая версия запросов имеет новый параметр времени ожидания?
Расти
1
Кажется, начиная с версии 2.4.0: Поддержка тайм-аутов подключения! Timeout теперь принимает кортеж (connect, read), который используется для установки индивидуальных тайм-аутов connect и read . pypi.org/project/requests/2.4.0
CONvid19
23

Для создания тайм-аута вы можете использовать сигналы .

Лучший способ решить это дело, вероятно,

  1. Установить исключение в качестве обработчика для сигнала тревоги
  2. Вызовите сигнал тревоги с задержкой в ​​десять секунд
  3. Вызовите функцию внутри try-except-finallyблока.
  4. Блок исключений достигается, если время ожидания истекло.
  5. В блоке finally вы отменяете сигнал тревоги, поэтому он не выделяется позже.

Вот пример кода:

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)

Есть некоторые оговорки к этому:

  1. Это не потокобезопасно, сигналы всегда доставляются в основной поток, поэтому вы не можете поместить это в любой другой поток.
  2. Существует небольшая задержка после планирования сигнала и выполнения фактического кода. Это означает, что время ожидания для примера истекло, даже если он спал только десять секунд.

Но это все в стандартной библиотеке Python! За исключением импорта функции сна, это только один импорт. Если вы собираетесь использовать тайм-ауты во многих местах, вы можете легко поместить TimeoutException, _timeout и singaling в функцию и просто вызвать это. Или вы можете сделать декоратор и поставить его на функции, см. Ответ ниже.

Вы также можете настроить это как «менеджер контекста», чтобы использовать его с withоператором:

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

Один из возможных недостатков этого подхода к контекстному менеджеру заключается в том, что вы не можете знать, истек ли срок действия кода или нет.

Источники и рекомендуемое чтение:

totokaka
источник
3
Сигналы доставляются только в основном потоке, таким образом, он определенно не будет работать в других потоках, вероятно .
Дима Тиснек
1
Пакет timeout-decorator предоставляет декоратор тайм-аута, который использует сигналы (или, необязательно, многопроцессорную обработку).
Кристиан Лонг
13

Попробуйте этот запрос с тайм-аутом и обработкой ошибок:

import requests
try: 
    url = "http://google.com"
    r = requests.get(url, timeout=10)
except requests.exceptions.Timeout as e: 
    print e
Dawe
источник
5

Установить stream=Trueи использовать r.iter_content(1024). Да, eventlet.Timeoutпросто как-то у меня не работает.

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

Обсуждение здесь https://redd.it/80kp1h

Сополи
источник
это позор, запрос не поддерживает параметры maxtime, это единственное решение, работавшее с asyncio
wukong
4

Это может быть излишним, но очередь распределенных задач Celery имеет хорошую поддержку для тайм-аутов.

В частности, вы можете определить мягкое ограничение по времени, которое просто вызывает исключение в вашем процессе (чтобы вы могли очистить), и / или жесткое ограничение по времени, которое завершает задачу при превышении этого срока.

Под прикрытием здесь используется тот же подход к сигналам, на который ссылается ваша публикация «до», но в более удобной и управляемой форме. И если список веб-сайтов, которые вы отслеживаете, очень длинный, вы можете воспользоваться его основной функцией - всеми видами способов управления выполнением большого количества задач.

Крис Джонсон
источник
Это может быть хорошим решением. Проблема полного тайм-аута не связана напрямую python-requestsс httplib(используется запросами для Python 2.7). Пакет передает все, что связано timeoutнепосредственно с httplib. Я думаю, что ничто не может быть исправлено в запросе, потому что процесс может долго оставаться в httplib.
hynekcer
@hynekcer, я думаю, ты прав. Вот почему обнаружение тайм-аутов вне процесса и принудительное их выполнение путем чистого уничтожения процессов, как это делает Celery, может быть хорошим подходом.
Крис Джонсон
3

Я считаю, что вы можете использовать multiprocessingи не зависеть от стороннего пакета:

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)

Тайм-аут, на который передается kwargsтайм-аут для получения любого ответа от сервера, аргумент timeout- это тайм-аут для получения полного ответа.

Хорхе Лейтао
источник
Это можно улучшить с помощью общей функции try /, за исключением закрытой функции, которая перехватывает все ошибки и помещает их в return_dict ['error']. Затем в конце, перед возвратом, проверьте, есть ли «error» в return_dict, а затем поднимите его. Это также облегчает тестирование.
dialt0ne
2

timeout = (тайм-аут соединения, тайм-аут чтения данных) или указать один аргумент (timeout = 1)

import requests

try:
    req = requests.request('GET', 'https://www.google.com',timeout=(1,1))
    print(req)
except requests.ReadTimeout:
    print("READ TIME OUT")
Файзан куреши
источник
1

этот код работает для socketError 11004 и 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
источник
Голосование за творчество
JSmyth
1

Несмотря на вопрос о запросах, я считаю, что это очень легко сделать с помощью pycurl CURLOPT_TIMEOUT или CURLOPT_TIMEOUT_MS.

Нет потоков или сигнализации требуется:

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
Джон Смит
источник
1

Если вы используете опцию, stream=Trueвы можете сделать это:

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

Решение не требует сигналов или многопроцессорной обработки.

ub_marco
источник
1

Просто еще одно решение (получено от http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Перед загрузкой вы можете узнать размер контента:

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

Но будьте осторожны, отправитель может установить неправильное значение в поле ответа «длина содержимого».

Денис Кузин
источник
Спасибо. Чистое и простое решение. Работает для меня.
petezurich
0

Если дело доходит до этого, создайте сторожевой поток, который через 10 секунд портит внутреннее состояние запросов, например:

  • закрывает нижележащий сокет и в идеале
  • вызывает исключение, если запросы повторяют операцию

Обратите внимание, что в зависимости от системных библиотек вы можете не установить крайний срок разрешения DNS.

Дима Тиснек
источник
0

Ну, я попробовал много решений на этой странице и все еще сталкивался с нестабильностью, случайными зависаниями, низкой производительностью соединений.

Сейчас я использую Curl, и я очень доволен его функциональностью «максимального времени» и глобальными показателями, даже с такой плохой реализацией:

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

Здесь я определил параметр максимального времени 6 секунд, включающий как соединение, так и время передачи.

Я уверен, что у Curl есть хорошая привязка к Python, если вы предпочитаете придерживаться синтаксиса Python :)

технико
источник
0

Существует пакет под названием timeout-decorator, который вы можете использовать для тайм-аута любой функции python.

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

Он использует подход сигналов, что некоторые ответы здесь предлагают. В качестве альтернативы вы можете сказать ему использовать многопроцессорную обработку вместо сигналов (например, если вы находитесь в многопоточном окружении).

Кристиан Лонг
источник
0

Я использую запросы 2.2.1 и eventlet не работает для меня. Вместо этого я смог использовать время ожидания Gevent, поскольку Gevent используется в моем сервисе для 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)

Обратите внимание, что gevent.timeout.Timeout не перехватывается общей обработкой исключений. Так что либо явно перехватите, gevent.timeout.Timeout либо передайте другое исключение для использования следующим образом: with gevent.Timeout(5, requests.exceptions.Timeout):хотя при возникновении этого исключения сообщение не передается.

xsdf
источник
-1

Я придумал более прямое решение, которое, по общему признанию, некрасиво, но исправляет реальную проблему. Это выглядит примерно так:

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

Вы можете прочитать полное объяснение здесь

реалистический
источник
3
1 - потому что вы можете передать timeoutпараметрrequests.get() без отвратительных обходных путей 2 - хотя оба не будут ограничивать общее время ожидания в отличие отeventlet.Timeout(10)
jfs