用Python requests发送JSON POST请求

requests.post(url, json=payload)只需一行代码即可向服务器发送JSON正文。json=参数序列化字典、设置Content-Type: application/json标头并写入请求正文。响应返回状态码,大多数API还会返回服务器存储的数据,让你确认调用达到了预期效果。这是REST API写入、Webhook以及任何期望JSON负载的端点背后的模式。

Python requests.post发送JSON示例

输出:

输出将显示在这里...

输出:

Status: 201
Server stored: {'language': 'Python', 'stars': 3, 'id': 101}

示例工作原理

  1. json=序列化dict并设置Content-Type: application/json,因此服务器接收到JSON对象而不是表单字段。
  2. timeout=10限制等待时间,使无法访问的端点快速失败而不是阻塞运行。
  3. raise_for_status()将4xx或5xx响应转换为异常,使失败的响应不会静默流向下一行。
  4. response.json()将响应作为Python dict返回。此测试API会返回你的字段并添加服务器分配的id;状态201 Created确认该调用被视为新资源。

验证服务器实际收到的内容

两个独立信号确认POST成功,可靠的检查需要同时使用两者。响应正文证明内容已到达:返回的字段加上新的id意味着服务器解析了你的JSON。Run Details证明传输:记录浏览器实际发送的请求。

Run Details信号确认的内容
一个POST到jsonplaceholder.typicode.com,状态201调用以POST方式发出且服务器创建了资源
方法列显示POST而非GETrequests.post发送了预期的HTTP方法
请求时间(ms)往返时间,与JSON解析时间分开测量
状态被阻止或失败,无HTTP代码请求未到达服务器(见下方CORS说明)
Packages列出requestsweb.run在导入时加载了requests

当状态码为2xx但返回正文中没有你的字段时,传输成功但内容未成功。这种区别是大多数POST教程无法展示的,因为它们从未将发送的请求和服务器的响应并排显示。

json vs data:数据静默丢失的Bug

data=替代json=发送相同的负载,服务器仍会以2xx响应,但返回正文中不会有你的字段。请求本身没问题;编码方式有误。

参数requests发送的内容服务器解析的内容
json={"stars": 3}JSON正文和Content-Type: application/jsonJSON对象
data={"stars": 3}表单编码正文和application/x-www-form-urlencoded表单字段,不是JSON
data=json.dumps({"stars": 3})没有JSON标头的原始JSON字符串可能拒绝解析为JSON的文本

规则:对任何期望JSON正文的API,将dict传给json=;只有在提交HTML表单时才使用data=。接收表单编码的JSON API通常会以2xx响应并静默丢弃数据,这就是仅检查状态码永远不够的原因。

浏览器POST被阻止而非被拒绝的情况

跨域POST在浏览器中的行为与服务器脚本不同。由于正文是application/json,浏览器会在POST之前发送CORS预检OPTIONS。如果端点不返回匹配的Access-Control-Allow-OriginAccess-Control-Allow-Headers,浏览器会在POST发出前将其阻止。

发生这种情况时,requests抛出requests.exceptions.ConnectionError之类的连接错误,而不是HTTPError,Run Details将请求标记为阻止或失败且没有HTTP状态。403或422是相反的信号:请求到达了服务器,服务器选择拒绝它。

规则:没有HTTP状态意味着服务器之前存在网络或CORS问题;有HTTP状态意味着服务器响应了,即使是以错误响应。jsonplaceholder支持CORS,因此示例可以正常发送。许多公共API不支持CORS,这就是为什么相同的requests.post代码在终端可以成功但在此运行时或任何浏览器内Python中可能失败。

常见错误

错误:当API期望JSON时用data=发送dict。

错误示例:

requests.post(url, data={"stars": 3})

正确示例:

requests.post(url, json={"stars": 3})

原因:data=对dict进行表单编码并设置表单Content-Type,因此JSON API接收不到JSON对象,你的字段从结果中消失。

错误:将正文序列化两次。

错误示例:

requests.post(url, json=json.dumps({"stars": 3}))

正确示例:

requests.post(url, json={"stars": 3})

原因:json=已经序列化该值。传入预构建的字符串会将其双重编码为JSON字符串而不是对象。

向POST添加标头和认证

大多数写入API需要认证,通过请求标头与正文一起发送:

response = requests.post(
    url,
    json=payload,
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)

requests将你的标头与json=设置的Content-Type合并,因此你只需提供API特定的标头。永远不要在共享代码中硬编码真实token;从环境变量读取。Authorization这样的自定义标头本身就足以触发上述的CORS预检。读取方面请参阅从URL读取JSON,并将调用包装在try/except中以处理连接错误。

FAQ

如何在Python中发送POST请求?

以字典作为负载调用requests.post(url, json=payload)json=参数序列化dict,设置Content-Type: application/json标头,并在一次调用中完成发送。用response.json()读取响应。

为什么我的POST返回200但服务器忽略了数据?

正文以错误格式发送,通常是通过data=作为表单字段而不是通过json=作为JSON。服务器对请求本身以2xx响应,但从不解析JSON对象。切换到json=并确认服务器返回了你的字段。

为什么我的requests POST在浏览器中被阻止?

跨域JSON POST触发CORS预检。如果端点不返回Access-Control-Allow-OriginAccess-Control-Allow-Headers,浏览器会在请求发出前将其阻止;requests抛出没有HTTP状态的连接错误而不是4xx。相同的代码在终端可能仍然有效。

requests.post中json和data的区别是什么?

json=将dict序列化为JSON正文并设置Content-Type: application/jsondata=发送表单编码字段或没有JSON标头的原始字符串。对JSON API使用json=,对HTML表单提交使用data=