用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}
示例工作原理
json=序列化dict并设置Content-Type: application/json,因此服务器接收到JSON对象而不是表单字段。timeout=10限制等待时间,使无法访问的端点快速失败而不是阻塞运行。raise_for_status()将4xx或5xx响应转换为异常,使失败的响应不会静默流向下一行。response.json()将响应作为Python dict返回。此测试API会返回你的字段并添加服务器分配的id;状态201 Created确认该调用被视为新资源。
验证服务器实际收到的内容
两个独立信号确认POST成功,可靠的检查需要同时使用两者。响应正文证明内容已到达:返回的字段加上新的id意味着服务器解析了你的JSON。Run Details证明传输:记录浏览器实际发送的请求。
| Run Details信号 | 确认的内容 |
|---|---|
| 一个POST到jsonplaceholder.typicode.com,状态201 | 调用以POST方式发出且服务器创建了资源 |
| 方法列显示POST而非GET | requests.post发送了预期的HTTP方法 |
| 请求时间(ms) | 往返时间,与JSON解析时间分开测量 |
| 状态被阻止或失败,无HTTP代码 | 请求未到达服务器(见下方CORS说明) |
| Packages列出requests | web.run在导入时加载了requests包 |
当状态码为2xx但返回正文中没有你的字段时,传输成功但内容未成功。这种区别是大多数POST教程无法展示的,因为它们从未将发送的请求和服务器的响应并排显示。
json vs data:数据静默丢失的Bug
用data=替代json=发送相同的负载,服务器仍会以2xx响应,但返回正文中不会有你的字段。请求本身没问题;编码方式有误。
| 参数 | requests发送的内容 | 服务器解析的内容 |
|---|---|---|
json={"stars": 3} | JSON正文和Content-Type: application/json | JSON对象 |
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-Origin和Access-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-Origin和Access-Control-Allow-Headers,浏览器会在请求发出前将其阻止;requests抛出没有HTTP状态的连接错误而不是4xx。相同的代码在终端可能仍然有效。
requests.post中json和data的区别是什么?
json=将dict序列化为JSON正文并设置Content-Type: application/json。data=发送表单编码字段或没有JSON标头的原始字符串。对JSON API使用json=,对HTML表单提交使用data=。