Python Requests 收到 204 后调用 json 报 JSONDecodeError
按接口契约区分 204 无正文和异常空响应,不把 JSON 解码错误吞成空对象,也不因解析失败重复发送写请求。
接口返回 204 No Content,业务操作已经成功,Python 却在 response.json() 处报错:
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
204 表示这次响应没有内容。空响应体不是 JSON 文档;它既不等于 {},也不等于 JSON 的 null。response.json() 会尝试解析正文,因此成功的 HTTP 状态与 JSON 解码失败可以同时出现。
下面的异常捕获写法适用于 Requests 2.27.0 及更新版本,该版本开始提供统一的 requests.exceptions.JSONDecodeError。不要因为旧版 traceback 显示标准库或 simplejson 的异常名,就认为空正文能被解析成 JSON。
先区分预期无正文与异常响应
核对接口约定、status_code 和正文长度。记录诊断信息时避免打印包含令牌、个人信息的完整响应及请求头。收到 204 且接口明确允许它时,应走“成功但无内容”分支,不调用 JSON 解析。
如果收到的是 200 但正文为空,或返回了 HTML 登录页、代理错误页,应继续排查接口与请求链路。这些情况不能因为异常文字相似,就一律变成成功的空对象。
raise_for_status() 用于发现 HTTP 错误状态;它不验证 JSON,也不保证响应状态符合你的接口契约。反过来,错误状态返回的 JSON 也可能解析成功,不能只用 json() 是否报错判断业务成功。
在统一响应处理处显式分支
下面的函数只处理已经取得的 Response,不发送或重复请求。示例接口约定允许 200 返回 JSON,或者 204 不返回正文:
import requests
def decode_api_response(response):
response.raise_for_status()
if response.status_code == 204:
return {"has_content": False, "data": None}
if response.status_code != 200:
raise ValueError("Unexpected successful response status")
if not response.content:
raise ValueError("Expected JSON body, received empty content")
media_type = response.headers.get("Content-Type", "").split(";", 1)[0]
media_type = media_type.strip().lower()
if media_type != "application/json":
raise ValueError("Expected application/json response")
try:
data = response.json()
except requests.exceptions.JSONDecodeError as exc:
raise ValueError("Response body is not valid JSON") from exc
return {"has_content": True, "data": data}
这里用 has_content 区分无正文与正文为 JSON null 的情况。后者解析得到 Python None,但仍是实际收到的 JSON 内容。若应用已有结果类型,应把这个区别纳入自己的返回契约,而不是直接把所有异常替换成 {}。
成功状态与媒体类型应按真实接口调整。例如接口定义了 201,或明确使用 application/problem+json 等类型,就应显式纳入对应分支;不要把示例中的 200 和 application/json 当成所有 API 的固定规范。即使媒体类型正确,正文也可能损坏,所以仍需保留解码失败处理。
不要因解析失败重放写请求
删除、创建或修改接口可能已经完成操作,只是在客户端读取响应时失败。此时直接重试原请求,可能重复创建数据或触发其他副作用。
先按接口约定检查操作后的状态,必要时使用既有的请求标识或幂等机制核对。网络超时、HTTP 错误、JSON 解码失败应分别记录,不能把它们合并为一个无条件重试分支。
核对调用端是否仍假定总有字典
修复后,204 分支不进入 JSON 解码;200 的 {} 和 null 都作为有内容返回;200 空正文、HTML 或损坏 JSON 仍应明确失败。还要检查上层代码,避免它继续对“无内容”结果直接读取业务字段。
保存原客户端封装和接口契约。回退客户端代码不会撤销已完成的服务器操作;若此前存在误重试,应根据业务记录核对影响,而不是再次调用写接口来验证解析逻辑。


