Python中requests库设置了timeout参数为什么还会卡住?
conn = request.urlopen(image_url,timeout=300)
请问有啥优化的办法么?
爬虫爬了 24 小时卡住了。。 重启启动以后又继续爬了 所以不是被封
Python中requests库设置了timeout参数为什么还会卡住?
我有过类似经历,当然我用的是自带的 urllib。
后来跟着源代码一路排查,发现卡死在了获取 DNS 的步骤上。很不幸这个 timeout 不包括 DNS 查询时间。
urllib 直接使用 socket.getaddrinfo 获取 DNS(应该是这个函数,你可以再确认一下),写一个循环重复 1000 次,然后你发现自己的程序卡死了那么你应该遇到了和我一样的状况。
上面的那个函数触发一个系统调用去获取 DNS,所以当时我的结论是和操作系统相关的一个 bug,高并发请求导致 DNS 会卡死。(
也有可能是 DNS 服务器的问题?)
当时的服务器是 ubuntu1204,在相同 vps 服务商那里建一台全新的机器,也会有同样的问题,更换 dns 后问题得到解决。
这个问题我遇到过。requests 的 timeout 参数其实包含连接超时和读取超时两部分。你设置 timeout=5,意思是“连接阶段最多等5秒,连接成功后每次接收数据也最多等5秒”。问题就出在这个“每次”上。
比如服务器响应很慢,每4秒吐一点数据,连接没断,那么每次读取都没超时,整个下载就会一直卡下去。这不是bug,是设计如此。
要解决这个问题,你需要用 timeout=(connect_timeout, read_timeout) 这种元组形式,并且为整个响应设置一个总超时。下面是一个比较健壮的方案:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import signal
class TimeoutException(Exception):
pass
def timeout_handler(signum, frame):
raise TimeoutException("请求总超时")
# 设置总超时(秒)
TOTAL_TIMEOUT = 30
# 创建自定义适配器
adapter = HTTPAdapter(
max_retries=Retry(total=3, backoff_factor=0.5)
)
# 创建会话并挂载适配器
session = requests.Session()
session.mount('http://', adapter)
session.mount('https://', adapter)
# 设置信号超时(仅Unix/Linux有效)
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(TOTAL_TIMEOUT)
try:
# 分别设置连接超时和读取超时
response = session.get(
'https://example.com',
timeout=(5, 10), # (连接超时, 读取超时)
stream=True # 流式传输以便手动控制
)
# 手动控制读取过程
response.raise_for_status()
content = b""
for chunk in response.iter_content(chunk_size=8192):
signal.alarm(TOTAL_TIMEOUT) # 每次读取前重置总超时
if chunk:
content += chunk
# 这里可以添加进度检查,如果下载太慢可以主动中断
signal.alarm(0) # 取消定时器
print(f"成功下载 {len(content)} 字节")
except TimeoutException:
print(f"请求总耗时超过 {TOTAL_TIMEOUT} 秒,已终止")
except requests.exceptions.Timeout:
print("连接或读取超时")
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
finally:
signal.alarm(0) # 确保清理
关键点:
timeout=(5, 10)分别控制连接和读取stream=True配合iter_content()实现流式读取- 信号机制(Unix)或线程计时器(Windows)实现总超时
- 使用会话和适配器便于统一配置
对于Windows,可以用threading.Timer实现总超时:
import threading
def interrupt_request(timeout):
def interrupter():
import ctypes
ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(threading.get_ident()),
ctypes.py_object(TimeoutException)
)
timer = threading.Timer(timeout, interrupter)
timer.start()
return timer
总结来说,要同时设置连接超时、读取超时和总超时三层防护。
requests 文档上有写
Note
timeout is not a time limit on the entire response download; rather, an exception is raised if the server has not issued a response for timeout seconds (more precisely, if no bytes have been received on the underlying socket for timeout seconds). If no timeout is specified explicitly, requests do not time out.
在最开始加一个
import socket
socket.setdefaulttimeout(时间)
就可以了
我也感觉是 dns 的问题。因为我再次重试感觉也很顺
那如果要下大文件怎么办呢?
换了阿里 DNS 以后。 问题消失。
竟然还能出现 DNS 的卡 BUG 问题 此贴完结。
换成了阿里 dns 后竟然还出现了这个问题。
怎么解决
下大文件也可以,这个超时代表一定时间没有数据传输,你大文件还是在传输的


