☰
requests 超时、重试、SSL 报错?9 个高频问题与一整张排错对照表
2026/10/7 14:46:13 网站建设 项目流程

requests 超时、重试、SSL 报错?9 个高频问题与一整张排错对照表

先给结论:requests的默认配置几乎全是"危险默认"——默认没有超时、默认不重试、默认每次新建连接。绝大多数线上事故不是 requests 的 bug,而是这三个默认没被覆盖。

下面 9 个坑,按线上出现频率排序,每个都给现象、根因、修法。

坑 1:不设 timeout,请求永远挂着

现象:程序卡死几小时,日志没有任何报错,线程/协程全部耗尽。

根因:requests的timeout默认值是None,即无限等待。服务端不返回,客户端就一直等着。

# 危险:没有超时r=requests.get(url)# 正确:分别设置连接超时和读取超时r=requests.get(url,timeout=(3.05,10))

timeout=(3.05, 10)表示连接超时 3.05 秒、读取超时 10 秒。这两个数是不同的东西:连接超时是 TCP 握手,读取超时是"连上之后等多久收到下一个字节"。只传一个数字(timeout=5)则表示两者都用 5 秒。

坑 2:以为 requests 会自动重试(它不会)

现象:偶发的 502/连接重置直接抛异常,明明重试一次就好。

根因:requests 本身不做任何重试。重试要挂urllib3的Retry到HTTPAdapter上。

fromrequests.adaptersimportHTTPAdapterfromurllib3.util.retryimportRetry retry=Retry(total=3,# 总重试次数backoff_factor=0.5,# 退避:0.5s, 1s, 2sstatus_forcelist=[429,500,502,503,504],allowed_methods=["GET","HEAD"],# 只对幂等方法重试)s=requests.Session()s.mount("https://",HTTPAdapter(max_retries=retry))

注意allowed_methods:默认只重试幂等方法。如果你的重试列表里放了 POST,重复下单这类非幂等请求会出大事故。

坑 3:verify=False一关了之

现象:SSLError: certificate verify failed,加verify=False后 warning 刷屏。

根因:证书链校验失败(内网自签证书、系统 CA 过旧、代理中间人证书)。verify=False只是关掉校验,等于放弃了中间人攻击防护,urllib3 会持续打InsecureRequestWarning。

修法(按优先级):

  1. 用certifi的最新 CA:requests.get(url, verify=certifi.where())
  2. 内网自签证书:把 CA 证书路径传给verify="/path/to/ca.pem"
  3. 仅在本机调试时临时verify=False,并配urllib3.disable_warnings()

坑 4:每次都requests.get,连接池白建

现象:QPS 上不去,TIME_WAIT 堆积。

根因:requests.get()每次都新建一个Session,握手、DNS、TLS 全部重来。

# 慢:每次新建连接foruinurls:requests.get(u)# 快:复用 Session(自动连接池 + keep-alive)withrequests.Session()ass:foruinurls:s.get(u,timeout=(3,10))

坑 5:Session 跨线程共享

现象:多线程下偶发莫名其妙的连接错误、响应串号。

根因:Session不是线程安全的。它内部的连接池虽然有一定并发能力,但 cookies、适配器状态并非为并发设计。

修法:每个线程一个 Session,或用threading.local()存,或改用httpx(明确区分 Client 的线程/异步模型)。

坑 6:中文乱码

现象:response.text出现æ\u0088\ue105之类乱码。

根因:服务器返回的Content-Type没带charset时,requests 按 HTTP 默认ISO-8859-1猜,text就按错编码解。

r=requests.get(url)# 先看它猜成了什么print(r.encoding,r.apparent_encoding)# ISO-8859-1 vs utf-8r.encoding=r.apparent_encoding# 或 r.encoding = "utf-8"print(r.text)

判据:r.encoding取自响应头,r.apparent_encoding是chardet/charset_normalizer实际探测的结果。两者不一致时,以探测结果为准。

坑 7:data=和json=分不清

现象:后端收不到参数,或收到的是一坨字符串。

写法Content-Type请求体
data={"a":1}application/x-www-form-urlencodeda=1
json={"a":1}application/json{"a": 1}
data=json.dumps(d)仍是 form 表单(除非手动加头)字符串被当表单值

想要 JSON 就直接用json=参数;手工data=json.dumps(...)必须自己补headers={"Content-Type": "application/json"}。

坑 8:下载大文件把内存打爆

现象:下载几百 MB 文件,内存飙到几 GB。

根因:response.content会一次性把整个响应体读进内存。

withrequests.get(url,stream=True,timeout=(3,30))asr:r.raise_for_status()withopen("big.zip","wb")asf:forchunkinr.iter_content(chunk_size=8192):ifchunk:f.write(chunk)

stream=True之后必须消费或关闭响应,否则连接不会归还连接池(见坑 9)。

坑 9:响应没关,连接池耗尽

现象:跑一段时间报ConnectionError: Max retries exceeded,或urllib3提示连接池已满。

根因:拿到响应后只取了status_code没读 body,或用stream=True后没读完也没close(),连接就一直占着。

r=requests.get(url,stream=True)r.close()# 显式归还# 更好的写法withrequests.get(url,stream=True)asr:...

一整张排错对照表

现象根因修法
程序永久挂起timeout默认None始终传timeout=(3, 10)
偶发 502 没人重试requests 不自动重试Retry+HTTPAdapter
SSLError/ warning 刷屏证书链校验失败verify=certifi.where()或指定 CA
QPS 上不去每次新建连接复用Session
多线程偶发串号Session 非线程安全每线程一个 Session
中文乱码默认ISO-8859-1设r.encoding = r.apparent_encoding
后端收不到 JSONdata=发成表单用json=
下载大文件 OOMcontent全读进内存stream=True+iter_content
连接池耗尽响应未关闭with或显式close()

最后

requests 的三个危险默认:无超时、不重试、不复用连接。把它们覆盖掉,能消掉线上八成以上的 HTTP 相关故障。再配一条:所有响应都用with包住,别让连接泄漏。

你在 requests 上踩过最坑的一次是什么?评论区说说。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询