在使用 Python 的 requests 库上传文件时,如果文件名包含中文,你可能会遇到一个诡异的问题:
filename*=utf-8''...)。即便你尝试了 encode('utf-8').decode('latin-1') 等常规编码转换,问题依然可能存在。
现代版本的 requests(及其底层的 urllib3)为了遵循 RFC 2231 标准,在发现文件名包含非 ASCII 字符(如中文)时,会自动将 Content-Disposition 里的 filename 参数转换成如下格式:
HTTPContent-Disposition: form-data; name="file"; filename*=utf-8''%E4%B8%AD%E6%96%87.xlsx
然而,很多现有的后端框架(如旧版 Spring、PHP 或某些 Nginx 配置)并不支持 filename*= 这种语法。它们只认最原始的:
HTTPContent-Disposition: form-data; name="file"; filename="中文.xlsx"
由于识别不到 filename 这个 Key,服务器会直接丢弃该文件部分,导致报错或获取不到文件。
既然 urllib3 内部强制执行了新标准,而我们又无法轻易修改第三方库的源代码,最好的办法就是通过 Monkey Patch,在程序运行时动态替换 urllib3 格式化 Header 参数的函数。
在你的请求脚本最上方(import requests 之后),加入以下代码:
Pythonimport urllib3.fields
def format_multipart_header_param(name: str, value) -> str:
"""
自定义格式化函数:
1. 强制不使用 RFC 2231 的 filename*= 语法。
2. 保持原生的 filename="xxx" 格式。
3. 对特殊字符 (\n, \r, ") 进行转义处理。
"""
if isinstance(value, bytes):
value = value.decode("utf-8")
# 对文件名中的换行符、回车符和双引号进行百分号编码,防止 Header 解析截断
# 10 -> \n, 13 -> \r, 34 -> "
value = value.translate({10: "%0A", 13: "%0D", 34: "%22"})
return f'{name}="{value}"'
def patched_format_header_param(name: str, value) -> str:
return format_multipart_header_param(name, value)
# 核心:替换 urllib3 内部字段的处理函数
urllib3.fields.format_header_param = patched_format_header_param
# --- 之后正常使用 requests 即可 ---
import requests
files = {'file': ('2026年3月生日礼.xlsx', open('test.xlsx', 'rb'))}
response = requests.post("http://example.com/upload", files=files)
在处理 HTTP 协议相关的坑时,有时候“标准”反而是障碍。当后端服务器比较传统时,通过 Monkey Patch 强行回归到简单直接的 Header 格式往往是最有效的手段。
注意: 此方案适用于 requests 2.x 系列。如果在极个别环境下仍然乱码,请确认服务器端的默认解码字符集(如 GBK 或 UTF-8)。
本文作者:Dewar
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!