Час очікування python request.get весь відповідь


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 секунд, щоб цикл не застряг.

Це питання викликало інтерес раніше, але жодна з відповідей не є чіткою. Я покладу на це щеплення, щоб отримати гарну відповідь.

Я чую, що, можливо, не використовувати запити - це гарна ідея, але тоді, як мені отримати пропозицію про приємні речі. (ті в кортежі)


1
Яку відповідь ви шукаєте? (або, інакше кажучи, чому поточних відповідей вам недостатньо?)
yuvi

Ми в пільговому періоді щедрості. Час вибрати відповідь?
тотокака

Я все ще приймаю рішення між рішенням 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)

114
Звичайно, це зайво складно.
holdenweb

7
Дякую. Тепер я розумію технічну перевагу вашого рішення (про що ви сказали доволі лаконічно на початку своєї відповіді) і підтримую це. Проблема із сторонніми модулями полягає не в тому, що вони імпортують, а забезпечують їх імпорт, тому я мою перевагу використовувати стандартну бібліотеку, де це можливо.
holdenweb

9
Є чи eventlet.monkey_patch()потрібно?
Користувач

3
Так, socketмодуль потрібно зафіксувати мавпою, так що принаймні вам знадобитьсяeventlet.monkey_patch(socket=True)
Альваро

52
Станом на 2018 рік ця відповідь застаріла. Використанняrequests.get('https://github.com', timeout=5)
CONvid19

313

Встановіть параметр тайм-ауту :

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 = questions.get (' ipv4.download.thinkbroadband.com/1GB.zip ', timeout = 20)
Kiarash

5
Ах, вибачте, я неправильно зрозумів, що ви мали на увазі, коли ви сказали "всю відповідь". Так, ви праві: це не верхня межа загальної кількості часу на очікування.
Лукаша

85

ОНОВЛЕННЯ: 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))

Якщо віддалений сервер дуже повільний, ви можете сказати Запити, щоб назавжди почекати відповіді, передавши None як значення тайм-ауту, а потім отримавши чашку кави.

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

Моя стара (ймовірно, застаріла) відповідь (яка була розміщена давно):

Є й інші способи подолати цю проблему:

1. Використовуйте TimeoutSauceвнутрішній клас

Від: https://github.com/kennethreitz/requests/isissue/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 не працює. якщо ви продовжуєте читати цю нитку, інші люди сказали: "Це не працюватиме у вашому випадку використання, я боюся. Функція очікування зчитування знаходиться в межах індивідуального виклику sov 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піднімається, якщо сервер не видав відповідь за час очікування секунд (точніше, якщо жодних байтів не було отримано в базовому сокеті за час очікування секунд). Якщо явний час не вказано, запити не вичерпуються.


Яка версія запитів має новий параметр очікування?
Іржавий

1
Здається, це версія версії 2.4.0: Підтримка таймаутів підключення! Тепер час очікування приймає кортеж (підключення, зчитування), який використовується для встановлення індивідуальних тайм-аутів підключення та зчитування . pypi.org/project/requests/2.4.0
CONvid19

23

Для створення тайм-ауту ви можете використовувати сигнали .

Мабуть, найкращий спосіб вирішити цю справу

  1. Встановіть виняток як обробник сигналу тривоги
  2. Викличте сигнал тривоги із затримкою на десять секунд
  3. Викличте функцію всередині try-except-finallyблоку.
  4. За винятком блоку, якщо функція вичерпана.
  5. У останньому блоці ви скасовуєте сигнал тривоги, тому згодом це не буде позначено.

Ось приклад коду:

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. Існує невелика затримка після планування сигналу та виконання фактичного коду. Це означає, що приклад затягував би час, навіть якби він проспав лише десять секунд.

Але все це у стандартній бібліотеці пітонів! За винятком імпорту функції сну, це лише один імпорт. Якщо ви збираєтеся використовувати тайм-аути у багатьох місцях, ви можете легко поставити функцію TimeoutException, _timeout та синглінг у функцію та просто зателефонувати їй. Або ви можете зробити декоратор і поставити його на функції, дивіться відповідь, пов’язану нижче.

Ви також можете встановити це як "менеджер контексту", щоб ви могли використовувати його з 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')

Один із можливих недоліків цього підходу до менеджера контекстів - це те, що ви не можете знати, чи дійсно код вийшов із часу чи ні.

Джерела та рекомендоване читання:


3
Сигнали доставляються лише в основний потік, таким чином, він, напевно , не працюватиме в інших потоках , не ймовірно .
Діма Тиснек

1
Пакет time-decorator забезпечує декоратор тайм-аута, який використовує сигнали (або необов'язково багатопроцесорні).
Крістіан Лонг

13

Спробуйте цей запит з таймаутом та помилками:

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

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


Прохання про сором не підтримує параметри часу, це рішення єдине, що працювало з asyncio
wukong

4

Це може бути надмірним, але в черзі завдань з розподіленою селерою є хороша підтримка таймаутів.

Зокрема, ви можете визначити м'який часовий ліміт, який просто збільшує виняток у вашому процесі (таким чином ви можете очистити) та / або жорсткий обмеження часу, яке припиняє завдання, коли перевищено обмеження часу.

Під обкладинками це використовує той самий підхід до сигналів, про який йдеться у вашій посаді "до", але більш зручним та керованим способом. І якщо список веб-сайтів, які ви відстежуєте, довгий, ви можете скористатися його основною особливістю - всілякими способами управління виконанням великої кількості завдань.


Це може бути хорошим рішенням. Проблема загального тайм-ауту не пов'язана безпосередньо з, python-requestsа з httplib(використовується запитами на Python 2.7). Пакет передає все, що стосується timeoutбезпосередньо httpsb. Я думаю, що нічого не можна виправити в запиті, оскільки процес може тривалий час перебувати в httplib.
hynekcer

@hynekcer, я думаю, ти маєш рацію. Ось чому виявлення тайм-аутів поза процесом та примусове виконання шляхом чистого вбивства процесів, як це робить Селера, може бути хорошим підходом.
Кріс Джонсон

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є час очікування для отримання повної відповіді.


Це можна покращити за допомогою загальної спроби / за винятком приватної функції, яка фіксує всі помилки та ставить їх у return_dict ['помилка']. Потім наприкінці, перед поверненням, перевірте, чи є "помилка" у 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_()

Захист до творчості
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')

Рішення не потребує сигналів або багатопроцесорної обробки.


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, і я дуже радий, що це "max time" функціональність та глобальні виступи, навіть при такій поганій реалізації:

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

Тут я визначив максимальний параметр часу в 6 секунд, що охоплює і з'єднання, і час передачі.

Я впевнений, що Curl має приємне зв'язування пітона, якщо ви віддаєте перевагу дотримуватися пітонічного синтаксису :)


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 timeout, оскільки gevent використовується в моїй службі для гармати.

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):хоча жодне повідомлення не передається при підвищенні цього винятку.


-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

Повне пояснення ви можете прочитати тут


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.