multipart/form-data
常用于文件上传,其中,post请求中的参数为files
以下是参数详细解释:
1. 代码整体
其中核心代码为,获取文件对象,拼接files参数:
with open(file_path, 'rb') as f:
files = {
'file': (
Path(file_path).name,
f,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
),
}
response = requests.post(
url,
headers=headers,
files=files,
)
整个过程可以拆分为:
open 打开文件
⬇️
得到文件对象 f
⬇️
构造 files 字典
⬇️
requests 读取文件内容
⬇️
生成 multipart/form-data
⬇️
发送 http 请求
2. with open(...,'rb') as f到底是什么?
其中:
f = open(file_path,'rb')
这里的 f 不是文件内容
这里的区别需要额外注意!!
f实际上是:
文件对象(File Object) 或者说叫 文件句柄(File Handle)
打印可以得到:
f = open('123.xlsx', 'rb')
print(f.__class__) # <class '_io.BufferedReader'>
print(type(f)) # <class '_io.BufferedReader'>
print(f) # <_io.BufferedReader name='123.xlsx'>
f.close() # 代码中 with 可以自动关闭文件,这里需要手动关闭
它本质上是一个 python对象
2.1 文件对象里面保存了什么?
可以想象成:
f
|-- 文件路径
|-- 打开模式(rb)
|-- 当前读取职位
|-- 操作系统文件描述符
|-- read() 方法
等等,
例如:
文件名:f.name 得到:report.xlsx,注意,当open里面的文件是绝对路径时,f的name方法也返回绝对路径
当前光标:f.tell() 得到 0
读取:content = f.read(10) 读取 前10个字节
手动打开需要手动关闭:
f.close()
所以,f不是Excel 文件 而是“如何读取 Excel 文件”的对象
3. 为什么要用 "rb" ?
在代码中:
open(file_path,'rb')
这里:
r表示:read,只读;
b表示:binary,二进制模式。
Excel文件里面实际上长这样:
50 4B 03 04 ...
而不是:
姓名:张三 ...
所以上传:
- Excel
- 图片
- ZIP
都必须使用二进制读取,rb
否则会导致:UnicodeDecodeError 或者 文件损坏。
4. files 到底是什么?
在代码中:
files = {
'file': (
Path(file_path).name,
f,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
}
它并不是普通参数。
它专门表示:
multipart/form-data
也就是:
<form enctype="multipart/form-data">
表单数据;
requests 支持三种写法:
- 写法一
files = {
'file' : open('a.xlsx','rb')
}
- 写法二,指定文件名
files = {
'file' : (
'报表.xlsx',
open('a.xlsx','rb'),
)
}
- 写法三,完整写法
files = {
'file' : (
'报表.xlsx',
open('a.xlsx','rb'),
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
}
即:file : 文件名,文件对象,MIME 类型
5. 这个三元组到底什么意思?
代码中:
(
Path(file_path).name,
f,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
5.1 第一项:文件名
Path(file_path).name
假设:
file_path = "/tmp/report.xlsx"
得到:report.xlsx
服务器收到:filename = "report.xlsx"
5.2 第二项:文件对象
f
requests内部:f.read()
读取内部真实数据。
5.3 第三项:MIME 类型
告诉服务器:
这是什么文件。
代码中:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
表示:xlsx
常见的MIME 类型:
| 文件 | MIME |
|---|---|
| txt | text/plain |
| json | application/json |
| jpg | image/jpeg |
| png | image/png |
| application/pdf | |
| xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
6. requests 最终发送的 HTTP 长什么样?
代码中:
files = {
'file' : ( ... )
}
会被 requests 自动转换成:
POST /upload HTTP/1.1
Content-Type:
multipart/form-data;
boundary=----abcd123
请求体:
------abc123
Content-Disposition:
form-data;
name="file";
filename="report.xlsx"
Content-Type:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
<Excel二进制数据>
------abc123--
7. 为什么字段名是"file"?
这一句:
files = {
'file': ...
}
里面:'file'
非常重要,他并不是随便写的,需要和后端保持一致,
例如 Django中:
request.FILES['file']
那么前端就必须传入相同的字段名:
files = {
‘file': ...
}
如果 Django:
request.FILES['avatar']
那么,python:
files = {
'avatar': ...
}
8. request 内部到底做了什么?
实际上内部流程是:
files
↓
requests.models.RequestEncodingMixin
↓
_encode_files()
↓
urllib3.filepost.encode_multipart_formdata()
↓
生成 boundary
↓
读取 f.read()
↓
拼接 HTTP
↓
socket.send()
最终流程:
requests
↓
urllib3
↓
http.client
↓
socket
↓
TCP
发送到服务器
9. 最容易犯的错误
9.1 错误 1
不要:
files = {
'file': file_path
}
因为:
字符串 ≠ 文件
9.2 错误 2
不要:
open(file_path,'r')
读取模式必须:rb,原因上面已经解释
9.3 错误 3
不要自己写:
headers = {
'Content-Type':
"multipart/form-data"
}
因为,requests会自动生成:
boundary=xxxxxx
如果你自己写:
multipart/form-data
没有 boundary ,服务器会返回错误:
Invalid boundary
9.4 错误 4
请注意区别,在参数中 files= 和 data= 提交数据的区别,其中:
files= -> multipart/form-data
data= -> application/x-www-form-urlencoded