HTTPError需在raise_for_status()后捕获;4xx不重试,5xx可降级重试,降级时须校验e.response非None并记录状态码与Retry-After头。

捕获 HTTPError 必须在 raise_for_status() 之后
HTTPError 不是 requests 自动抛出的,它只在显式调用 response.raise_for_status() 时触发(对应 4xx/5xx 响应)。如果跳过这步,response.status_code 虽然可读,但不会进 except 分支。
常见错误:只检查 status_code == 404 却没捕获 HTTPError,导致 503、401 等状态码被忽略或误判为成功响应。
正确做法是:先调用 raise_for_status(),再用 except HTTPError as e 捕获;若需保留原始响应对象(比如要读取 404 页面的 HTML),就别调用它,改用手动判断 status_code。
区分 4xx 和 5xx 决定是否降级
4xx 是客户端问题,基本不重试也不降级——比如 401 Unauthorized 表示缺认证,403 Forbidden 表示权限不足,404 Not Found 表示资源不存在。这些该记录日志、跳过或走 fallback 逻辑,而不是反复请求。
立即学习“Python免费学习笔记(深入)”;
5xx 才是服务端临时故障,适合降级或重试:
-
500/502/503/504:可返回缓存数据、默认值或空结果 -
503响应头含Retry-After:必须解析并 sleep 对应秒数,不能硬编码间隔 - 降级不等于静默吞错:要明确记录
e.response.status_code和e.response.headers.get("Retry-After")
降级策略的典型实现方式
不是所有场景都适合同一套 fallback。根据业务目标选一种:
- 对关键字段缺失:返回
None或预设占位值(如"N/A"),避免后续解析报AttributeError - 对列表页失败:用本地缓存的上一次成功响应(需带时间戳校验,超 1 小时则弃用)
- 对详情页失败且有备用 API:切换到低精度接口(例如用摘要代替全文)
- 对登录态相关请求失败(如
401):触发重新登录流程,再重发原请求(注意避免死循环)
示例中不要写完整类,只聚焦降级动作:
except HTTPError as e:
if e.response.status_code == 503:
retry_after = e.response.headers.get("Retry-After")
if retry_after and retry_after.isdigit():
time.sleep(int(retry_after))
return {"status": "degraded", "data": []} # 降级返回空列表
elif e.response.status_code in (404, 403):
return {"status": "skipped", "reason": "resource_unavailable"}
else:
raise # 其他 4xx/5xx 不兜底,让上层处理容易忽略的边界点
HTTPError 的 e.response 可能为 None(极少数底层异常),直接访问 e.response.status_code 会抛 AttributeError。
真正稳定的写法是:
- 始终用
hasattr(e, "response") and e.response is not None做防御检查 - 不要依赖
e.code—— 它是 urllib 风格属性,在 requests 中不一定存在 - 降级逻辑里避免嵌套太多 try/except,否则掩盖真实错误类型
- 如果用了 session + cookie,降级后记得清理失效凭证(比如删掉
session.cookies.clear())


















