[英]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 提供的好東西。 (元組中的那些)
使用 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
。 如果沒有明確指定超時,則請求不會超時。
要創建超時,您可以使用信號。
解決這種情況的最好方法可能是
try-except-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)
對此有一些警告:
但是,這一切都在標准的 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()]
盡管有所有答案,但我相信這個線程仍然缺乏適當的解決方案,並且沒有現有的答案提供了一種合理的方法來做一些應該簡單明了的事情。
讓我們首先說,截至 2022 年,僅憑requests
絕對沒有辦法正確地做到這一點。 這是庫的開發人員有意識的設計決定。
使用timeout
參數的解決方案根本無法完成他們想要做的事情。 乍一看它“似乎”起作用的事實純屬偶然:
timeout
參數與請求的總執行時間絕對無關。 它僅控制底層套接字接收任何數據之前可以經過的最長時間。 以 5 秒的超時示例為例,服務器也可以每 4 秒發送 1 個字節的數據,這完全沒問題,但不會對您有太大幫助。
使用stream
和iter_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.