超时是网络编程中一个至关重要的概念,它能防止你的程序因为网络问题、服务器无响应或目标地址不可达而无限期地等待下去,导致程序“卡死”。

为什么必须设置超时?
想象一下以下几种场景:
- 服务器宕机或网络中断:你请求的服务器已经下线,但你的代码不知道,它会一直等待,直到 TCP 连接超时(这可能需要很长时间)。
- 服务器处理缓慢:服务器接收到了请求,但由于内部逻辑复杂(如复杂的数据库查询、大量计算),处理时间非常长,迟迟不返回响应。
- 目标地址错误或防火墙阻拦:你请求的域名或 IP 地址错误,或者被中间的防火墙墙掉了,导致请求无法到达服务器。
在这些情况下,如果没有设置超时,你的 Python 脚本就会永远阻塞在 requests.get() 这一行,无法继续执行后续代码,也无法处理其他请求或任务。
requests 如何设置超时?
requests 库的请求方法(如 get, post, put, delete 等)都接受一个 timeout 参数,这个参数是控制“等待”行为的关键。
timeout 参数可以有两种形式:

单个浮点数(简单超时)
当你只传入一个单一的数字(如 0 或 10)时,它表示的是 “连接超时 + 读取超时”的总和。
- 连接超时:指的是客户端与服务器建立 TCP 连接所等待的最长时间,如果在这个时间内连接不上,就会抛出
requests.exceptions.ConnectTimeout异常。 - 读取超时:指的是客户端在发送请求后,等待服务器返回响应数据的最长时间,如果在这个时间内没有收到任何数据,就会抛出
requests.exceptions.ReadTimeout异常。
示例:
import requests
import time
try:
# 设置总超时时间为 5 秒
# 如果连接 + 读取的总时间超过 5 秒,就会抛出 Timeout 异常
print("开始请求,设置总超时 5 秒...")
response = requests.get('https://httpbin.org/delay/10', timeout=5)
print("请求成功!")
print(response.status_code)
except requests.exceptions.Timeout:
print("请求超时了!")
except requests.exceptions.RequestException as e:
print(f"发生其他请求错误: {e}")
分析:
上面的代码请求了一个会延迟 10 秒才响应的地址 (https://httpbin.org/delay/10),但我们设置了 timeout=5,5 秒后 requests 会放弃等待,抛出 Timeout 异常。
元组(精细超时控制)
为了更精确地控制超时行为,timeout 参数也可以接受一个元组,形式为 (connect_timeout, read_timeout)。
connect_timeout:连接超时时间。read_timeout:读取超时时间。
这种方式更灵活,可以根据实际需求分别设置,你希望快速发现无法连接的服务器,但可以容忍服务器在建立连接后稍慢地返回数据。
示例:
import requests
try:
# 连接超时 3 秒,读取超时 10 秒
print("开始请求,连接超时 3 秒,读取超时 10 秒...")
response = requests.get('https://httpbin.org/delay/2', timeout=(3, 10))
print("请求成功!")
print(response.status_code)
except requests.exceptions.Timeout:
print("请求超时了!")
except requests.exceptions.RequestException as e:
print(f"发生其他请求错误: {e}")
分析:
这个请求会延迟 2 秒返回,我们的设置是连接 3 秒,读取 10 秒,总时间 2 < 3 + 10,2 < 10,所以请求会成功。
再看一个失败的例子:
import requests
try:
# 连接超时 3 秒,读取超时 5 秒
# 服务器会延迟 10 秒返回,这超过了读取超时时间
print("开始请求,连接超时 3 秒,读取超时 5 秒...")
response = requests.get('https://httpbin.org/delay/10', timeout=(3, 5))
print("请求成功!")
print(response.status_code)
except requests.exceptions.ReadTimeout:
# 注意:这里捕获的是更具体的 ReadTimeout
print("读取超时了!服务器响应太慢。")
except requests.exceptions.Timeout:
print("请求超时了!")
except requests.exceptions.RequestException as e:
print(f"发生其他请求错误: {e}")
分析:
连接成功建立(假设很快),但服务器需要 10 秒才返回数据,而我们设置的 read_timeout 只有 5 秒,5 秒后客户端会放弃等待,抛出 requests.exceptions.ReadTimeout 异常。
常见的超时异常
了解可能抛出的异常类型,有助于你编写更健壮的错误处理代码。
requests.exceptions.Timeout:这是一个通用的超时异常,当你使用单个数字作为timeout时,如果连接或读取任一阶段超时,都会抛出这个异常。requests.exceptions.ConnectTimeout:连接超时,专门用于在建立连接阶段超时。requests.exceptions.ReadTimeout:读取超时,专门用于在等待服务器返回数据阶段超时。requests.exceptions.TooManyRedirects:重定向过多,当你设置了allow_redirects=True(默认) 并且重定向次数超过了requests的上限时,会抛出此异常,这也可以看作是一种“超时”。requests.exceptions.RequestException:这是所有requests异常的基类,捕获它可以捕获所有由requests引发的错误。
最佳实践和推荐
-
永远不要不设置超时:这是一个坏习惯,除非你有特殊需求,否则请始终为你的请求设置一个合理的
timeout。 -
为不同场景设置不同的超时:
- 关键业务请求:可以设置较短的超时(如
timeout=(3, 5)),快速失败,然后重试或通知用户。 - 后台任务/非关键请求:可以设置较长的超时(如
timeout=(10, 30)),给服务器更多处理时间,但也要避免无限等待。
- 关键业务请求:可以设置较短的超时(如
-
使用
try...except进行错误处理: 妥善处理超时和其他网络异常,让你的程序在遇到问题时能够优雅地降级或重试,而不是直接崩溃。 -
考虑重试机制: 对于瞬时性的网络抖动,一次超时不代表永久失败,可以实现一个简单的重试逻辑。
示例:带重试机制的请求
import requests from requests.exceptions import Timeout MAX_RETRIES = 3 TIMEOUT = 5 # 总超时时间 def fetch_url_with_retry(url): for attempt in range(MAX_RETRIES): try: print(f"尝试第 {attempt + 1} 次请求...") response = requests.get(url, timeout=TIMEOUT) response.raise_for_status() # 如果状态码不是 200,则抛出 HTTPError return response except Timeout: print(f"第 {attempt + 1} 次请求超时。") if attempt == MAX_RETRIES - 1: print(f"已达到最大重试次数 {MAX_RETRIES},请求最终失败。") return None except requests.exceptions.RequestException as e: print(f"第 {attempt + 1} 次请求发生错误: {e}") return None # 对于其他错误,可能不需要重试 # 使用示例 response = fetch_url_with_retry('https://httpbin.org/delay/10') if response: print("最终请求成功!") print(response.json()) else: print("最终请求失败。")
| 超时类型 | 描述 | 示例 | 可能抛出的异常 |
|---|---|---|---|
| 单个数字 | 连接和读取的总超时时间 | requests.get(url, timeout=10) |
requests.exceptions.Timeout |
| 元组 | 分别设置连接超时和读取超时 | requests.get(url, timeout=(3, 10)) |
requests.exceptions.ConnectTimeoutrequests.exceptions.ReadTimeout |
设置超时是编写健壮网络应用的基本要求,根据你的应用场景,选择合适的超时策略,并做好异常处理,你的程序将会更加稳定可靠。
