簡體   English   中英

python請求超時。獲取整個響應

[英]Timeout for python requests.get entire response

我正在收集有關網站列表的統計信息,並且為了簡單起見,我正在使用它的請求。 這是我的代碼:

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 秒后超時,這樣循環就不會卡住。

這個問題以前也很有趣,但沒有一個答案是干凈的。 我會為此付出一些賞金以獲得一個不錯的答案。

我聽說也許不使用 requests 是個好主意,但是我應該如何獲得 requests 提供的好東西。 (元組中的那些)

設置超時參數

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

版本 2.25.1 中的更改

如果讀取之間的連接或延遲時間超過十秒,上面的代碼將導致對requests.get()的調用超時。 請參閱: https ://requests.readthedocs.io/en/stable/user/advanced/#timeouts

使用 eventlet 怎么樣? 如果您想在 10 秒后使請求超時,即使正在接收數據,此代碼段也適合您:

import requests
import eventlet
eventlet.monkey_patch()

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

更新: 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 已請求將其合並到主請求項目中,但尚未被接受。

timeout = int(seconds)

由於requests >= 2.4.0 ,您可以使用timeout參數,即:

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

筆記:

timeout不是整個響應下載的時間限制; 相反,如果服務器在超時秒數內沒有發出響應(更准確地說,如果在超時秒數內底層套接字上沒有收到任何字節),則會引發exception 如果沒有明確指定超時,則請求不會超時。

要創建超時,您可以使用信號

解決這種情況的最好方法可能是

  1. 設置異常作為警報信號的處理程序
  2. 延遲十秒發出警報信號
  3. try-except-finally塊中調用函數。
  4. 如果函數超時,則會到達 except 塊。
  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. 在信號的調度和實際代碼的執行之后會有一點延遲。 這意味着即使該示例只睡了 10 秒,它也會超時。

但是,這一切都在標准的 python 庫中! 除了睡眠功能導入之外,它只是一個導入。 如果您要在很多地方使用超時,您可以輕松地將 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')

這種上下文管理器方法的一個可能缺點是您無法知道代碼是否實際超時。

來源和推薦閱讀:

嘗試使用超時和錯誤處理這個請求:

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

連接超時是請求將等待您的客戶端在套接字上建立與遠程機器的連接(對應於 connect())調用number of seconds 最好將連接超時設置為略大於 3 的倍數,這是默認的 TCP 數據包重傳窗口。

一旦您的客戶端連接到服務器並發送 HTTP 請求,讀取超時就會開始。 它是客戶端等待服務器發送響應的秒數。 (具體來說,它是客戶端在從服務器發送的字節之間等待的秒數。在 99.9% 的情況下,這是服務器發送第一個字節之前的時間)。

如果您為超時指定單個值,則超時值將應用於連接超時和讀取超時。 如下所示:

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

如果您想分別設置連接和讀取的值,請指定一個元組:

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

如果遠程服務器很慢,你可以告訴 Requests 永遠等待響應,通過傳遞 None 作為超時值,然后檢索一杯咖啡。

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

https://docs.python-requests.org/en/latest/user/advanced/#timeouts

設置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

大多數其他答案都不正確

盡管有所有答案,但我相信這個線程仍然缺乏適當的解決方案,並且沒有現有的答案提供了一種合理的方法來做一些應該簡單明了的事情。

讓我們首先說,截至 2022 年,僅憑requests絕對沒有辦法正確地做到這一點。 這是庫的開發人員有意識的設計決定

使用timeout參數的解決方案根本無法完成他們想要做的事情。 乍一看它“似乎”起作用的事實純屬偶然:

timeout參數與請求的總執行時間絕對無關。 它僅控制底層套接字接收任何數據之前可以經過的最長時間。 以 5 秒的超時示例為例,服務器也可以每 4 秒發送 1 個字節的數據,這完全沒問題,但不會對您有太大幫助。

使用streamiter_content的答案要好一些,但它們仍然不能涵蓋請求中的所有內容。 在發送響應標頭之后,您實際上不會從iter_content收到任何內容,這屬於同一問題 - 即使您使用 1 字節作為iter_content的塊大小,讀取完整響應標頭可能需要完全任意的時間,您可以從來沒有真正到達您從iter_content讀取任何響應正文的地步。

以下是一些完全打破timeout和基於stream的方法的示例。 都試一試。 無論您使用哪種方法,它們都會無限期地掛起。

服務器.py

import socket
import time

server = socket.socket()

server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server.bind(('127.0.0.1', 8080))

server.listen()

while True:
    try:
        sock, addr = server.accept()
        print('Connection from', addr)
        sock.send(b'HTTP/1.1 200 OK\r\n')

        # Send some garbage headers very slowly but steadily.
        # Never actually complete the response.

        while True:
            sock.send(b'a')
            time.sleep(1)
    except:
        pass

演示1.py

import requests

requests.get('http://localhost:8080')

演示2.py

import requests

requests.get('http://localhost:8080', timeout=5)

演示3.py

import requests

requests.get('http://localhost:8080', timeout=(5, 5))

演示4.py

import requests

with requests.get('http://localhost:8080', timeout=(5, 5), stream=True) as res:
    for chunk in res.iter_content(1):
        break

正確的解決方案

我的方法利用了 Python 的sys.settrace函數。 這很簡單。 您不需要使用任何外部庫或顛倒您的代碼。 與大多數其他答案不同,這實際上保證了代碼在指定時間執行。 請注意,您仍然需要指定timeout參數,因為settrace僅涉及 Python 代碼。 實際的套接字讀取是外部系統調用, settrace不涵蓋,但timeout參數涵蓋。 由於這個事實,確切的時間限制不是TOTAL_TIMEOUT ,而是下面評論中解釋的值。

import requests
import sys
import time

# This function serves as a "hook" that executes for each Python statement
# down the road. There may be some performance penalty, but as downloading
# a webpage is mostly I/O bound, it's not going to be significant.

def trace_function(frame, event, arg):
    if time.time() - start > TOTAL_TIMEOUT:
        raise Exception('Timed out!') # Use whatever exception you consider appropriate.

    return trace_function

# The following code will terminate at most after TOTAL_TIMEOUT + the highest
# value specified in `timeout` parameter of `requests.get`.
# In this case 10 + 6 = 16 seconds.
# For most cases though, it's gonna terminate no later than TOTAL_TIMEOUT.

TOTAL_TIMEOUT = 10

start = time.time()

sys.settrace(trace_function)

try:
    res = requests.get('http://localhost:8080', timeout=(3, 6)) # Use whatever timeout values you consider appropriate.
except:
    raise
finally:
    sys.settrace(None) # Remove the time constraint and continue normally.

# Do something with the response

濃縮的

import requests, sys, time

TOTAL_TIMEOUT = 10

def trace_function(frame, event, arg):
    if time.time() - start > TOTAL_TIMEOUT:
        raise Exception('Timed out!')

    return trace_function

start = time.time()
sys.settrace(trace_function)

try:
    res = requests.get('http://localhost:8080', timeout=(3, 6))
except:
    raise
finally:
    sys.settrace(None)

而已!

這可能有點矯枉過正,但 Celery 分布式任務隊列對超時有很好的支持。

特別是,您可以定義一個軟時間限制,它只會在您的流程中引發異常(以便您可以清理)和/或一個硬時間限制,當超過時間限制時終止任務。

在幕后,這使用了與您的“之前”帖子中提到的相同的信號方法,但以更可用和更易於管理的方式。 如果您正在監控的網站列表很長,您可能會受益於它的主要功能——管理大量任務執行的各種方式。

我相信您可以使用multiprocessing而不依賴於第 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)

傳遞給kwargs的超時是從服務器獲取任何響應的超時,參數timeout是獲取完整響應的超時。

請原諒,但我想知道為什么沒有人提出以下更簡單的解決方案? :-o

## request
requests.get('www.mypage.com', timeout=20)

如果您使用選項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')

該解決方案不需要信號或多處理。

只是另一種解決方案(從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

但要小心,發件人可能會在“內容長度”響應字段中設置不正確的值。

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

此代碼適用於 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_()

盡管問題是關於請求的,但我發現使用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

好吧,我在這個頁面上嘗試了許多解決方案,但仍然面臨不穩定、隨機掛起、連接性能差的問題。

我現在正在使用 Curl,我對它的“最大時間”功能和全局性能感到非常高興,即使實現如此糟糕:

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

在這里,我定義了一個 6 秒的最大時間參數,包括連接時間和傳輸時間。

如果您喜歡堅持使用 Python 語法,我確信 Curl 有一個很好的 Python 綁定:)

有一個名為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))

它使用這里的一些答案建議的信號方法。 或者,您可以告訴它使用多處理而不是信號(例如,如果您處於多線程環境中)。

如果涉及到這一點,請創建一個看門狗線程,在 10 秒后弄亂請求的內部狀態,例如:

  • 關閉底層套接字,理想情況下
  • 如果請求重試操作,則觸發異常

請注意,根據系統庫,您可能無法設置 DNS 解析的截止日期。

我正在使用 requests 2.2.1,而 eventlet 對我不起作用。 相反,我可以使用 gevent timeout 代替,因為 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):盡管在引發此異常時沒有傳遞任何消息。

最大的問題是,如果無法建立連接, requests包會等待太久,阻塞了程序的其余部分。

有幾種方法可以解決這個問題,但是當我尋找類似於 requests 的 oneliner 時,我找不到任何東西。 這就是為什么我圍繞請求構建了一個名為reqto (“請求超時”)的包裝器,它支持來自requests的所有標准方法的適當超時。

pip install reqto

語法與請求相同

import reqto

response = reqto.get(f'https://pypi.org/pypi/reqto/json',timeout=1)
# Will raise an exception on Timeout
print(response)

此外,您可以設置自定義超時功能

def custom_function(parameter):
    print(parameter)


response = reqto.get(f'https://pypi.org/pypi/reqto/json',timeout=5,timeout_function=custom_function,timeout_args="Timeout custom function called")
#Will call timeout_function instead of raising an exception on Timeout
print(response)

重要說明是導入線

import reqto

由於在后台運行的monkey_patch,需要比所有其他使用請求、線程等的導入更早導入。

我想出了一個更直接的解決方案,它固然丑陋,但解決了真正的問題。 它有點像這樣:

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

你可以在這里閱讀完整的解釋

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM