用Python zipfile模块提取zip文件中的文件时,如何不保留顶级文件夹?

8

我正在使用当前的代码从一个zip文件中提取文件,并保持目录结构:

zip_file = zipfile.ZipFile('archive.zip', 'r')
zip_file.extractall('/dir/to/extract/files/')
zip_file.close()

以下是一个示例zip文件的结构:
/dir1/file.jpg
/dir1/file1.jpg
/dir1/file2.jpg

最终我希望得到以下内容:

/dir/to/extract/file.jpg
/dir/to/extract/file1.jpg
/dir/to/extract/file2.jpg

但是只有当zip文件具有一个包含所有文件的顶层文件夹时,它才应该忽略,因此当我解压缩具有以下结构的zip文件时:

/dir1/file.jpg
/dir1/file1.jpg
/dir1/file2.jpg
/dir2/file.txt
/file.mp3

应该保持这样的状态:
/dir/to/extract/dir1/file.jpg
/dir/to/extract/dir1/file1.jpg
/dir/to/extract/dir1/file2.jpg
/dir/to/extract/dir2/file.txt
/dir/to/extract/file.mp3

有任何想法吗?
5个回答

7

如果我理解你的问题正确,你想在提取zip文件中的项目之前删除任何常见的前缀目录。

如果是这样,那么以下脚本应该可以实现你想要的功能:

import sys, os
from zipfile import ZipFile

def get_members(zip):
    parts = []
    # get all the path prefixes
    for name in zip.namelist():
        # only check files (not directories)
        if not name.endswith('/'):
            # keep list of path elements (minus filename)
            parts.append(name.split('/')[:-1])
    # now find the common path prefix (if any)
    prefix = os.path.commonprefix(parts)
    if prefix:
        # re-join the path elements
        prefix = '/'.join(prefix) + '/'
    # get the length of the common prefix
    offset = len(prefix)
    # now re-set the filenames
    for zipinfo in zip.infolist():
        name = zipinfo.filename
        # only check files (not directories)
        if len(name) > offset:
            # remove the common prefix
            zipinfo.filename = name[offset:]
            yield zipinfo

args = sys.argv[1:]

if len(args):
    zip = ZipFile(args[0])
    path = args[1] if len(args) > 1 else '.'
    zip.extractall(path, get_members(zip))

请您添加一些注释,以便更好地理解这里发生了什么? - aturegano
2
@aturegano。我在示例代码中添加了一些注释。zipinfo对象的文件名是可写的。因此,脚本会从存档中的所有文件中剥离公共前缀,然后将它们提取到目标目录中。 - ekhumoro

1

通过读取ZipFile.namelist()返回的条目,查看它们是否在同一个目录中,然后打开/读取每个条目并将其写入使用open()打开的文件中。


1

这可能是压缩文件本身的问题。在 Python 提示符中尝试执行以下操作,以查看文件是否位于压缩文件本身的正确目录中。

import zipfile

zf = zipfile.ZipFile("my_file.zip",'r')
first_file = zf.filelist[0]
print file_list.filename

这里应该写类似于“dir1”的内容。 重复上述步骤,将索引1替换为filelist,如下所示:first_file = zf.filelist[1]。这次输出应该看起来像是“dir1/file1.jpg”,如果不是这种情况,则zip文件不包含目录,并且将解压缩到一个单独的目录中。


0

基于 @ekhumoro 的回答,我想出了一个更简单的函数来提取同一级别的所有内容,虽然不完全符合您的要求,但我认为可以帮助某些人。

    def _basename_members(self, zip_file: ZipFile):
        for zipinfo in zip_file.infolist():
            zipinfo.filename = os.path.basename(zipinfo.filename)
            yield zipinfo

    from_zip="some.zip"
    to_folder="some_destination/"
    with ZipFile(file=from_zip, mode="r") as zip_file:
        os.makedirs(to_folder, exist_ok=True)
        zip_infos = self._basename_members(zip_file)
        zip_file.extractall(path=to_folder, members=zip_infos)

0

基本上你需要做两件事:

  1. 在zip文件中识别根目录。
  2. 从zip文件中其他项目的路径中删除根目录。

以下操作应该保留zip文件的整体结构,同时删除根目录:

import typing, zipfile

def _is_root(info: zipfile.ZipInfo) -> bool:
    if info.is_dir():
        parts = info.filename.split("/")
        # Handle directory names with and without trailing slashes.
        if len(parts) == 1 or (len(parts) == 2 and parts[1] == ""):
            return True
    return False

def _members_without_root(archive: zipfile.ZipFile, root_filename: str) -> typing.Generator:
    for info in archive.infolist():
        parts = info.filename.split(root_filename)
        if len(parts) > 1 and parts[1]:
            # We join using the root filename, because there might be a subdirectory with the same name.
            info.filename = root_filename.join(parts[1:])
            yield info

with zipfile.ZipFile("archive.zip", mode="r") as archive:
    # We will use the first directory with no more than one path segment as the root.
    root = next(info for info in archive.infolist() if _is_root(info))
    if root:
        archive.extractall(path="/dir/to/extract/", members=_members_without_root(archive, root.filename))
    else:
        print("No root directory found in zip.")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接