8

问题在于,对于上传到 python 应用程序的某些档案或文件,ZipFile'snamelist()返回解码错误的字符串。

from zip import ZipFile
for name in ZipFile('zipfile.zip').namelist():
    print('Listing zip files: %s' % name)

如何修复该代码,以便我始终以 unicode 解码文件名(因此支持 Chineeze、俄语和其他语言)?

我已经看过一些 Python 2 的示例,但由于字符串的性质在 python3 中发生了变化,我不知道如何重新编码它,或者在它上面应用 chardet。

4

3 回答 3

9

如何修复该代码,以便我始终以 unicode 解码文件名(因此支持 Chineeze、俄语和其他语言)?

自动地?你不能。基本 ZIP 文件中的文件名是没有附加编码信息的字节字符串,因此除非您知道创建 ZIP 的机器上的编码是什么,否则您无法可靠地获取人类可读的文件名。

现代 ZIP 文件上的标志有一个扩展名,告诉您文件名是 UTF-8。不幸的是,你从 Windows 用户那里收到的文件通常没有它,所以你会用 chardet 等固有的不可靠方法来猜测。

我已经看过一些 Python 2 的示例,但由于字符串的性质在 python3 中发生了变化,我不知道如何重新编码它,或者在它上面应用 chardet。

Python 2 只会给你原始字节。在 Python 3 中,新行为是:

  • 如果设置了 UTF-8 标志,它会使用 UTF-8 对文件名进行解码,并返回正确的字符串值

  • 否则,它使用 DOS 代码页 437 解码文件名,这不太可能是预期的。但是,您可以将字符串重新编码回原始字节,然后尝试使用您实际想要的代码页再次解码,例如name.encode('cp437').decode('cp1252').

不幸的是(再次,因为不幸的是,在 ZIP 方面永远不会结束),ZipFile在没有告诉你它做了什么的情况下默默地解码。因此,如果您想切换并仅在文件名可疑时执行转码步骤,则必须复制用于嗅探是否设置了 UTF-8 标志的逻辑:

ZIP_FILENAME_UTF8_FLAG = 0x800

for info in ZipFile('zipfile.zip').filelist():
    filename = info.filename
    if info.flag_bits & ZIP_FILENAME_UTF8_FLAG == 0:
        filename_bytes = filename.encode('437')
        guessed_encoding = chardet.detect(filename_bytes)['encoding'] or 'cp1252'
        filename = filename_bytes.decode(guessed_encoding, 'replace')
    ...
于 2016-06-12T10:59:27.317 回答
4

zipfile.py这是根据仅支持 cp437 和 utf-8 字符编码的 zip 规范解码文件名的代码:

        if flags & 0x800:
            # UTF-8 file names extension
            filename = filename.decode('utf-8')
        else:
            # Historical ZIP filename encoding
            filename = filename.decode('cp437')

如您所见,如果0x800未设置标志,即,如果您的输入中未使用 utf-8,zipfile.zipcp437使用,因此“Chineeze、俄语和其他语言”的结果可能不正确。

实际上,可以使用 ANSI 或 OEM Windows 代码页来代替 cp437。

如果您知道实际的字符编码,例如cp866(OEM(控制台)代码页)可以在俄语 Windows 上使用,那么您可以重新编码文件名以获得原始文件名:

filename = corrupted_filename.encode('cp437').decode('cp866')

最好的选择是使用 utf-8 创建 zip 存档,以便您可以在同一存档中支持多种语言:

c:\> 7z.exe a -tzip -mcu archive.zip <files>..

或者

$ python -mzipfile -c archive.zip <files>..`
于 2016-06-12T17:02:31.410 回答
1

遇到了同样的问题,但使用了已定义的语言(俄语)。

  1. 最简单的解决方案就是使用此实用程序进行转换:https ://github.com/vlm/zip-fix-filename-encoding 对我来说,它适用于 98% 的档案(无法在 11388 语料库中的 317 个文件上运行)

  2. 更复杂的解决方案:使用带有 zipfile 的 python 模块 chardet。但这取决于您使用的 python 版本(2 或 3) - 它在 zipfile 上有一些差异。对于 python 3,我写了一个代码:

    import chardet
    original_name = name
    try:
        name = name.encode('cp437')
    except UnicodeEncodeError:
        name = name.encode('utf8')
    encoding = chardet.detect(name)['encoding']
    name = name.decode(encoding)
    

    此代码尝试使用旧式 zip(编码 CP437 并且刚刚损坏),如果失败,似乎 zip 存档是新式(UTF-8)。确定正确的编码后,您可以通过以下代码提取文件:

    from shutil import copyfileobj
    fp = archive.open(original_name)
    fp_out = open(name, 'wb')
    copyfileobj(fp, fp_out)
    

就我而言,这解决了最后 2% 的失败文件。

于 2019-07-19T17:29:52.493 回答