DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

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 仍应明确失败。还要检查上层代码,避免它继续对“无内容”结果直接读取业务字段。

保存原客户端封装和接口契约。回退客户端代码不会撤销已完成的服务器操作;若此前存在误重试,应根据业务记录核对影响,而不是再次调用写接口来验证解析逻辑。