在跨平台传输 ZIP 压缩包时,Python 内置 zipfile 模块解压出的中文文件名经常变成乱码或问号,尤其从 Windows 传到 Linux(含麒麟)或 macOS 后最明显。这个问题的根源不在代码逻辑,而在于 ZIP 协议的历史编码缺陷——ZIP 标准最初使用 CP437 编码存储文件名(不支持中文),而不同系统压缩时使用的实际编码又不统一:Windows 默认 GBK/GB2312,Linux/macOS/麒麟默认 UTF-8。Python 的 zipfile 读取时始终按 CP437 解码,导致中文字符错乱。
针对这一痛点,业界主要有三种解决思路:Python 3.11+ 内置的 metadata_encoding 参数可一行搞定,但版本受限;手动转码方案通过 CP437→GBK/UTF-8 逆向修复,全版本通用且稳定;第三方库 patoolib 依赖系统原生解压工具,适合临时场景但缺乏环境独立性。下面提供一套基于手动转码优化、可自动识别编码并递归解压嵌套 ZIP 包的通用脚本,兼容所有 Python 版本和主流操作系统,包括国产麒麟系统。
- # -*- coding: utf-8 -*-
- """
- Python ZIP压缩包万能解压脚本
- 功能:自动识别GBK/UTF-8编码、修复中文文件名乱码、支持嵌套ZIP压缩包递归解压
- 适配:全Python版本、Windows/麒麟Linux/macOS全平台
- """
- import zipfile
- import os
- def unzip_file_auto_encoding(zip_path: str, output_root: str) -> None:
- """
- 通用解压函数,自动识别编码、处理中文乱码、递归解压嵌套zip
- :param zip_path: 待解压的zip文件绝对/相对路径
- :param output_root: 文件解压输出根目录
- """
- os.makedirs(output_root, exist_ok=True)
- with zipfile.ZipFile(zip_path, "r") as zf:
- for file_name in zf.namelist():
- real_file_name = ""
- # 自动编码识别:优先GBK,失败则尝试UTF-8
- try:
- real_file_name = file_name.encode("cp437").decode("gbk")
- except UnicodeDecodeError:
- real_file_name = file_name.encode("cp437").decode("utf-8")
- full_file_path = os.path.join(output_root, real_file_name)
- # 处理目录
- if file_name.endswith("/"):
- os.makedirs(full_file_path, exist_ok=True)
- else:
- # 先解压原始乱码文件,再重命名为正确中文名
- zf.extract(file_name, output_root)
- os.rename(os.path.join(output_root, file_name), full_file_path)
- # 递归解压嵌套压缩包
- if real_file_name.lower().endswith(".zip"):
- nest_output_dir = os.path.join(output_root, real_file_name[:-4])
- unzip_file_auto_encoding(full_file_path, nest_output_dir)
- # 可选:解压后删除嵌套压缩包,按需开启
- # os.remove(full_file_path)
- if __name__ == "__main__":
- TARGET_ZIP = "test.zip" # 待解压的压缩包路径
- OUTPUT_DIR = "unzip_out" # 解压输出目录
- unzip_file_auto_encoding(TARGET_ZIP, OUTPUT_DIR)
- print(f"✅ 解压完成!所有文件已输出至:{os.path.abspath(OUTPUT_DIR)}")
复制代码
脚本的核心逻辑是先将 zipfile 读取的原始文件名(按 CP437 编码)重新编码为字节,再尝试用 GBK 解码。由于 Windows 压缩包通常使用 GBK,这个优先尝试成功率高;如果抛出 UnicodeDecodeError,立即切换为 UTF-8 解码,适配 Linux/macOS/ 麒麟等系统。这样无需用户手动指定编码,自动修复乱码。
在处理嵌套 ZIP 包时,脚本会检查解压出的每一个文件是否以 .zip 结尾,如果是则递归调用自身,并将嵌套包解压到该文件名去后缀的子目录中,避免文件覆盖和混淆。例如 test.zip 内包含 archive.zip,解压后会生成 unzip_out/archive/ 目录,其中放置 archive.zip 内所有文件。
如果你的 Python 环境是 3.11 及以上版本,可以使用官方新增的 metadata_encoding 参数简化代码:- import zipfile
- with zipfile.ZipFile("test.zip", "r", metadata_encoding="gbk") as zf:
- zf.extractall("unzip_out")
- # 若为UTF-8编码压缩包,将metadata_encoding="utf-8"
复制代码 但注意此方式无法自动切换编码,需要预先知道压缩包的编码,且仅限 Python 3.11+。
第三方库 patoolib 虽然方便,但依赖系统安装的 unzip(Linux)或 7-Zip/WinRAR(Windows),在服务器或无界面的轻量化环境里不建议使用。安装命令为 pip install patool,调用方式为 patoolib.extract_archive("test.zip", outdir="unzip_out")。
实际使用时,建议将通用脚本作为默认解压工具,因为它不依赖任何第三方库,兼容所有 Python 版本和操作系统,也完美适配国产麒麟 Linux 环境。只需修改脚本末尾的 TARGET_ZIP 和 OUTPUT_DIR 路径即可集成到项目中。注意嵌套解压后原压缩包默认保留,若需自动删除可解除注释 os.remove 行。 |