Requests上传文件需用rb模式打开、显式指定Content-Type、按规范构造files参数,单文件混表单字段用(None,value)元组,多文件同名用列表,避免自动推断MIME类型错误。

直接用 files 参数传字典,但必须注意二进制模式打开文件、Content-Type 自动推断的边界和 multipart 的字段顺序。
上传单个二进制文件(带表单字段)
Requests 的 files 参数支持混合上传:既可传文件句柄,也能塞进普通表单字段。关键不是“能不能”,而是“怎么塞才不被后端拒绝”。
- 文件必须以
rb模式打开,传bytes或io.BytesIO对象,不能传字符串或文本模式句柄 - 表单字段若混在
files里,需写成(field_name, (None, value))形式,否则 Requests 会当成文件处理 - 推荐显式指定
Content-Type,尤其当后端校验严格时:(None, "value", "text/plain")
import requests
with open("photo.jpg", "rb") as f:
files = {
"image": ("photo.jpg", f, "image/jpeg"),
"user_id": (None, "12345"),
"category": (None, "avatar", "text/plain"),
}
r = requests.post("https://api.example.com/upload", files=files)
上传多个同名文件(如多张图片)
后端常通过相同字段名接收多个文件(例如 files[] 或 images),Requests 不支持直接用字典键重复,得用列表 + 元组结构。
- 每个元组格式为
(field_name, (filename, file_obj, content_type)) - 如果某文件不需要 filename,第二个元素用
(None, data);但多数 API 要求 filename 非空 - 不要把多个文件塞进一个
files字典值里——那只会传第一个
files = [
("images", ("a.jpg", open("a.jpg", "rb"), "image/jpeg")),
("images", ("b.png", open("b.png", "rb"), "image/png")),
]
r = requests.post(url, files=files)
绕过自动 Content-Type 推断(避免 MIME 错误)
Requests 默认根据扩展名猜 Content-Type,但有些文件没后缀,或后端只认特定类型(如强制要求 application/octet-stream)。此时不能依赖 files 的三元组自动推断。
立即学习“Python免费学习笔记(深入)”;
- 显式传
content_type是最稳的方式,哪怕只是"application/octet-stream" - 如果连 filename 都不想暴露,用
(None, data, "application/octet-stream") - 别试图手动拼
multipart/form-databody——Requests 的files已封装边界逻辑,自己拼极易出错
with open("data.bin", "rb") as f:
files = {
"payload": (None, f.read(), "application/octet-stream")
}
r = requests.post(url, files=files)
常见失败原因与调试技巧
上传失败往往不是代码写错,而是 multipart 边界、字段顺序或编码细节没对上。
- 抓包看请求体:用
curl -v或 Wireshark 对比成功/失败请求,重点核对Content-Disposition字段是否含filename=、name=是否匹配后端预期 - 后端报 “invalid multipart” 多半是字段名大小写不一致,或某个字段漏了
name= - 文件内容为空?检查是否多次读取了同一个
BytesIO或已关闭的文件句柄 - 超时或连接重置?大文件记得加
timeout=和stream=True,但注意files本身不支持流式上传(整个 body 还是得先构造完)
multipart 的坑不在语法,而在字段语义是否和后端约定完全一致——尤其是那些不写文档、靠试出来的 API。


















