Python zipfile模块解压中文文件名乱码的根本原因是ZIP编码不统一:Windows常用GBK而Python默认用CP437或UTF-8解析,解决方案包括Python 3.11+使用metadata_encoding='utf-8'参数,或手动对ZipInfo.filename进行GBK转UTF-8编码处理。

Python 的 zipfile 模块开箱即用,无需安装第三方包,能可靠完成大多数 ZIP 压缩与解压任务——但默认行为容易踩坑,比如中文路径乱码、覆盖写入、目录结构丢失等。
压缩文件时如何避免中文路径乱码
Windows 默认用 GBK 编码写入 ZIP 文件名,而 Python 3.7+ 的 zipfile.ZipFile 默认按 UTF-8 解析,导致解压后文件名变成乱码。根本原因不是“不支持中文”,而是编码不一致。
- 写入 ZIP 时强制指定
zipfile.ZIP_DEFLATED压缩方式,并用ZipInfo手动设置filename字段的编码(仅限 Windows) - 更稳妥的做法:统一用
pathlib.Path处理路径,对每个待添加文件创建zipfile.ZipInfo实例,把filename显式设为 UTF-8 编码字节串再 decode('utf-8')(实际是绕过内部编码逻辑) - 简单场景下可改用
shutil.make_archive,它在 Windows 上会自动适配系统编码
示例(安全写入中文路径):
from zipfile import ZipFile, ZipInfo
import os
<p>with ZipFile("out.zip", "w") as zf:
for file in ["测试.txt", "data/子目录/说明.md"]:
if os.path.exists(file):
zinfo = ZipInfo(file)
zinfo.filename = file # Python 3.11+ 已修复,旧版本需 zinfo.filename = file.encode('utf-8').decode('latin1')
with open(file, "rb") as f:
zf.writestr(zinfo, f.read())
</p>
解压 ZIP 时如何保留完整目录结构
直接调用 zipfile.ZipFile.extractall() 通常能保留层级,但若 ZIP 包内路径含驱动器符(如 C:\a.txt)或绝对路径(/home/user/x.py),部分旧版 Python 会静默忽略前缀,造成文件被解到当前目录甚至被覆盖。
- 务必先检查
ZipFile.namelist(),过滤掉以/开头或含..的路径(防路径遍历) - 手动调用
extract()而非extractall(),对每个ZipInfo的filename做os.path.normpath()标准化 - 解压目标路径必须显式传给
extract()或extractall()的path参数,不能依赖当前工作目录
示例(安全解压):
import os
from zipfile import ZipFile
<p>def safe_extract(zip_path, target_dir):
os.makedirs(target_dir, exist_ok=True)
with ZipFile(zip_path) as zf:
for member in zf.filelist:</p><h1>过滤危险路径</h1><pre class="brush:php;toolbar:false;"> if member.filename.startswith(("/", "\")) or ".." in member.filename:
continue
# 标准化并拼接目标路径
target_path = os.path.join(target_dir, os.path.normpath(member.filename))
if member.is_dir():
os.makedirs(target_path, exist_ok=True)
else:
os.makedirs(os.path.dirname(target_path), exist_ok=True)
with zf.open(member) as src, open(target_path, "wb") as dst:
dst.write(src.read())safe_extract("in.zip", "./output")
用 write() 还是 writestr()?性能和适用场景怎么选
write() 适合已有磁盘文件,writestr() 适合内存中生成的内容(如 JSON 字符串、动态 HTML)。二者底层都走压缩流,性能差异主要来自 I/O 方式。
-
write(filename, arcname=None):读取整个文件进内存再压缩,大文件(>100MB)可能触发 MemoryError;arcname可重命名归档内路径 -
writestr(zinfo_or_arcname, data, compress_type=None):data必须是 bytes 或 str(str 会自动 encode('utf-8')),适合小内容;若传ZipInfo实例,可精确控制时间戳、权限等元数据 - 超大文件建议用分块读取 +
writestr()配合ZipInfo,或换用subprocess调用系统zip命令
为什么 extractall() 有时不报错却没解出文件
常见于 ZIP 包本身损坏、加密、或使用了非标准压缩算法(如 LZMA、ZSTD),而 zipfile 默认只支持 ZIP_DEFLATED 和 ZIP_STORED。
- 先运行
zf.testzip(),返回第一个损坏成员名,为 None 表示校验通过 - 检查
zf.compression和各ZipInfo.compress_type是否为ZIP_DEFLATED或ZIP_STORED - 加密 ZIP 无法直接解压,
extractall(pwd=...)中pwd必须是 bytes 类型(如b"123"),且密码错误会抛RuntimeError,不是静默失败 - 某些工具打的 ZIP 含空目录项(
is_dir() == True),但extractall()不创建空目录,需手动补全
真正麻烦的是跨平台路径处理和边缘 ZIP 格式兼容性——别迷信“自动解压”,每次操作前用 testzip() 和 namelist() 看一眼,比事后调试快得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











