python requests.get超时的完成响应

Kia*_*ash 134 python timeout python-requests

我正在收集网站列表的统计数据,为了简单起见,我正在使用它的请求.这是我的代码:

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())) )
Run Code Online (Sandbox Code Playgroud)

现在,我希望requests.get在10秒后超时,这样循环就不会卡住.

这个问题之前也引起了人们的兴趣,但没有一个答案是清晰的.我将在此给予一些赏金以获得一个很好的答案.

我听说也许不使用请求是一个好主意,但那么我应该如何获得请求提供的好东西.(元组中的那些)

Luk*_*asa 224

设置超时参数:

r = requests.get(w, verify=False, timeout=10)
Run Code Online (Sandbox Code Playgroud)

只要您没有设置stream=True该请求,requests.get()如果连接超过十秒,或者服务器发送的数据超过十秒,这将导致调用超时.

  • 这不是整个回应.http://requests.readthedocs.org/en/latest/user/quickstart/#timeouts (22认同)
  • 啊,对不起,当你说'整个回应'时,我误解了你的意思.是的,你是对的:它不是等待总时间的上限. (3认同)
  • 我刚刚检查了这个,它从未停止过: r = requests.get('http://ipv4.download.thinkbroadband.com/1GB.zip', timeout = 20) (2认同)

Alv*_*aro 114

使用eventlet怎么样?如果您希望在10秒后超时请求,即使正在接收数据,此代码段也适用于您:

import requests
import eventlet
eventlet.monkey_patch()

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

  • 当然这是不必要的复杂. (82认同)
  • 截至*** 2018 ***,此答案已过时。使用**`requests.get('https://github.com',timeout = 5)`** (19认同)
  • 是`eventlet.monkey_patch()`吗? (9认同)
  • 谢谢.我现在明白你的解决方案的技术优势(你在答案的开头简明扼要地提出)并对其进行了推动.第三方模块的问题不是导入它们,而是确保它们可以导入,因此我自己喜欢尽可能使用标准库. (6认同)
  • [此评论](https://github.com/requests/requests/issues/3099#issuecomment-215498005) 来自请求开发人员,很好地解释了为什么请求没有总响应时间超时,以及它们是什么建议改为。 (3认同)
  • 为什么这个不受重视的@Alvaro?我只是查了一下eventlet,他们页面的底部有一个例子,与我想做的非常相似?!http://eventlet.net/ (2认同)
  • 是的,`socket`模块需要修补猴子,所以至少你需要一个`eventlet.monkey_patch(socket = True)` (2认同)

Hie*_*ieu 75

更新:http://docs.python-requests.org/en/master/user/advanced/#timeouts

在新版本中requests:

如果为超时指定单个值,则如下所示:

r = requests.get('https://github.com', timeout=5)
Run Code Online (Sandbox Code Playgroud)

超时值将应用于超时connectread超时.如果要单独设置值,请指定元组:

r = requests.get('https://github.com', timeout=(3.05, 27))
Run Code Online (Sandbox Code Playgroud)

如果远程服务器非常慢,您可以通过传递None作为超时值然后检索一杯咖啡来告诉请求永远等待响应.

r = requests.get('https://github.com', timeout=None)
Run Code Online (Sandbox Code Playgroud)

我的旧(可能是过时的)答案(很久以前发布):

还有其他方法可以解决这个问题:

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
Run Code Online (Sandbox Code Playgroud)

此代码应该使我们将读取超时设置为等于连接超时,这是您在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)
Run Code Online (Sandbox Code Playgroud)

超时值将应用于连接和读取超时.如果要单独设置值,请指定元组:

r = requests.get('https://github.com', timeout=(3.05, 27))
Run Code Online (Sandbox Code Playgroud)

kevinburke已要求将其合并到主要请求项目中,但尚未被接受.


Tim*_*ado 38

到 2023 年,大多数其他答案都是错误的。你不会实现你想要的。

TL;DR - 正确的解决方案,浓缩

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)
Run Code Online (Sandbox Code Playgroud)

阅读解释即可了解原因!

尽管有所有答案,我相信该线程仍然缺乏适当的解决方案,并且没有现有答案提供合理的方法来完成应该简单明了的事情。

首先我们要说的是,到2023年,仍然绝对没有办法requests单独完成它。这是图书馆开发人员有意识的设计决定

利用该参数的解决timeout方案根本无法完成他们想要做的事情。乍一看“似乎”有效这一事实纯粹是偶然的:

timeout参数与请求的总执行时间完全无关。它仅控制底层套接字接收任何数据之前可以经过的最长时间。以 5 秒超时为例,服务器也可以每 4 秒发送 1 个字节的数据,这完全没问题,但不会对您有太大帮助。

stream带有和 的答案iter_content稍好一些,但它们仍然没有涵盖请求中的所有内容。从发送响应标头到发送响应标头之后,您实际上都没有收到任何内容iter_content,这属于同一问题 - 即使您使用 1 个字节作为 的块大小iter_content,读取完整的响应标头可能会花费完全任意的时间,并且您实际上永远无法到达从 读取任何响应正文的位置iter_content

以下是一些完全打破基于timeoutandstream的方法的示例。全部尝试一下。无论您使用哪种方法,它们都会无限期地挂起。

服务器.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
Run Code Online (Sandbox Code Playgroud)

演示1.py

import requests

requests.get('http://localhost:8080')
Run Code Online (Sandbox Code Playgroud)

演示2.py

import requests

requests.get('http://localhost:8080', timeout=5)
Run Code Online (Sandbox Code Playgroud)

演示3.py

import requests

requests.get('http://localhost:8080', timeout=(5, 5))
Run Code Online (Sandbox Code Playgroud)

演示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
Run Code Online (Sandbox Code Playgroud)

正确的解决方案

我的方法是利用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
Run Code Online (Sandbox Code Playgroud)

就是这样!


Ped*_*ito 35

2019年1月起,您可以使用timeout = int(seconds)参数requests >= 2.4.0,即:

requests.get(url, timeout=10)
Run Code Online (Sandbox Code Playgroud)

注意:

timeout不是整个响应下载的时间限制; 相反,如果服务器没有发出超时秒的响应(更准确地说,如果在底层套接字上没有收到超时秒的字节),则会引发异常.如果未明确指定超时,则请求不会超时.

  • 似乎是从 2.4.0 版开始的:*支持连接超时!Timeout 现在接受一个元组(连接、读取),用于设置单独的连接和读取超时*。https://pypi.org/project/requests/2.4.0/ (2认同)

tot*_*aka 23

要创建超时,您可以使用信号.

解决这种情况的最佳方法可能是

  1. 将异常设置为警报信号的处理程序
  2. 以十秒延迟调用警报信号
  3. try-except-finally块内调用该函数.
  4. 如果函数超时,则到达except块.
  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)
Run Code Online (Sandbox Code Playgroud)

这有一些警告:

  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')
Run Code Online (Sandbox Code Playgroud)

使用此上下文管理器方法的一个可能的缺点是您无法知道代码是否实际超时.

来源和推荐阅读:

  • 信号只在主线程中传递,因此*defnitely*将无法在其他线程中工作,而不是*可能*. (2认同)

Ale*_*hen 15

连接超时是指number of seconds请求将等待您的客户端在套接字上调用与远程计算机建立连接(对应于 connect())。将连接超时设置为略大于 3 的倍数是一个很好的做法,这是默认的 TCP 数据包重传窗口。

\n

一旦客户端连接到服务器并发送 HTTP 请求,读取超时就开始了。这是客户端等待服务器发送响应的秒数。(具体来说,它是客户端在服务器发送字节之间等待的秒数。在 99.9% 的情况下,这是服务器发送第一个字节之前的时间)。

\n

如果为超时指定单个值,则该超时值将应用于连接超时和读取超时。像下面这样:

\n
r = requests.get(\'https://github.com\', timeout=5)\n
Run Code Online (Sandbox Code Playgroud)\n

如果您想单独设置连接和读取的值,请指定一个元组:

\n
r = requests.get(\'https://github.com\', timeout=(3.05, 27))\n
Run Code Online (Sandbox Code Playgroud)\n

如果远程服务器非常慢,您可以通过传递 None 作为超时值来告诉 Requests 永远等待响应,然后检索一杯咖啡。

\n
r = requests.get(\'https://github.com\', timeout=None)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n


Pol*_*olv 6

设置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()]
Run Code Online (Sandbox Code Playgroud)

讨论在这里https://redd.it/80kp1h


DaW*_*aWe 6

尝试使用超时和错误处理此请求:

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


Chr*_*son 5

这可能有点矫枉过正,但 Celery 分布式任务队列对超时有很好的支持。

特别是,您可以定义一个软时间限制,它只会在您的流程中引发异常(以便您可以清理)和/或一个硬时间限制,在超过时间限制时终止任务。

在幕后,这使用了与您的“之前”帖子中提到的相同的信号方法,但以更有用和更易于管理的方式。如果您正在监视的网站列表很长,您可能会受益于它的主要功能——管理大量任务执行的各种方法。


Jor*_*tao 5

我相信您可以使用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)
Run Code Online (Sandbox Code Playgroud)

传递给的超时是从服务器获取任何kwargs响应的超时,参数是获取完整响应的超时。timeout