Python zipfile 不解压缩 windows zip 存档的文件夹

Python zipfile does not unzip folders for windows zip archive

我有一个 zip 文件,它是在 Windows 机器上使用此工具 System.IO.Compression.ZipFile 创建的(此 zip 存档包含许多文件和文件夹)。我有一个 python 代码 运行 在 Linux 机器上(准确地说是 raspberry pi),它必须解压缩存档并创建所有必要的文件夹和文件。我正在使用 Python 3.5.0zipfile 库,这是示例代码:

import zipfile

zip = zipfile.ZipFile("MyArchive.zip","r")
zip.extractall()
zip.close()

现在,当我 运行 这段代码时,我得到的不是漂亮的解压缩目录树,而是根目录中的所有文件,这些文件的名称很奇怪,例如 Folder1\Folder2\MyFile.txt.

我的假设是,由于 zip 存档是在 Windows 上创建的,并且 windows 上的目录分隔符是 \,而在 Linux 上是 /, python zipfile 库将 \ 视为文件名的一部分,而不是目录分隔符。另请注意,当我手动提取此存档(不是通过 python 代码)时,所有文件夹都按预期创建,因此看来这绝对是 zipfile 库的问题。另一个注意事项是,对于使用不同工具(不是 System.IO.Compression.ZipFile)创建的 zip 存档,使用相同的 python 代码可以正常工作。

对正在发生的事情以及如何解决它有任何见解吗?

这确实是 zipfile module 的一个错误,它在 ZipFile._extract_member() 中有以下行来盲目地将文件名中的 '/' 替换为 OS-具体路径分隔符,当它还应该寻找 '\':

arcname = member.filename.replace('/', os.path.sep)

您可以通过使用直接从源代码复制但更正上述行的版本覆盖 ZipFile._extract_member() 来解决此问题:

from zipfile import ZipFile, ZipInfo
import shutil
import os
def _extract_member(self, member, targetpath, pwd):
    """Extract the ZipInfo object 'member' to a physical
       file on the path targetpath.
    """
    if not isinstance(member, ZipInfo):
        member = self.getinfo(member)

    if os.path.sep == '/':
        arcname = member.filename.replace('\', os.path.sep)
    else:
        arcname = member.filename.replace('/', os.path.sep)

    if os.path.altsep:
        arcname = arcname.replace(os.path.altsep, os.path.sep)
    # interpret absolute pathname as relative, remove drive letter or
    # UNC path, redundant separators, "." and ".." components.
    arcname = os.path.splitdrive(arcname)[1]
    invalid_path_parts = ('', os.path.curdir, os.path.pardir)
    arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
                               if x not in invalid_path_parts)
    if os.path.sep == '\':
        # filter illegal characters on Windows
        arcname = self._sanitize_windows_name(arcname, os.path.sep)

    targetpath = os.path.join(targetpath, arcname)
    targetpath = os.path.normpath(targetpath)

    # Create all upper directories if necessary.
    upperdirs = os.path.dirname(targetpath)
    if upperdirs and not os.path.exists(upperdirs):
        os.makedirs(upperdirs)

    if member.is_dir():
        if not os.path.isdir(targetpath):
            os.mkdir(targetpath)
        return targetpath

    with self.open(member, pwd=pwd) as source, \
            open(targetpath, "wb") as target:
        shutil.copyfileobj(source, target)

    return targetpath
ZipFile._extract_member = _extract_member

发生的事情是,虽然 Windows 将 \ (path.sep) 和 / (path.altsep) 都识别为路径分隔符,但 Linux 只识别 / (path.sep).

所示,ZipFile 的现有实现始终确保 path.sep/ 被视为有效的分隔符。这意味着在 Linux 上,\ 被视为文件名的文字部分。要更改它,您可以将 os.altsep 设置为 \,因为如果它不是 None 空的,它会被检查。

如果你沿着修改 ZipFile 本身的道路前进,就像其他答案所建议的那样,只需添加一行以盲目地将 \ 更改为 path.sep,因为 / 总是已经改变了。这样一来,/\和可能的path.altsep都将被转换为path.sep。这就是命令行工具似乎正在做的事情。