【问题标题】:namelist() from ZipFile returns strings with an invalid encodingZipFile 中的 namelist() 返回编码无效的字符串
【发布时间】:2016-10-09 23:04:43
【问题描述】:

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

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

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

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

【问题讨论】:

  • stackoverflow.com/questions/1807063/… 可能会给你一些答案,特别是第二个答案。
  • 无论人们在此处的答案中发布的解决方法如何,它们都不是真正可靠的解决方案,并解释了其他语言的其他程序如何处理该问题。 python包开发人员也没有关于这方面的信息。

标签: string python-3.x unicode encoding chardet


【解决方案1】:

如何修复该代码,以便我始终以 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')
    ...

【讨论】:

  • 我想指出,我曾体验过 Mac OS X 的 ZIP 文件,它确实将文件列表编码为 utf-8,但忘记设置标志。
  • 谢谢!!当文件名使用 cp949 编码时,它适用于我的情况。只需使用:name.encode('cp437').decode('cp949') 工作!
【解决方案2】:

这是根据the zip spec that supports only cp437 and utf-8 character encodings 解码zipfile.py 中文件名的代码:

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

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

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

如果您知道实际的字符编码,例如 cp866 (OEM (console) codepage) may be used on Russian 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>..`

【讨论】:

    【解决方案3】:

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

    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% 的失败文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-05
      • 1970-01-01
      • 1970-01-01
      • 2019-12-11
      • 1970-01-01
      • 2021-09-30
      相关资源
      最近更新 更多