Python Requests POST中data与json参数差异及Token接口调用问题
关于Requests库POST请求参数传递的问题解析
问题背景
之前以为对字典d来说,requests.post(url, data=json.dumps(d))和requests.post(url, json=d)是等价的,但调用某个Token接口时发现完全不是这么回事:
这个接口的文档定义如下:
- URL:
{{base_url}}/token - 参数:
grant_type={{password}}username={{username}}password={{password}}scope={"account":"{{account}}", "tenant":"{{tenant}}"}
我从.env加载变量后试了三种传参方式,结果完全不同:
- 用
json参数传完整字典,直接返回'{"error":"unsupported_grant_type"}'
resp = requests.post(f'{base_url}/token', json={'grant_type': 'password', 'username': uname, 'password': pwd, 'scope': {'account': account, 'tenant': tenant}})
- 用
data参数传完整字典,返回解析错误:'{"error":"invalid_grant","error_description":"{\\"ErrorMessage\\":\\"Error trying to Login - User [username] Account [] Unexpected character encountered while parsing value: a.'
resp = requests.post(f'{base_url}/token', data={'grant_type': 'password', 'username': uname, 'password': pwd, 'scope': {'account': account, 'tenant': tenant}})
- 混合方式:
data参数里只把scope的值用json.dumps序列化,请求成功了
resp = requests.post(f'{base_url}/token', data={'grant_type': 'password', 'username': uname, 'password': pwd, 'scope': json.dumps({'account': account, 'tenant': tenant})})
问题解答
1. 这种混合使用的差异是否真实存在?为何与之前看到的“要么用data=json.dumps(d)要么用json=d”的结论不同?
差异是真实存在的,核心原因在于这个接口要的不是纯JSON,也不是纯表单键值对,而是「表单键值对里嵌套JSON字符串」的混合格式:
- 用
json=d时,Requests会把整个字典转成JSON字符串,同时设置请求头Content-Type: application/json发送,但这个接口可能只认application/x-www-form-urlencoded类型的表单请求,所以压根识别不到grant_type参数,直接返回unsupported_grant_type。 - 用
data=d时,Requests会把字典转成表单键值对,但对于scope里的嵌套字典,它只会直接转成普通字符串(比如{'account': 'xxx', 'tenant': 'yyy'}会变成"{'account': 'xxx', 'tenant': 'yyy'}"),这种带单引号的非标准JSON,接口解析时直接报错,返回invalid_grant。 - 混合方式里,我们手动把
scope转成标准JSON字符串,再放到表单键值对里发送,既符合接口要求的表单请求类型,又保证scope参数是能正常解析的标准格式,所以请求成功。
之前的结论只适用于「接口只接受纯JSON」或者「接口只接受纯表单键值对」的场景,碰到这种混合格式的接口就不适用了。
2. 作为API新手,能否从文档看出这种参数要求,还是只能通过试错发现?
完全可以从文档里看出来,重点盯这两点:
- 先看接口的Content-Type要求:如果文档明确说要
application/x-www-form-urlencoded,那就是表单提交,不是JSON提交。 - 再看参数定义:文档里
scope的示例是{"account":"{{account}}", "tenant":"{{tenant}}"},这里的大括号是JSON格式,说明scope的值得是一个JSON字符串,不是嵌套的表单参数。
如果文档写得模糊,试错也是很正常的操作,毕竟不少接口文档都写得很随意。
3. 除了最终的解决方案,有没有更规范的参数传递方式?
有两种更严谨的方式:
- 方式一:显式指定Content-Type
虽然混合方式已经成功,但可以明确设置请求头的Content-Type为application/x-www-form-urlencoded,让请求意图更清晰:import requests import json payload = { 'grant_type': 'password', 'username': uname, 'password': pwd, 'scope': json.dumps({'account': account, 'tenant': tenant}) } resp = requests.post(f'{base_url}/token', data=payload, headers={'Content-Type': 'application/x-www-form-urlencoded'}) - 方式二:用urllib编码表单数据
对于复杂的表单参数,可以用urllib.parse.urlencode来编码,确保参数格式完全符合表单规范:import requests import json from urllib.parse import urlencode payload = { 'grant_type': 'password', 'username': uname, 'password': pwd, 'scope': json.dumps({'account': account, 'tenant': tenant}) } encoded_payload = urlencode(payload) resp = requests.post(f'{base_url}/token', data=encoded_payload, headers={'Content-Type': 'application/x-www-form-urlencoded'})
这两种方式都比直接传字典更稳妥,能避免Requests自动处理参数时可能出现的格式问题。
内容的提问来源于stack exchange,提问作者Hendy
相关产品推荐
相关产品推荐

