【问题标题】:Timeout for python requests.get entire responsepython请求超时。获取整个响应
【发布时间】:2014-03-24 19:12:56
【问题描述】:

我正在收集有关网站列表的统计信息,并且为了简单起见,我使用了对它的请求。这是我的代码:

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 秒后超时,这样循环就不会卡住。

before 也对这个问题很感兴趣,但没有一个答案是干净的。我会为此付出一些赏金以获得一个不错的答案。

我听说不使用 requests 是个好主意,但是我应该如何获得 requests 提供的好东西。 (元组中的那些)

【问题讨论】:

标签: python timeout python-requests


【解决方案1】:

设置timeout parameter:

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

2.25.1 版本的变化

如果读取之间的连接或延迟时间超过十秒,上面的代码将导致对requests.get() 的调用超时。见:https://docs.python-requests.org/en/master/user/advanced/#timeouts

【讨论】:

【解决方案2】:

使用 eventlet 怎么样?如果你想在 10 秒后让请求超时,即使正在接收数据,这个 sn-p 也会为你工作:

import requests
import eventlet
eventlet.monkey_patch()

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

【讨论】:

  • 这肯定是不必要的复杂。
  • 谢谢。我现在了解您的解决方案的技术优势(您在回答的开头相当简洁地说明了这一点)并对其表示赞同。第三方模块的问题不是导入它们,而是确保它们可以被导入,因此我自己倾向于尽可能使用标准库。
  • 是否需要eventlet.monkey_patch()
  • 截至 2018 这个答案已经过时了。使用 requests.get('https://github.com', timeout=5)
  • 来自请求开发人员的 This comment 很好地解释了为什么请求没有总响应时间超时,以及他们的建议。
【解决方案3】:

更新:https://requests.readthedocs.io/en/master/user/advanced/#timeouts

requests的新版本中:

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

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

超时值将应用于connectread 超时。如果您想单独设置值,请指定一个元组:

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 has requested它要合并到主请求项目中,但尚未被接受。

【讨论】:

  • 选项 1 不起作用。如果您继续阅读该线程,其他人会说“恐怕这不适用于您的用例。读取超时功能在单个套接字 recv() 调用的范围内,因此如果服务器停止发送数据超过我们将中止的读取超时。"
  • 在那个线程中有另一个使用 Signal 的不错的解决方案,这对我也不起作用,因为我使用 Windows 并且 signal.alarm 仅适用于 linux。
  • @Kiarash 我还没有测试过。但是,据我了解,当 Lukasa 说this won't work for you use-case 时。他的意思是它不适用于其他人想要的 mp3 流。
  • @Hieu - 这已合并到另一个拉取请求中 - github.com/kennethreitz/requests/pull/…
  • timeout=None 没有阻塞通话。
【解决方案4】:

timeout = int(seconds)

由于requests >= 2.4.0,您可以使用timeout 参数,即:

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

注意:

timeout不是整个响应下载的时间限制;相当, 如果服务器没有发出响应,则会引发 exception 超时秒数(更准确地说,如果在 超时秒的底层套接字)。如果没有指定超时 明确地,请求不会超时。

【讨论】:

  • 什么版本的请求有新的超时参数?
  • 似乎是从 2.4.0 版开始:支持连接超时! Timeout 现在接受一个元组(连接,读取),用于设置单独的连接和读取超时pypi.org/project/requests/2.4.0
【解决方案5】:

要创建超时,您可以使用signals

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

  1. 设置异常作为警报信号的处理程序
  2. 延迟 10 秒调用警报信号
  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 和 singaling 放入一个函数中,然后调用它。或者您可以制作一个装饰器并将其放在函数上,请参阅下面链接的答案。

您还可以将其设置为"context manager",以便您可以将其与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')

这种上下文管理器方法的一个可能缺点是您无法知道代码是否实际超时。

来源和推荐阅读:

【讨论】:

  • 信号仅在主线程中传递,因此它明确不会在其他线程中起作用,而不是可能
  • timeout-decorator 包提供了一个使用信号(或可选多处理)的超时装饰器。
【解决方案6】:

用超时和错误处理试试这个请求:

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

【讨论】:

    【解决方案7】:

    连接超时number of seconds 请求将等待您的客户端在套接字上建立与远程机器的连接(对应于connect())调用。最好将连接超时设置为略大于 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

    【讨论】:

      【解决方案8】:

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

      【讨论】:

      • 很遗憾请求不支持 maxtime 参数,这个解决方案是唯一使用 asyncio 的解决方案
      【解决方案9】:

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

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

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

      【讨论】:

      • 这可能是一个很好的解决方案。总超时问题与python-requests 没有直接关系,而是与httplib (用于Python 2.7 的请求)有关。该包将与timeout 相关的所有内容直接传递给httplib。我认为请求中没有什么可以修复,因为该过程可以在 httplib 中停留很长时间。
      • @hynekcer,我认为你是对的。这就是为什么检测进程外超时并像 Celery 那样通过干净地杀死进程来强制执行可能是一个好方法的原因。
      【解决方案10】:

      我相信你可以使用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['error'] 的私有函数中的通用 try/except 来改进。然后最后,在返回之前,检查 return_dict 中是否“错误”,然后将其引发。它也使测试变得更加容易。
      【解决方案11】:

      如果您使用选项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')
      

      该解决方案不需要信号或多处理。

      【讨论】:

      • 如果目标服务器停止流式传输数据,这将不起作用。您将永远被锁定在iter 行。例如,当您的身份验证会话到期时,就会发生这种情况。
      【解决方案12】:

      只是另一种解决方案(从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
      

      但请注意,发件人可能会在“内容长度”响应字段中设置不正确的值。

      【讨论】:

      • 谢谢。清洁和简单的解决方案。为我工作。
      【解决方案13】:

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

      【讨论】:

        【解决方案14】:

        此代码适用于 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_()
        

        【讨论】:

        • 为创意点赞
        【解决方案15】:

        尽管问题是关于请求的,但我发现使用 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
        

        【讨论】:

          【解决方案16】:

          好吧,我在这个页面上尝试了许多解决方案,但仍然面临不稳定、随机挂起、连接性能差的问题。

          我现在正在使用 Curl,我真的很高兴它的“最大时间”功能和全局性能,即使实现如此糟糕:

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

          在这里,我定义了一个 6 秒的最大时间参数,包括连接时间和传输时间。

          如果您更喜欢使用 Python 语法,我相信 Curl 有一个不错的 Python 绑定:)

          【讨论】:

            【解决方案17】:

            有一个名为 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))
            

            它使用这里的一些答案建议的信号方法。或者,您可以告诉它使用多处理而不是信号(例如,如果您处于多线程环境中)。

            【讨论】:

            【解决方案18】:

            如果是这样,创建一个 watchdog 线程,在 10 秒后搞乱请求的内部状态,例如:

            • 关闭底层套接字,理想情况下
            • 如果请求重试操作,则触发异常

            请注意,根据系统库的不同,您可能无法设置 DNS 解析的截止日期。

            【讨论】:

              【解决方案19】:

              我正在使用 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): 尽管引发此异常时没有传递任何消息。

              【讨论】:

                【解决方案20】:

                我想出了一个更直接的解决方案,它固然丑陋,但解决了真正的问题。有点像这样:

                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
                

                你可以阅读完整的解释here

                【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-05-13
                • 1970-01-01
                • 2018-05-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-04-02
                相关资源
                最近更新 更多