应使用 io.bytesio 在内存中构建 zip,避免写临时文件;需调用 seek(0),用 writestr 写入内容,设置正确响应头(application/zip + attachment),中文名需手动设 utf-8 标志,大文件注意内存与超时风险。

用 zipfile 在内存中构建 ZIP,避免写临时文件
Python 后端生成多文件 ZIP 时,最常见错误是先写到磁盘再读取返回——这不仅慢,还可能因并发或权限出错。直接在内存中打包是标准做法,核心就是用 io.BytesIO 替代磁盘路径。
-
zipfile.ZipFile的第一个参数必须是类文件对象(BytesIO实例),不能传字符串路径 - 记得在写入后调用
zip_buffer.seek(0),否则后续读取会从末尾开始,返回空内容 - 如果文件内容来自数据库或网络,别直接
zip_file.write()路径——它只接受本地文件;改用zip_file.writestr()写入 bytes 或 str
Django/Flask 中返回 ZIP 流的正确响应头设置
浏览器能否正确下载 ZIP,不取决于你有没有压缩,而取决于响应头是否明确告诉它“这是个二进制附件”。漏掉或写错 Content-Type 和 Content-Disposition,会导致页面乱码或自动解压失败。
-
Content-Type必须设为application/zip,不是application/octet-stream(虽能用,但语义弱,部分代理会拦截) -
Content-Disposition推荐用attachment; filename="files.zip",双引号包裹文件名,避免中文名被截断 - Flask 中用
send_file(zip_buffer, mimetype="application/zip", as_attachment=True, download_name="files.zip");Django 则需手动设置response['Content-Disposition']
处理中文文件名时的编码陷阱
ZIP 标准本身对中文支持差,zipfile 默认用 CP437 编码存文件名,Windows 解压正常,macOS/Linux 常显示乱码。这不是 Python bug,而是 ZIP 规范历史遗留问题。
- Python 3.6+ 支持
zipfile.ZipFile的strict_timestamps=False参数,但它不解决文件名编码——真正有效的是用zipfile.ZIP_DEFLATED+ 手动编码转换 - 稳妥方案:把中文文件名转成 UTF-8 bytes,再用
zipfile.ZipInfo设置filename和flag_bits = 0x800(表示 UTF-8) - 注意:旧版 macOS 自带解压工具不识别
0x800标志,若用户群体含大量老系统,建议统一用英文文件名或拼音替代
大文件打包时的内存与超时风险
所有文件内容都加载进 BytesIO,意味着内存占用 ≈ 所有文件原始大小之和。10 个 50MB 文件 = 至少 500MB 内存峰值,容易触发 OOM 或被 Nginx/Cloudflare 中断。
立即学习“Python免费学习笔记(深入)”;
- 不要一次性读完大文件再
writestr;改用分块读取 +zip_file.writestr传入 bytes,或更优:用zip_file.write配合临时NamedTemporaryFile(仅当必须支持超大单文件时) - Gunicorn/Uvicorn 默认 worker 超时是 30 秒,打包耗时超过这个值会直接断连;需同步调高
--timeout和反向代理(如 Nginx)的proxy_read_timeout - 真正海量文件(如上万小文件),考虑改用流式 ZIP 库如
stream-zip,它不缓存整个 ZIP,但不兼容所有客户端(特别是旧 IE)
文件名编码和内存水位是线上最容易翻车的两个点,前者导致用户打不开,后者让服务突然 502——检查时优先盯死这两处。










