【问题标题】:Python zipfile module can't extract filenames with Chinese charactersPython zipfile 模块无法提取带有中文字符的文件名
【发布时间】:2017-04-22 12:34:50
【问题描述】:

我正在尝试使用 python 脚本从中国服务提供商处下载文件(我自己不是来自中国)。提供商给了我一个 .zip 文件,其中包含一个名称中似乎包含中文字符的文件。这似乎导致 zipfile 模块出错。

代码:

import zipfile

f = "/path/to/zip_file.zip"

if zipfile.is_zipfile(f):
    fz = zipfile.ZipFile(f, 'r')

zipfile 本身不包含任何非 ASCII 字符,但其中的文件包含。当我运行上面的脚本时,我得到以下异常:

Traceback (most recent call last):   File "./temp.py", line 9, in <module>
    fz = zipfile.ZipFile(f, 'r')   File "/usr/lib/python2.7/zipfile.py", line 770, in __init__
    self._RealGetContents()   File "/usr/lib/python2.7/zipfile.py", line 859, in _RealGetContents
    x.filename = x._decodeFilename()   File "/usr/lib/python2.7/zipfile.py", line 379, in _decodeFilename
    return self.filename.decode('utf-8')   File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True) UnicodeDecodeError: 'utf8' codec can't decode byte 0xbd in position 30: invalid start byte

我尝试过查看许多类似问题的答案:

如果我错了,请纠正我,但它看起来像 open issue with the zipfile module

我该如何解决这个问题?是否有任何替代模块来处理我应该使用的压缩文件?还是其他解决方案?

TIA。

编辑: 我可以使用 linux 命令行实用程序“解压缩”完美地访问/解压缩同一个文件。

【问题讨论】:

    标签: python-2.7 unicode zipfile


    【解决方案1】:

    @Mr.Ham 的解决方案完美解决了我的问题。我用的是中文版的Win10。其中文件系统的默认编码是GBK。

    我认为适用于其他语言用户。只需将解码从 GBK 更改为系统默认编码也可以。并且默认的系统编码可以通过Python自动获取。

    所以修补后的代码如下所示:

    import zipfile
    import locale
    
    default_encoding = locale.getpreferredencoding()
    
    with zipfile.ZipFile("/path/to/zip_file.zip") as f:
        zipinfo = f.infolist()
        for member in zipinfo:
            member.filename = member.filename.encode('cp437').decode(default_encoding)
            # The second argument could make the extracted filese to the same dir as the zip file, or leave it blank to your work dir.
            f.extract(member, "/path/to/zip_file")
    

    【讨论】:

      【解决方案2】:

      最近我遇到了同样的问题。这是我的解决方案。希望对你有用。

      import shutil
      import zipfile
      f = zipfile.ZipFile('/path/to/zip_file.zip', 'r')
      for fileinfo in f.infolist():
          filename = fileinfo.filename.encode('cp437').decode('gbk')
          outputfile = open(filename, "wb")
          shutil.copyfileobj(f.open(fileinfo.filename), outputfile)
          outputfile.close()
      f.close()
      

      更新:您可以通过pathlib 使用以下更简单的解决方案:

      from pathlib import Path
      import zipfile
      
      with zipfile.ZipFile('/path/to/zip_file.zip', 'r') as f:
          for fn in f.namelist():
              extracted_path = Path(f.extract(fn))
              extracted_path.rename(fn.encode('cp437').decode('gbk'))
      

      【讨论】:

        【解决方案3】:

        这段代码呢?

        import zipfile
        
        with zipfile.ZipFile('/path/to/zip_file.zip', 'r') as f:
            zipInfo = f.infolist()
            for member in zipInfo:
                member.filename = member.filename.encode('cp437').decode('gbk')
                f.extract(member)
        

        【讨论】:

          【解决方案4】:

          Python 2.x(2.7) 和 Python 3.x 处理模块 zipfile 中非 utf-8 文件名的方式有点不同。

          首先,他们都检查文件的 ZipInfo.flag_bits,如果 ZipInfo.flag_bits & 0x800,文件名将使用 utf-8 解码。

          如果上面的检查为False,在Python 2.x中,将返回名称的字节串;在 Python 3.x 中,该模块将对编码为 cp437 的文件进行解码并返回解码结果。当然,在两个 Python 版本中,模块都不知道文件名的真实编码。

          因此,假设您从 ZipInfo 对象或 zipfile.namelist 方法中获得了一个文件名,并且您已经知道该文件名是使用 XXX 编码进行编码的。这些是您获得正确 unicode 文件名的方法:

          # in python 2.x
          filename = filename.decode('XXX')
          
          
          # in python 3.x
          filename = filename.encode('cp437').decode('XXX')
          

          【讨论】:

          【解决方案5】:

          ZIP 文件无效。它有一个标志,表明其中的文件名被编码为 UTF-8,但实际上不是;它们包含作为 UTF-8 无效的字节序列。也许他们是GBK?也许还有别的?也许是一些邪恶的不一致的混合物?不幸的是,野外的 ZIP 工具在处理非 ASCII 文件名方面非常糟糕。

          一个快速的解决方法可能是替换解码文件名的库函数。这是一个猴子补丁,因为没有一种简单的方法可以将您自己的 ZipInfo 类注入 ZipFile,但是:

          zipfile.ZipInfo._decodeFilename = lambda self: self.filename
          

          将禁用解码文件名的尝试,并始终返回带有字节字符串 filename 属性的 ZipInfo,您可以继续以任何合适的方式手动解码/处理。

          【讨论】:

          • “它有一个标志,表明它里面的文件名被编码为 UTF-8”我从来没有听说过那个标志。在哪里可以找到它?
          • 对不起,我没有在我的问题中提到这一点,但我可以使用 linux 命令行实用程序“unzip”完美地访问/解压缩它。所以我怀疑这是否是文件本身的问题。
          • @Rhymoid:文件属性标志字的第 11 位,参见PKware appnote sec 4.1.4:“如果设置了该位,则该文件的文件名和注释字段必须使用 UTF- 8”。 hyperwiser:通过任何工具的处理来判断文件的有效性是不明智的。当然,早于 UTF-8 标志的工具会忽略它,就像不关心编码的面向字节的工具一样。除了野外工具对 ZIP 的各种草率定义的边缘情况的不同反应之外。
          猜你喜欢
          • 2016-01-23
          • 1970-01-01
          • 1970-01-01
          • 2011-12-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-12
          相关资源
          最近更新 更多