zipfile 优雅地正确解压压缩包内中文文件名文件
背景
使用 Python 标准库 zipfile
解压压缩包,但压缩包中含中文文件名文件。
此时无论使用 ZipFile.extract
还是 ZipFile.extractall
解压都会导致解压出来的中文文件名变成 乱码 。
解决方案
通过查看 ZipFile
源码可以发现其是在 __init__
的 _RealGetContents
方法对编码进行了处理
摘抄该部分代码出来如下:
if flags & 0x800:
# UTF-8 file names extension
filename = filename.decode('utf-8')
else:
# Historical ZIP filename encoding
filename = filename.decode('cp437')
# Create ZipInfo instance to store file information
x = ZipInfo(filename)
x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET]
(x.create_version, x.create_system, x.extract_version, x.reserved,
x.flag_bits, x.compress_type, t, d,
x.CRC, x.compress_size, x.file_size) = centdir[1:12]
if x.extract_version > MAX_EXTRACT_VERSION:
raise NotImplementedError("zip file version %.1f" %
(x.extract_version / 10))
x.volume, x.internal_attr, x.external_attr = centdir[15:18]
# Convert date/time code to (year, month, day, hour, min, sec)
x._raw_time = t
x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
x._decodeExtra()
x.header_offset = x.header_offset + concat
self.filelist.append(x)
self.NameToInfo[x.filename] = x
显然,要处理中文乱码只需要将 cp437
编码转换为 gbk
编码即可
然后我们查看 ZipFile.extract
还是 ZipFile.extractall
的源码, debug 发现跟进去发现其就是利用 filename
这个字段解压的
综上,解决中文乱码的思路揪出来了,在不改变 zipfile
源码的情况,我们可以选择创建一个新的 class 重写 zipfile
指定地方源码或者
使用补丁的方式更改 zipfile.ZipFile
实例指定属性
在这里,我选择的是定义一个函数对 zipfile.ZipFile
实例进行打补丁
源码如下:
import zipfile
def zipfile_support_gbk(zip_file: zipfile.ZipFile):
"""
补丁函数,使得 zipfile 支持中文 gbk 编码
:param zip_file:
:return:
"""
name_to_info = zip_file.NameToInfo
# 这里 list 相当于深拷贝了 name_to_info 字典的 key
# 避免了遍历字典时操作字典情况
voice_li = list(name_to_info.keys())
for voice_name in voice_li:
real_voice_name = voice_name.encode('cp437').decode('gbk')
info = name_to_info.pop(voice_name)
info.filename = real_voice_name
name_to_info[real_voice_name] = info