Hết thời gian cho các yêu cầu python. Quên toàn bộ phản hồi


169

Tôi đang thu thập số liệu thống kê trên một danh sách các trang web và tôi đang sử dụng các yêu cầu cho đơn giản. Đây là mã của tôi:

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

Bây giờ, tôi muốn requests.gethết thời gian chờ sau 10 giây để vòng lặp không bị kẹt.

Câu hỏi này đã được quan tâm trước đây nhưng không có câu trả lời nào trong sạch. Tôi sẽ đặt một số tiền thưởng vào việc này để có được một câu trả lời hay.

Tôi nghe rằng có thể không sử dụng các yêu cầu là một ý tưởng tốt nhưng sau đó làm thế nào để tôi nhận được những điều tốt đẹp yêu cầu cung cấp. (những người trong tuple)


1
Những loại câu trả lời bạn đang tìm kiếm? (hoặc, nói cách khác, tại sao các câu trả lời hiện tại không đủ cho bạn?)
yuvi

Chúng ta đang trong thời kỳ ân hạn của tiền thưởng. Thời gian để chọn một câu trả lời?
totokaka

Tôi vẫn đang quyết định giữa các giải pháp và tín hiệu. Tôi sẽ trao giải cho câu hỏi vào tối nay.
Kiarash


Câu trả lời:


138

Còn việc sử dụng eventlet thì sao? Nếu bạn muốn hết thời gian yêu cầu sau 10 giây, ngay cả khi dữ liệu được nhận, đoạn mã này sẽ hoạt động cho bạn:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)

114
Chắc chắn điều này là phức tạp không cần thiết.
Holdenweb

7
Cảm ơn bạn. Bây giờ tôi hiểu ưu thế kỹ thuật của giải pháp của bạn (mà bạn đã nêu khá ngắn gọn khi bắt đầu câu trả lời của bạn) và nâng cao nó. Vấn đề với các mô-đun của bên thứ ba không phải là nhập chúng mà là đảm bảo chúng được nhập vào, do đó tôi thích sử dụng thư viện chuẩn nếu có thể.
Holdenweb

9
eventlet.monkey_patch()bắt buộc không?
Người dùng

3
Có, socketmô-đun cần được vá khỉ, vì vậy ít nhất bạn sẽ cần mộteventlet.monkey_patch(socket=True)
Alvaro

51
Tính đến năm 2018 câu trả lời này đã lỗi thời. Sử dụngrequests.get('https://github.com', timeout=5)
CONvid19

312

Đặt tham số thời gian chờ :

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

Miễn là bạn không đặt stream=Truetheo yêu cầu đó, điều này sẽ khiến cuộc gọi requests.get()hết thời gian chờ nếu kết nối mất hơn mười giây hoặc nếu máy chủ không gửi dữ liệu trong hơn mười giây.


31
Đó không phải là cho toàn bộ phản ứng. requests.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash

1
Vâng, đó là, trong một số trường hợp. Một trong những trường hợp đó là của bạn. =) Tôi mời bạn xem mã nếu bạn không bị thuyết phục.
Lukasa

hoàn cảnh ra sao
Kiarash

1
Tôi vừa kiểm tra cái này và nó không bao giờ dừng lại: r = request.get (' ipv4.doad.thinkbroadband.com/1GB.zip ', timeout = 20)
Kiarash

5
À, xin lỗi, tôi đã hiểu nhầm ý của bạn khi bạn nói 'toàn bộ câu trả lời'. Vâng, bạn đã đúng: đó không phải là giới hạn trên của tổng thời gian chờ đợi.
Lukasa

85

CẬP NHẬT: https://requests.readthedocs.io/en/master/user/advified/#timeouts

Trong phiên bản mới của requests:

Nếu bạn chỉ định một giá trị cho thời gian chờ, như thế này:

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

Giá trị thời gian chờ sẽ được áp dụng cho cả thời gian chờ connectreadthời gian chờ. Chỉ định một tuple nếu bạn muốn đặt các giá trị riêng biệt:

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

Nếu máy chủ từ xa rất chậm, bạn có thể yêu cầu Yêu cầu chờ phản hồi mãi mãi, bằng cách chuyển Không có giá trị hết thời gian chờ và sau đó lấy một tách cà phê.

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

Câu trả lời cũ (có lẽ đã lỗi thời) của tôi (đã được đăng từ lâu):

Có nhiều cách khác để khắc phục vấn đề này:

1. Sử dụng TimeoutSaucelớp nội bộ

Từ: 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

Mã này sẽ khiến chúng tôi đặt thời gian chờ đọc bằng với thời gian chờ kết nối, là giá trị hết thời gian bạn chuyển qua cuộc gọi Session.get () của bạn. (Lưu ý rằng tôi chưa thực sự kiểm tra mã này, vì vậy nó có thể cần gỡ lỗi nhanh, tôi chỉ cần viết thẳng vào cửa sổ GitHub.)

2. Sử dụng một nhánh yêu cầu từ kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Từ tài liệu của nó: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advified.rst

Nếu bạn chỉ định một giá trị cho thời gian chờ, như thế này:

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

Giá trị thời gian chờ sẽ được áp dụng cho cả thời gian chờ kết nối và thời gian đọc. Chỉ định một tuple nếu bạn muốn đặt các giá trị riêng biệt:

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

kevinburke đã yêu cầu sáp nhập vào dự án yêu cầu chính, nhưng nó chưa được chấp nhận.


tùy chọn 1 không hoạt động. nếu bạn tiếp tục đọc chủ đề đó, những người khác đã nói "điều này sẽ không hoạt động cho trường hợp sử dụng của bạn, tôi sợ. Chức năng hết thời gian đọc nằm trong phạm vi của một cuộc gọi recv () socket riêng lẻ, vì vậy nếu máy chủ dừng gửi dữ liệu nhiều hơn thời gian chờ đọc, chúng tôi sẽ hủy bỏ. "
Kiarash

Có một giải pháp hay khác trong luồng đó là sử dụng Signal, nó cũng không hoạt động với tôi, vì tôi chỉ sử dụng Windows và signal.alarm chỉ là linux.
Kiarash

@Kiarash Tôi chưa thử nó. Tuy nhiên, như tôi hiểu khi Lukasa nói this won't work for you use-case. Anh ta có nghĩa là nó không hoạt động với dòng mp3 mà anh chàng kia muốn.
Hiếu

1
@Hieu - điều này đã được hợp nhất trong một yêu cầu kéo khác - github.com/kennethreitz/requests/pull/
mẹo

thời gian chờ = Không có gì là không chặn cuộc gọi.
crazydan

49

timeout = int(seconds)

requests >= 2.4.0, bạn có thể sử dụng timeoutđối số, nghĩa là:

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

Ghi chú:

timeoutkhông phải là giới hạn thời gian trên toàn bộ tải xuống phản hồi; thay vào đó, một exceptionđược nâng lên nếu máy chủ không đưa ra phản hồi cho thời gian chờ giây (chính xác hơn, nếu không có byte nào được nhận trên ổ cắm bên dưới trong thời gian chờ giây). Nếu không có thời gian chờ được chỉ định rõ ràng, yêu cầu không hết thời gian.


Phiên bản yêu cầu nào có tham số thời gian chờ mới?
Rusty

1
Có vẻ là từ phiên bản 2.4.0: Hỗ trợ cho thời gian chờ kết nối! Hết giờ giờ chấp nhận một tuple (kết nối, đọc) được sử dụng để đặt thời gian chờ kết nối và đọc riêng lẻ . pypi.org/project/requests/2.4.0
CONvid19

23

Để tạo thời gian chờ, bạn có thể sử dụng tín hiệu .

Cách tốt nhất để giải quyết trường hợp này có lẽ là

  1. Đặt ngoại lệ làm trình xử lý cho tín hiệu báo động
  2. Gọi tín hiệu báo động với độ trễ mười giây
  3. Gọi hàm bên trong một try-except-finallykhối.
  4. Khối ngoại trừ đạt được nếu chức năng hết thời gian.
  5. Trong khối cuối cùng, bạn hủy bỏ báo thức, vì vậy nó sẽ không được hát sau đó.

Dưới đây là một số mã ví dụ:

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)

Có một số cảnh báo về điều này:

  1. Nó không phải là luồng an toàn, tín hiệu luôn được gửi đến luồng chính, vì vậy bạn không thể đặt luồng này vào bất kỳ luồng nào khác.
  2. Có một chút chậm trễ sau khi lập lịch tín hiệu và thực thi mã thực tế. Điều này có nghĩa là ví dụ sẽ hết thời gian ngay cả khi nó chỉ ngủ trong mười giây.

Nhưng, đó là tất cả trong thư viện python tiêu chuẩn! Ngoại trừ chức năng ngủ, chỉ nhập một lần. Nếu bạn sẽ sử dụng thời gian chờ nhiều nơi, bạn có thể dễ dàng đặt TimeoutException, _timeout và singelling trong một hàm và chỉ cần gọi nó. Hoặc bạn có thể tạo một trang trí và đưa nó vào các chức năng, xem câu trả lời được liên kết dưới đây.

Bạn cũng có thể thiết lập điều này như một "trình quản lý bối cảnh" để bạn có thể sử dụng nó với withcâu lệnh:

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

Một nhược điểm có thể xảy ra với phương pháp quản lý bối cảnh này là bạn không thể biết liệu mã có thực sự hết thời gian hay không.

Nguồn và đề nghị đọc:


3
Tín hiệu được chỉ giao trong các chủ đề chính, do đó nó defnitely sẽ không làm việc trong các chủ đề khác, không lẽ .
Dima Tisnek

1
Gói thời gian chờ trang trí cung cấp một trang trí hết thời gian sử dụng tín hiệu (hoặc tùy chọn đa xử lý).
Christian Long

13

Hãy thử yêu cầu này với thời gian chờ và xử lý lỗi:

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

5

Đặt stream=Truevà sử dụng r.iter_content(1024). Vâng, eventlet.Timeoutchỉ bằng cách nào đó không làm việc cho tôi.

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

Cuộc thảo luận ở đây https://redd.it/80kp1h


đó là một yêu cầu xấu hổ không hỗ trợ các thông số tối đa, giải pháp này là giải pháp duy nhất hoạt động với asyncio
wukong

4

Điều này có thể là quá mức cần thiết, nhưng hàng đợi nhiệm vụ phân tán Celery có hỗ trợ tốt cho thời gian chờ.

Cụ thể, bạn có thể xác định giới hạn thời gian mềm làm tăng ngoại lệ trong quy trình của mình (để bạn có thể dọn sạch) và / hoặc giới hạn thời gian cứng chấm dứt tác vụ khi vượt quá giới hạn thời gian.

Dưới vỏ bọc, điều này sử dụng cách tiếp cận tín hiệu tương tự như được tham chiếu trong bài đăng "trước" của bạn, nhưng theo cách dễ sử dụng và dễ quản lý hơn. Và nếu danh sách các trang web bạn đang theo dõi dài, bạn có thể được hưởng lợi từ tính năng chính của nó - tất cả các cách để quản lý việc thực hiện một số lượng lớn các tác vụ.


Đây có thể là một giải pháp tốt. Vấn đề về tổng thời gian chờ không liên quan trực tiếp đến python-requestsnhưng httplib(được sử dụng bởi các yêu cầu cho Python 2.7). Gói này vượt qua mọi thứ liên quan timeouttrực tiếp đến omeplib. Tôi nghĩ rằng không có gì có thể được sửa chữa trong yêu cầu bởi vì quá trình có thể tồn tại lâu dài trong omeplib.
hynekcer

@hynekcer, tôi nghĩ bạn nói đúng. Đây là lý do tại sao phát hiện hết thời gian chờ xử lý và thực thi bằng cách giết sạch các quy trình, như Celery, có thể là một cách tiếp cận tốt.
Chris Johnson

3

Tôi tin rằng bạn có thể sử dụng multiprocessingvà không phụ thuộc vào gói bên thứ 3:

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)

Thời gian chờ đã qua kwargslà thời gian chờ để nhận bất kỳ phản hồi nào từ máy chủ, đối số timeoutlà thời gian chờ để nhận được phản hồi hoàn chỉnh .


Điều này có thể được cải thiện với một lần thử chung / ngoại trừ trong chức năng riêng tư bắt được tất cả các lỗi và đặt chúng trong return_dict ['error']. Sau đó, ở cuối, trước khi quay lại, hãy kiểm tra xem 'error' trong return_dict và sau đó nâng nó lên. Nó làm cho nó dễ dàng hơn để kiểm tra là tốt.
dialt0ne

2

thời gian chờ = (hết thời gian kết nối, hết thời gian đọc dữ liệu) hoặc đưa ra một đối số (thời gian chờ = 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

mã này hoạt động cho socketError 11004 và 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_()

Nâng cao khả năng sáng tạo
JSmyth

1

Mặc dù câu hỏi liên quan đến các yêu cầu, tôi thấy điều này rất dễ thực hiện với pycurl CURLOPT_TIMEOUT hoặc CURLOPT_TIMEOUT_MS.

Không có luồng hoặc tín hiệu cần thiết:

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

Trong trường hợp bạn đang sử dụng tùy chọn, stream=Truebạn có thể làm điều này:

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

Giải pháp không cần tín hiệu hoặc đa xử lý.


1

Chỉ là một giải pháp khác (đã nhận nó từ http://docs.python-requests.org/en/master/user/advified/#streaming-uploads )

Trước khi tải lên, bạn có thể tìm hiểu kích thước nội dung:

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

Nhưng hãy cẩn thận, người gửi có thể thiết lập giá trị không chính xác trong trường phản hồi 'độ dài nội dung'.


Cảm ơn. Giải pháp sạch và đơn giản. Làm việc cho tôi.
petezurich

0

Nếu nói đến điều đó, hãy tạo một chuỗi cơ quan giám sát làm rối loạn trạng thái bên trong của yêu cầu sau 10 giây, ví dụ:

  • đóng ổ cắm bên dưới và lý tưởng nhất
  • kích hoạt một ngoại lệ nếu các yêu cầu thử lại hoạt động

Lưu ý rằng tùy thuộc vào thư viện hệ thống, bạn có thể không thể đặt thời hạn cho độ phân giải DNS.


0

Vâng, tôi đã thử nhiều giải pháp trên trang này và vẫn gặp phải sự bất ổn, treo ngẫu nhiên, hiệu suất kết nối kém.

Bây giờ tôi đang sử dụng Curl và tôi thực sự hài lòng về chức năng "thời gian tối đa" và về các màn trình diễn toàn cầu, ngay cả với việc triển khai kém như vậy:

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

Ở đây, tôi đã xác định tham số thời gian tối đa 6 giây, bao gồm cả kết nối và thời gian truyền.

Tôi chắc chắn Curl có một ràng buộc python đẹp, nếu bạn thích bám theo cú pháp pythonic :)


0

Có một gói được gọi là timeout-decorator mà bạn có thể sử dụng để hết thời gian cho bất kỳ chức năng python nào.

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

Nó sử dụng cách tiếp cận tín hiệu mà một số câu trả lời ở đây gợi ý. Ngoài ra, bạn có thể yêu cầu nó sử dụng đa xử lý thay vì tín hiệu (ví dụ: nếu bạn đang ở trong môi trường đa luồng).


0

Tôi đang sử dụng các yêu cầu 2.2.1 và eventlet không hoạt động với tôi. Thay vào đó tôi đã có thể sử dụng thời gian chờ thay vì gevent được sử dụng trong dịch vụ của tôi cho 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)

Xin lưu ý rằng gevent.timeout.Timeout không bị bắt bởi xử lý Ngoại lệ chung. Vì vậy, hoặc bắt gevent.timeout.Timeout hoặc chuyển một cách rõ ràng trong một ngoại lệ khác sẽ được sử dụng như vậy: with gevent.Timeout(5, requests.exceptions.Timeout):mặc dù không có thông báo nào được thông qua khi ngoại lệ này được đưa ra.


-1

Tôi đã đưa ra một giải pháp trực tiếp hơn được thừa nhận là xấu xí nhưng khắc phục vấn đề thực sự. Nó đi một chút như thế này:

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

Bạn có thể đọc giải thích đầy đủ ở đây


3
1- bởi vì bạn có thể truyền timeouttham sốrequests.get() mà không có cách giải quyết xấu xí 2- mặc dù cả hai sẽ không giới hạn tổng thời gian chờ không giống nhưeventlet.Timeout(10)
jfs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.