1. 内容
主要讨论使用requests.post函数的参数data、json和files来发送不同类型的数据或者文件。
2. 环境
python:3.9.12
requests:2.20.0
3. requests.post
主要关注post请求中的data、json以及files参数。首先,查看requests.data的源码,data和json参数的内容如下:
def post(url, data=None, json=None, **kwargs):
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('post', url, data=data, json=json, **kwargs)
继续查看requests.request的源码,主要关注参数files的说明,如下:
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
...
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
to add for the file.
"""
在了解data、json以及files参数的取值范围之后,接下来就是以实际的例子来说明分别识别data、json以及files发送数据的情况。
4. data
通过requests.data参数可以看到,data的参数支持4种类型,分别为Dictionary、list of tuples、bytes、file-like object,这里主要关注使用Dictionary、list of tuples这两种方式发送数据时的请求方式、请求信息和响应结果信息。剩余的bytes、file-like object会在之后遇到实际应用时补充。
4.1. Dictionary
首先,使用字典的形式提交数据,如下所示:
- 定义一个Python字典:data,并以键值对的形式说明需要上传的数据。
- 通过输出请求头的body信息res.request.body发现,其取值为name=Tom&age=18.
- 使用data传递Dictionary类型的键值对时,此时的Content-Type默认为application/x-www-form-urlencoded
import requests
url = "http://httpbin.org/post"
data = {
"name": "Tom",
"age": 18
}
res = requests.post(url, data=data)
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
print("响应头", res.headers, type(res.headers))
print("响应体", res.text, type(res.text))
输出结果为:
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '15', 'Content-Type': 'application/x-www-form-urlencoded'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 name=Tom&age=18 <class 'str'>
响应头 {'Date': 'Tue, 17 May 2022 11:12:41 GMT', 'Content-Type': 'application/json', 'Content-Length': '496', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true'} <class 'requests.structures.CaseInsensitiveDict'>
响应体 {
"args": {},
"data": "",
"files": {},
"form": {
"age": "18",
"name": "Tom"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "15",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.20.0",
"X-Amzn-Trace-Id": "Root=1-62838329-3e9cc2f465f24f73694a0f1a"
},
"json": null,
"origin": "58.48.122.206",
"url": "http://httpbin.org/post"
}
<class 'str'>
4.1.1. 源码-分析requests是如何对传入的data数据进行处理的
首先,使用pycharm工具,一步步查看代码调用顺序,找到修改data数据的代码即可,这里可自己查看的调用顺序整理如下:(api.py::post带包api.py文件中的post函数)
调用requests.post时,此时相当于执行了requests/api.py::post函数
接着调用requests/api.py::request函数
接着会调用requests/sessions.py::request函数
在该函数中执行了这个语句prep = self.prepare_request(req),继续查看prepare_request()函数
在该函数中调用了requests/models.py/prepare函数
接着在该函数中调用了requests/models.py/prepare_body(data, files, json),可以看到这里对data数据进行了处理。
这里主要关注models.py文件中的prepare_body和_encode_params函数,下面列举出该函数的部分内容,如下:
- 在prepare_body函数中,此时当data存在值的时候,会调用self._encode_params(data),也就是在调用_encode_params的时候将
data = {"name": "Tom","age": 18}
转化为了name=Tom&age=18。(这里就不在深究requests是如何实现的了)
def prepare_body(self, data, files, json=None):
"""Prepares the given HTTP body data."""
# Check if file, fo, generator, iterator.
# If not, run through normal process.
# Nottin' on you.
body = None
content_type = None
if is_stream:
...
else:
...
else:
if data:
body = self._encode_params(data)
if isinstance(data, basestring) or hasattr(data, 'read'):
content_type = None
else:
content_type = 'application/x-www-form-urlencoded'
4.2. list of tuples
使用元组列表构建一个data,然后传递给post请求的data参数中,如下所示:
- 使用元组构建data的方式也比较简单,其实就是将data转化为list,每个键值对变成list中的一个元组,即可以通过
list(zip(data.keys(), data.values()))
将data转化为元组列表。
import requests
url = "http://httpbin.org/post"
data = [("name", "Tom"), ("age", 18)]
res = requests.post(url, data=data)
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
输出结果如下,可以发现与使用Dictionary的方式一样。
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '15', 'Content-Type': 'application/x-www-form-urlencoded'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 name=Tom&age=18 <class 'str'>
5. json
通过requests.post中对json参数的说明:
:param json: (optional) json data to send in the body of the :class:`Request`.
在python中,不存在json数据类型。在使用json参数的时候,可以直接传递一个Dictionary类型的数据,requests会自动将其转化为json并使用utf-8编码为字节。比如下面的例子:
- 将Dictionary类型的数据传递给json参数,此时输出请求体res.request.body为
b'{"name": "Tom", "age": 18}'
,即requests将字典转化为了json字符串并编码为字节了。 - 使用json发送数据的时候,此时默认采用的content-type为application/json(可以通过res.request.headers看到请求采用的content-type类型)
import requests
import json
url = "http://httpbin.org/post"
data = {
"name": "Tom",
"age": 18
}
res = requests.post(url, json=data)
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
输出结果为:
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '26', 'Content-Type': 'application/json'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 b'{"name": "Tom", "age": 18}' <class 'bytes'>
如上,res.request.body返回的是一个bytes类型。这里可以通过查看源码来看requests在接收到json参数传递的数据之后做了哪些操作。
5.1. 源码-分析requests是如何对传入的json数据进行处理的
和前面分析data数据一样,这里直接查看requests/models.py/prepare_body函数,该函数的部分内容如下所示:
def prepare_body(self, data, files, json=None):
"""Prepares the given HTTP body data."""
# Check if file, fo, generator, iterator.
# If not, run through normal process.
# Nottin' on you.
body = None
content_type = None
if not data and json is not None:
# urllib3 requires a bytes-like body. Python 2's json.dumps
# provides this natively, but Python 3 gives a Unicode string.
content_type = 'application/json'
body = complexjson.dumps(json)
if not isinstance(body, bytes):
body = body.encode('utf-8')
如上,可以看到当data为空且json不为None的时候,此时设置了请求类型为application/json,同时将参数转化为json字符串,并使用utf-8编码为bytes类型。
6. files
首先,根据requests.request函数中的参数说明(具体可以参考第3节),对于files参数,可以有以下几种类型:
- Dictionary of
'name': file-like-objects
- {‘name’: file-tuple}:file-tuple又可以包含多种,比如:
- 2-tuple:分别是filename, fileobj
- 3-tuple:分别是filename, fileobj, content_type
- 4-tuple:分别是filename, fileobj, content_type, custom_headers
注意:
- 这里的name相当于前端通过form表单上传文件时,input字段的name的取值,即后台会根据这里的name获取上传的文件。
<input type="file" name="file" id="file">
6.1. 采用{'name', file-like-objects}
上传文件
#coding=utf-8
# 采用xampp搭建的真实服务测试过,可以上传文件成功
import requests
url = "http://httpbin.org/post"
files = {
"file": open("./second_module.py", "rb")
}
res = requests.post(url, files=files)
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
print("响应头", res.headers, type(res.headers))
print("响应体", res.text, type(res.text))
输出结果如下:
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '175', 'Content-Type': 'multipart/form-data; boundary=60676effe8eb282f2238484ce8c733d7'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 b'--60676effe8eb282f2238484ce8c733d7\r\nContent-Disposition: form-data; name="file"; filename="second_module.py"\r\n\r\nimport first_module\r\n\r\n\r\n--60676effe8eb282f2238484ce8c733d7--\r\n' <class 'bytes'>
响应头 {'Date': 'Wed, 18 May 2022 09:33:48 GMT', 'Content-Type': 'application/json', 'Content-Length': '532', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true'} <class 'requests.structures.CaseInsensitiveDict'>
响应体 {
"args": {},
"data": "",
"files": {
"file": "import first_module\r\n\r\n"
},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "175",
"Content-Type": "multipart/form-data; boundary=60676effe8eb282f2238484ce8c733d7",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.20.0",
"X-Amzn-Trace-Id": "Root=1-6284bd7c-4834506703ccab59771b3319"
},
"json": null,
"origin": "58.48.122.206",
"url": "http://httpbin.org/post"
}
<class 'str'>
如上:
- 使用files上传文件时,此时默认的content-type类型为:multipart/form-data; boundary=60676effe8eb282f2238484ce8c733d7,即会自动生成1个随机的boundary。
- 请求体中包含如下内容.
b'--60676effe8eb282f2238484ce8c733d7\r\nContent-Disposition: form-data; name="file"; filename="second_module.py"\r\n\r\nimport first_module\r\n\r\n\r\n--60676effe8eb282f2238484ce8c733d7--\r\n'
6.2. 采用{'name': file-tuple}
的形式上传文件
#coding=utf-8
# 采用xampp搭建的真实服务测试过,可以上传文件成功
import requests
import mimetypes
url = "http://httpbin.org/post"
filepath = "./second_module.py"
# 猜测文件类型
file_type = mimetypes.guess_type("./second_module.py")[0] # image/jpeg
files = {
# 元组中第一个参数:代表实际上传服务器时使用的文件名,没有传入该参数的时候,实际上传之后的filename是open函数中打开的文件的文件名。
# 元祖第二个参数:就是一个文件对象
# 元祖第三个参数:文件类型,这里通过使用mimetypes来猜测文件类型。
# 元祖第四个参数:自定义的一些头部信息
"file": ("new_filename.py", open(filepath, "rb"), file_type, {'Expires':'0'})
}
res = requests.post(url, files=files)
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
此时的输出结果为:
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '215', 'Content-Type': 'multipart/form-data; boundary=98ef3ae8047229f3a0a34c3ad0b51004'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 b'--98ef3ae8047229f3a0a34c3ad0b51004\r\nContent-Disposition: form-data; name="file"; filename="new_filename.py"\r\nContent-Type: text/x-python\r\nExpires: 0\r\n\r\nimport first_module\r\n\r\n\r\n--98ef3ae8047229f3a0a34c3ad0b51004--\r\n' <class 'bytes'>
6.3. 上传多个文件
import requests
import mimetypes
url = "http://httpbin.org/post"
filepath = "./second_module.py"
file_type = mimetypes.guess_type("./second_module.py")[0] # image/jpeg
files = [
("file", ("new_filename1.py", open(filepath, "rb"), file_type, {'Expires':'0'})),
("file2", ("new_filename2.py", open(filepath, "rb"), file_type, {'Expires':'0'})),
]
res = requests.post(url, files=files)
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
输出结果如下:
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '394', 'Content-Type': 'multipart/form-data; boundary=fe0a1f7e51c9795b4665897765267b7c'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 b'--fe0a1f7e51c9795b4665897765267b7c\r\nContent-Disposition: form-data; name="file"; filename="new_filename1.py"\r\nContent-Type: text/x-python\r\nExpires: 0\r\n\r\nimport first_module\r\n\r\n\r\n--fe0a1f7e51c9795b4665897765267b7c\r\nContent-Disposition: form-data; name="file"; filename="new_filename2.py"\r\nContent-Type: text/x-python\r\nExpires: 0\r\n\r\nimport first_module\r\n\r\n\r\n--fe0a1f7e51c9795b4665897765267b7c--\r\n' <class 'bytes'>
说明:
- 上传多个文件时,可以使用上面例子中提到的元组列表的形式,也可以使用下面的形式
files = {
"file": ("new_filename3.py", open(filepath, "rb"), file_type, {'Expires':'0'}),
"file2": ("new_filename4.py", open(filepath, "rb"), file_type, {'Expires':'0'}),
}
7. 提交application/json类型的数据的两种方式
7.1. 方式1:使用data参数
此时需要使用json.dumps(data)将Dictionary类型转换为json字符串,并在请求头中添加content-type字段类型为application/json
import requests
url = "http://httpbin.org/post"
data = {
"name": "Tom",
"age": 18
}
res = requests.post(url, data=json.dumps(data), headers={"content-type":"application/json"})
print("请求头", res.request.headers, type(res.request.headers))
print("请求体", res.request.body, type(res.request.body))
此时的输出结果如下:
请求头 {'User-Agent': 'python-requests/2.20.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'content-type': 'application/json', 'Content-Length': '26'} <class 'requests.structures.CaseInsensitiveDict'>
请求体 {"name": "Tom", "age": 18} <class 'str'>
7.2. 方式2:使用json参数
直接参考第5节。