Python requests 请求一直卡住怎么办:timeout、状态码和重试一步步排查
来源:17golang原创
时间:2026-06-16 16:17:22 330浏览 收藏
用 Python 写接口调用时,最怕的一类问题不是立刻报错,而是程序一直停在那里。日志没有继续输出,任务队列越堆越多,手动中断后才发现卡在 requests.get() 这一行。
我们这次按排查路线来走:先复现没有 timeout 的卡住现象,再加上超时限制,接着检查 HTTP 状态码,最后给临时失败场景加上有限重试。这样处理后,请求失败会变成可控失败,而不是把整个任务拖住。
摘要
requests 默认不会自动给请求设置超时时间。调用外部接口时,建议显式传入 timeout,再用 raise_for_status() 检查状态码。对 429、500、502、503、504 这类可重试状态,可以用 HTTPAdapter 和 Retry 做有限重试,最后再检查返回体是否能按预期解析。
适合人群
- 使用 Python
requests调第三方接口、内部接口或爬取公开页面的开发者。 - 遇到接口调用偶发卡住、任务队列堆积、日志停在请求阶段的后端同学。
- 想把 timeout、状态码检查和重试封装成统一请求方法的项目维护者。
- 问题现场:请求没有 timeout 导致任务卡住
- 第一步验证:给请求加上 timeout
- 第二步定位:状态码不能只看有没有返回
- 修复方案:timeout、状态码检查和 Retry 组合
- 最终封装:一个可复用的请求函数
- 常见坑位
- 总结检查清单
问题现场:请求没有 timeout 导致任务卡住
我们先看现象。一个同步任务每天拉取外部接口数据,平时几秒钟就能结束。有一天任务突然没有结束,日志最后一行停在“开始请求外部 API”,后续解析、入库、完成日志都没有出现。
import requests
def fetch_user_profile(user_id):
url = f"https://api.example.test/users/{user_id}"
response = requests.get(url)
return response.json()
这段代码看起来很普通,但问题在于它没有设置 timeout。当网络链路、目标服务或代理层长时间没有返回时,任务可能一直等待。队列型任务里这会直接带来连锁反应:一个 worker 卡住,后面的任务开始堆积。

这一步说明,我们不能只关注“接口是否能通”,还要给每次调用设定等待上限。超时不是坏事,它是把不可控等待变成可控失败的第一道边界。
第一步验证:给请求加上 timeout
接着验证这个猜测。我们先不改复杂结构,只给请求加一个明确的超时时间。requests 的 timeout 可以传一个数字,也可以传一个二元组,分别表示连接超时和读取超时。
import requests
def fetch_user_profile(user_id):
url = f"https://api.example.test/users/{user_id}"
response = requests.get(url, timeout=(3, 10))
return response.json()
这里的 (3, 10) 表示连接阶段最多等 3 秒,服务已连接后读取响应最多等 10 秒。实际项目里要根据接口特征调整:内网接口通常更短,跨网络或第三方接口可以略长,但不建议完全不设。
加上 timeout 后,程序不会无限等待。它会在超时后抛出错误,我们就可以在外层捕获并记录上下文。
try:
data = fetch_user_profile(1001)
except requests.exceptions.Timeout:
print("请求超时,稍后重试或进入失败分支")
第二步定位:状态码不能只看有没有返回
现在请求不会无限卡住了,但还有一个隐藏问题:接口返回了,不代表业务成功。比如 500、502、503、504 都可能有响应体;如果代码直接 response.json(),可能把失败响应当成正常数据继续处理。
所以第二步要检查状态码。最直接的方式是调用 raise_for_status(),让非 2xx/3xx 的响应进入错误分支。
import requests
def fetch_user_profile(user_id):
url = f"https://api.example.test/users/{user_id}"
response = requests.get(url, timeout=(3, 10))
response.raise_for_status()
return response.json()
这一步的意义是把“有响应”拆成两层判断:先确认 HTTP 层成功,再解析业务数据。否则日志里只看到“请求完成”,但数据结构可能已经错了。
修复方案:timeout、状态码检查和 Retry 组合
现在可以定位到完整原因:没有超时会导致任务长期等待;没有状态码检查会让失败响应混进正常流程;没有有限重试会让临时抖动直接变成失败。解决方案是把这三件事组合起来。

对临时故障,可以使用 urllib3 的 Retry 配合 requests.adapters.HTTPAdapter。下面示例只对 GET 和 HEAD 这类适合重试的请求启用策略,并限制重试次数。
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def build_session():
retry = Retry(
total=2,
connect=2,
read=2,
status=2,
backoff_factor=0.5,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods={"GET", "HEAD"},
raise_on_status=False,
)
adapter = HTTPAdapter(max_retries=retry)
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
这里不要把重试次数设得太大。重试是用来抵抗临时抖动,不是用来掩盖接口长期不可用。对写操作尤其要谨慎,除非接口本身有幂等设计,否则不要轻易自动重试。
最终封装:一个可复用的请求函数
最后把请求逻辑封装起来。我们希望得到的不是“永远成功”,而是失败时能清楚返回是哪一类失败:超时、HTTP 状态异常、JSON 解析失败,还是其他请求错误。
import requests
session = build_session()
def get_json(url, params=None):
try:
response = session.get(url, params=params, timeout=(3, 10))
response.raise_for_status()
return {
"ok": True,
"data": response.json(),
"status_code": response.status_code,
}
except requests.exceptions.Timeout as error:
return {"ok": False, "type": "timeout", "message": str(error)}
except requests.exceptions.HTTPError as error:
status_code = error.response.status_code if error.response else None
return {"ok": False, "type": "http", "status_code": status_code}
except requests.exceptions.JSONDecodeError as error:
return {"ok": False, "type": "json", "message": str(error)}
except requests.exceptions.RequestException as error:
return {"ok": False, "type": "request", "message": str(error)}
这样封装后,上层业务就不用猜失败原因。比如 type 是 timeout,可以进入稍后重试;type 是 http,可以看状态码决定是否告警;type 是 json,说明接口返回体不是预期格式。
常见坑位
1. 只设置连接超时,不设置读取超时
如果只传一个数字,写法简单,但不容易区分连接阶段和读取阶段。对接口排查来说,二元组更清楚:连接慢和响应慢是两类问题。
2. 所有方法都自动重试
GET 请求通常更适合重试,POST、PATCH、DELETE 等写操作要看接口是否支持幂等。如果没有幂等保障,自动重试可能造成重复提交。
3. 没有记录上下文
捕获错误后只打印“请求失败”不够。至少要记录 URL、参数摘要、状态码、失败类型和耗时。这样后续才能判断是网络问题、服务问题,还是数据格式问题。
4. 把 Retry 当成稳定性保证
Retry 只是缓冲短暂抖动。如果接口长期慢或频繁 5xx,真正要做的是限流、降级、熔断或联系接口提供方,而不是无限增加重试次数。
总结检查清单
- 每个外部 HTTP 调用都要显式设置
timeout。 - 先检查
response.raise_for_status(),再解析 JSON。 - 只对适合重试的方法和状态码启用有限 Retry。
- 错误返回要区分
timeout、http、json和request。 - 日志要包含失败类型、状态码和关键业务参数摘要。
回到最开始的问题:requests 请求一直卡住,第一反应应该是检查有没有设置 timeout。把超时、状态码检查、有限重试和返回体检查连成固定流程,接口调用才会从“偶发卡死”变成“失败可控、原因可查”。
-
123 收藏
-
416 收藏
-
文章 · python教程 | 2星期前 | 异步编程 · fastapi · 后端架构 · Python教程 · asyncio · Python 异步编程 FastAPI asyncio TaskGroup 生产实践496 收藏
-
文章 · python教程 | 1星期前 | 性能优化 · gil · 生产实践 · Python教程 · CPython · Python 性能优化 线程安全 gil CPython free-threaded381 收藏
-
文章 · python教程 | 1星期前 | 性能优化 · fastapi · 生产实践 · Python教程 · Pydantic · Python 性能优化 FastAPI Pydantic v2 TypeAdapter validate_json342 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习