想了解更多关于使用 Python 的 requests 模块时出现超时的问题吗?那你来对地方了,本文将详细讨论解决这一问题所需了解的一切内容。

通过互联网访问远程资源时,往往无法按预期方式运行——有些请求可能找不到所需的资源,有些需要特殊权限,而有些则会在响应发送之前超时。本文的重点是你在使用 Python 的 requests 模块时可能遇到的超时问题。requests 模块已经成为用于网页抓取、自动化和访问 Web API 的最流行的 HTTP 客户端库。

在本文中,我将向你展示如何有效处理超时异常,从而防止代码意外终止。


Python Requests 超时概述

与 Python 标准库中的方法相比,Requests 模块让发送 HTTP 网络请求变得异常简单。难怪它如此受欢迎,下载量已超过 3000 万次,成为 Python 中最常用的第三方库之一。

当你发送请求后,如果在指定时间内没有返回响应,就会引发超时错误。如果不对其进行处理,脚本将在此处终止。然而,这只有在你指定超时时间的情况下才成立。如果没有指定超时时间,脚本将会无限期挂起,这显然不是你期望的行为。

指定超时时间的主要原因在于,你无法完全信任目标服务总能及时发送响应——同时你也不能无限期等待。有趣的是,Requests 不仅支持指定超时时间,还提供了一个异常类(Exception Class),帮助你捕获超时错误,并以你希望的方式进行处理。


如何在 Python 的 Requests 模块中实现超时设置

超时期限以秒为单位定义,需要明确定义。在定义超时期限时,您可以提供一个一般超时期限,该期限既包括连接到服务器所需的时间,也包括服务器响应时间。此外,您还可以通过将期限指定为元组来为服务器连接和响应时间定义单独的超时期限。我将在下面向您展示如何完成此操作。

首先,您需要安装请求。如果您尚未安装,请使用下面的 pip 命令来完成。

pip install requests

现在让我们为 GET 请求设置一个超时时间。我将使用测试 Web API 来进行此次测试。

import requests

test_url = 'https://jsonplaceholder.typicode.com/todos'

req_one = requests.get(test_url, timeout=3, json=True)

print(req_one.json())

如上所示,设置的超时时间为 3 秒。这意味着如果 3 秒后未收到响应,请求将超时并引发异常。现在,配置的 3 秒既用于连接到服务器,也用于获取响应。

请求允许您对超时期限进行精细控制,因为您可以使用整数元组为连接到服务器和返回响应设置单独的超时期限,而不是仅使用整数来实现一般超时功能。让我们看看如何在下面做到这一点。

import requests

test_url = 'https://jsonplaceholder.typicode.com/todos'

req_one = requests.get(test_url, timeout=(2, 3), json=True)

print(req_one.json())

所做的更改是将 3 秒替换为 (2, 3)。使用此新配置,提供的超时时间现在更加精细,连接到目标站点需要 2 秒,返回响应需要 3 秒。


捕获超时异常

如前所述,当未明确配置超时时,请求将永远挂起。但是,当指定超时时,又增加了一层复杂性,因为您需要在发生超时时捕获它。Requests 提供了 TimeOut 异常类,用于捕获此类异常。让我们看看如何在 Python 中发送 Web 请求时使用它来捕获超时异常。

为此,我们需要做一些会触发此异常的事情。方法是将超时时间从 3 秒减少到 0.0001。这样,在触发异常之前,没有平均目标能够返回响应。再次强调,这只是为了测试,您不能在实时场景中使用此值。

import requests

test_url = 'https://jsonplaceholder.typicode.com/todos'

req_one = requests.get(test_url, timeout=0.001, json=True)

print(req_one.json())

运行上述代码,会返回异常,并指明发生的异常类型。以下是回溯消息的截图。

回溯消息

如果你查看错误消息,你会发现错误类型是请求异常模块中提供的 ConnectTimeout。但是,你可以简单地使用 Timeout 异常类来实现这一点。现在让我们看看如何捕获此异常。

import requests

test_url = 'https://jsonplaceholder.typicode.com/todos'

try:

    req_one = requests.get(test_url, timeout=0.001, json=True)

    print(req_one.json())

except requests.exceptions.Timeout as e:

    print('The request timedout, please try again')

运行上述脚本并查看您将收到的响应。与初始错误消息不同,您将注意到没有抛出任何错误。相反,它只打印“请求超时,请重试”。这有效地表明我们已经能够捕获和处理超时异常。


实现会话超时

在上面的代码中,超时的指定方式是可以的。但是,如果我们必须在代码的 10 个不同部分发送 10 个请求,那么我们必须为每个请求定义超时时间。但是,如果请求是连接的并且需要位于同一个会话中,我们可以为会话设置一个全局超时时间,并且使用该会话对象发出的所有请求都将使用该会话时间。下面是关于如何在会话中定义和使用超时的代码。

import requests

test_url = 'https://jsonplaceholder.typicode.com/todos'

class TimeoutSession(requests.Session):

    def request(self, method, url, **kwargs):

        kwargs.setdefault("timeout", 0.001)

        return super().request(method, url, **kwargs)

session = TimeoutSession()

try:

    response = session.get(test_url, json=True)

    print(response.json())

except requests.exceptions.Timeout:

    print('Request time out, try again!')

现在让我带你看一下代码。创建了一个 TimeoutSession 对象,它继承自原始的 request.Session 对象,本质上让它继承了 request.Session 的所有属性和功能。但是,唯一的变化是 timeout 关键字参数的值,它被设置为 0.001。

请记住,我在其他情况下使用此值来触发超时异常。在生产中,没有服务器会那么快,因为您的超时期限支持为几秒钟,而不是少于一秒。

代码的其余部分是不言自明的。如果你运行代码,你会看到它会通知你请求超时。我们再次能够捕获超时异常。


重试以修复超时异常

在上述所有处理中,我们所做的只是打印出请求超时的消息。在实际工作环境中,由于网络错误必然会发生,因此您需要重试请求几次才能停止。

在本节中,我将向您展示如何在 Python 请求中实现重试,以便您的代码更加健壮,在返回超时错误消息之前多次尝试请求。下面是一个包装函数,它利用请求模块在指定的时间段内重试请求,如果失败则返回响应。

import requests

from requests.adapters import HTTPAdapter, Retry

test_url = 'https://jsonplaceholder.typicode.com/todos'

class TimeoutSession(requests.Session):

    def request(self, method, url, **kwargs):

        kwargs.setdefault("timeout", 0.001)

        return super().request(method, url, **kwargs)

def make_request_with_retries(url, method="GET", retries=3, backoff_factor=0.3, headers=None, data=None, return_json=False):

    session = TimeoutSession()

    retry_strategy = Retry(

        total=retries,

        backoff_factor=backoff_factor,

        status_forcelist=[500, 502, 503, 504],

        allowed_methods=["HEAD", "GET", "OPTIONS", "POST"]

    )

    adapter = HTTPAdapter(max_retries=retry_strategy)

    session.mount("http://", adapter)

    session.mount("https://", adapter)

    if headers:

        session.headers.update(headers)

    try:

        if method.upper() == "GET":

            response = session.get(url)

        elif method.upper() == "POST":

            response = session.post(url, data=data)

        response.raise_for_status()

        if return_json:

            return response.json()

        else:

            return response.content

    except requests.exceptions.RequestException as e:

        print(f"Request failed: {e}")

        return None

As you can see above, I still make use of the TimeoutSession class that is inherited from the request Session class. However, what I did differently here is that I didn’t catch the Timeout error explicitly. Instead, I created a function to send requests with the capability to retry requests. This function catches all exceptions from timeout exceptions to 500 server errors.

但是,只有在超过指定的重试次数后,它才会停止尝试。您需要了解的一件事是,它使用了退避策略。这基本上设置了重试之间的延迟,并且在请求失败后,每次重试的延迟都会呈指数级增长。这样做是为了防止目标服务器不堪重负。让我们看看如何使用上面的函数。

response = make_request_with_retries(test_url, return_json=True)

print(response)

将上面的代码添加到主脚本中,直接在 make_request_with_retries 函数下并运行它。您会注意到它仍然会打印出来。

如上所示,它在控件上很好地打印出了 ConnectTimeout 错误,并指出这是在超出最大重试次数之后发生的。我建议您使用这种重试方法来处理超时。


请求超时应遵循的一般准则

如果您正在访问远程资源,以下是有关请求超时的一些应遵循的准则。

明确设置超时期限:

如前所述,请求没有设置默认超时期限。如果您不设置,您的请求将永远挂起,不会返回任何响应,这对您和您的用户来说都是糟糕的用户体验。始终设置请求超时。

使用合理的时间段:

没有约定的超时期限。这取决于给定 API 端点的预期响应时间。如果您希望在 1 秒内收到响应,则可以将超时期限设置为 5-7 秒,以适应糟糕的日子。

始终处理超时异常:

当您设置超时期限时,您应该预料到它会在超过时间限制时引发异常并有效地处理它。虽然您可以正常终止脚本的执行,但最好再重试请求几次,这种方法已在上文讨论过。


结论

请求超时不应该让你感到害怕。事实上,我建议你将其视为一种功能,了解如何利用它将帮助你构建健壮的 API 和自动化工具,这些工具不仅能及时返回响应,还能在请求超时时通知你,并有效地处理它。

通过上文的内容,你应该已经了解了如何在 Python 中显式设置请求超时时间,以及如何使用自定义函数(如上面提供的示例)来处理超时问题。

这篇文章有用吗?

点击星号为它评分!

平均评分 / 5. 投票数:

到目前为止还没有投票!成为第一位评论此文章。

No more articles