110

在我的脚本中,requests.get永远不会返回:

import requests

print ("requesting..")

# This call never returns!
r = requests.get(
    "http://www.some-site.com",
    proxies = {'http': '222.255.169.74:8080'},
)

print(r.ok)

可能的原因是什么?有什么补救措施吗?使用的默认超时是get什么?

4

6 回答 6

161

get 使用的默认超时是多少?

默认超时是None,这意味着它将等待(挂起)直到连接关闭。

只需指定一个超时值,如下所示:

r = requests.get(
    'http://www.justdial.com',
    proxies={'http': '222.255.169.74:8080'},
    timeout=5
)
于 2013-07-22T07:59:49.970 回答
42

请求文档

您可以使用 timeout 参数告诉请求在给定的秒数后停止等待响应:

>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

笔记:

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

我经常遇到 requests.get() 需要很长时间才能返回,即使timeout是 1 秒。有几种方法可以克服这个问题:

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):
        if kwargs['connect'] is None:
            kwargs['connect'] = 5
        if kwargs['read'] is None:
            kwargs['read'] = 5
        super(MyTimeout, self).__init__(*args, **kwargs)

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

注意:此更改已合并到主请求项目中。

3. 使用evenletorsignal类似问题中已经提到的: Timeout for python requests.get entire response

于 2014-03-13T11:40:17.933 回答
6

我希望将默认超时轻松添加到一堆代码中(假设超时解决了您的问题)

这是我从提交到请求存储库的票证中获得的解决方案。

信用:https ://github.com/kennethreitz/requests/issues/2011#issuecomment-477784399

解决方案是这里的最后几行,但我展示了更多代码以获得更好的上下文。我喜欢使用会话进行重试行为。

import requests
import functools
from requests.adapters import HTTPAdapter,Retry


def requests_retry_session(
        retries=10,
        backoff_factor=2,
        status_forcelist=(500, 502, 503, 504),
        session=None,
        ) -> requests.Session:
    session = session or requests.Session()
    retry = Retry(
            total=retries,
            read=retries,
            connect=retries,
            backoff_factor=backoff_factor,
            status_forcelist=status_forcelist,
            )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    # set default timeout
    for method in ('get', 'options', 'head', 'post', 'put', 'patch', 'delete'):
        setattr(session, method, functools.partial(getattr(session, method), timeout=30))
    return session

然后你可以做这样的事情:

requests_session = requests_retry_session()
r = requests_session.get(url=url,...
于 2019-04-25T04:31:19.517 回答
4

查看了所有答案,得出的结论是问题仍然存在。在某些站点上,请求可能会无限挂起,并且使用多处理似乎是矫枉过正。这是我的方法(Python 3.5+):

import asyncio

import aiohttp


async def get_http(url):
    async with aiohttp.ClientSession(conn_timeout=1, read_timeout=3) as client:
        try:
            async with client.get(url) as response:
                content = await response.text()
                return content, response.status
        except Exception:
            pass


loop = asyncio.get_event_loop()
task = loop.create_task(get_http('http://example.com'))
loop.run_until_complete(task)
result = task.result()
if result is not None:
    content, status = task.result()
    if status == 200:
        print(content)

更新

如果您收到关于使用 conn_timeout 和 read_timeout 的弃用警告,请查看本参考资料底部附近以了解如何使用 ClientTimeout 数据结构。根据对上述原始代码的链接引用应用此数据结构的一种简单方法是:

async def get_http(url):
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as client:
        try:
            etc.
于 2017-11-04T06:53:00.010 回答
4

就我而言,“requests.get never return”的原因是因为requests.get()尝试连接到使用 ipv6 ip first 解析的主机。如果连接该 ipv6 ip 出现问题并卡住,那么只有当我明确设置并达到超时时它才会重试ipv4 ip 。timeout=<N seconds>

我的解决方案是猴子修补pythonsocket忽略ipv6(如果ipv4不起作用,则为ipv4),这个答案这个答案对我有用。

您可能想知道为什么curl命令有效,因为curl无需等待 ipv6 完成即可连接 ipv4。strace -ff -e network -s 10000 -- curl -vLk '<your url>'您可以使用命令跟踪套接字系统调用。对于python,strace -ff -e network -s 10000 -- python3 <your python script>可以使用命令。

于 2020-04-15T19:47:32.313 回答
3

修补记录在案的“发送”功能将为所有请求修复此问题 - 即使在许多依赖库和 sdk 中也是如此。修补库时,请务必修补支持/记录的功能,而不是 TimeoutSauce - 否则您可能会默默地失去补丁的效果。

import requests

DEFAULT_TIMEOUT = 180

old_send = requests.Session.send

def new_send(*args, **kwargs):
     if kwargs.get("timeout", None) is None:
         kwargs["timeout"] = DEFAULT_TIMEOUT
     return old_send(*args, **kwargs)

requests.Session.send = new_send

没有任何超时的影响是相当严重的,使用默认超时几乎不会破坏任何东西——因为 TCP 本身也有默认超时。

于 2020-04-22T15:20:01.507 回答