如何递归地提取zip文件?

18

我有一个压缩文件,其中包含三个压缩文件,就像这样:

zipfile.zip\  
    dirA.zip\
         a  
    dirB.zip\
         b  
    dirC.zip\
         c

我想提取所有嵌套在zip文件中名为dirA、dirB和dirC的目录中的内部zip文件。
基本上,我希望最终得到以下结构:

output\  
    dirA\
         a  
    dirB\
         b  
    dirC\
         c

我已经尝试了以下方法:

import os, re
from zipfile import ZipFile

os.makedirs(directory)  # where directory is "\output"
with ZipFile(self.archive_name, "r") as archive:
    for id, files in data.items():
        if files:
            print("Creating", id)
            dirpath = os.path.join(directory, id)

            os.mkdir(dirpath)

            for file in files:
                match = pattern.match(filename)
                new = match.group(2)
                new_filename = os.path.join(dirpath, new)

                content = archive.open(file).read()
            with open(new_filename, "wb") as outfile:
                outfile.write(content)

但它只是提取了 zip 文件,最终我得到了:

output\  
    dirA\
         dirA.zip 
    dirB\
         dirB.zip 
    dirC\
         dirC.zip

非常感谢任何带有代码片段的建议,因为我已经尝试了很多不同的方法并阅读了文档,但都没有成功。


请修改您的问题并提供一个包含data.items()内容的最小完整可验证示例 - martineau
@martineau 谢谢您的评论。 如上所述,数据包含 \zipfile.zip > dirA.zip > a \zipfile.zip > dirB.zip > b \zipfile.zip > dirC.zip > c我试图让问题更加通用,不依赖于“数据”包含什么,除了事实上有一个压缩文件中包含了其他压缩文件的情况。 - Yannis
4个回答

16
当提取zip文件时,您需要将内部zip文件写入内存而不是磁盘。为此,我使用了BytesIO
请查看以下代码:
import os
import io
import zipfile

def extract(filename):
    z = zipfile.ZipFile(filename)
    for f in z.namelist():
        # get directory name from file
        dirname = os.path.splitext(f)[0]  
        # create new directory
        os.mkdir(dirname)  
        # read inner zip file into bytes buffer 
        content = io.BytesIO(z.read(f))
        zip_file = zipfile.ZipFile(content)
        for i in zip_file.namelist():
            zip_file.extract(i, dirname)

如果您运行extract("zipfile.zip"),其中zipfile.zip是:
zipfile.zip/
    dirA.zip/
        a
    dirB.zip/
        b
    dirC.zip/
        c

输出应为:

dirA/
  a
dirB/
  b
dirC/
  c

正是我所需要的,它按照我问题描述的方式进行了提取。谢谢! - Yannis
如果原始的zip文件仅包含一些类似于.zip的文件在第一层级,例如.xlsx,则它们也将被解压缩。我建议在解压缩之前检查扩展名。 - outforawhile

10

为一个可以提取嵌套压缩文件并清理原始压缩文件的函数:

import zipfile, re, os

def extract_nested_zip(zippedFile, toFolder):
    """ Extract a zip file including any nested zip files
        Delete the zip file(s) after extraction
    """
    with zipfile.ZipFile(zippedFile, 'r') as zfile:
        zfile.extractall(path=toFolder)
    os.remove(zippedFile)
    for root, dirs, files in os.walk(toFolder):
        for filename in files:
            if re.search(r'\.zip$', filename):
                fileSpec = os.path.join(root, filename)
                extract_nested_zip(fileSpec, root)

我们可以使用S3路径吗?而不是本地磁盘路径。 - Rajashekhar Meesala

5

我尝试了其他一些解决方案,但无法使它们“原地”运行。我将发布我的解决方案来处理“原地”版本。注意:它会删除zip文件并用相同名称的目录“替换”它们,因此如果您想保留zip文件,请备份。

策略很简单。在目录(和子目录)中解压缩所有zip文件,然后重复此过程,直到没有zip文件为止。如果zip文件包含zip文件,则需要重复此过程。

import os
import io
import zipfile
import re

def unzip_directory(directory):
    """" This function unzips (and then deletes) all zip files in a directory """
    for root, dirs, files in os.walk(directory):
        for filename in files:
            if re.search(r'\.zip$', filename):
                to_path = os.path.join(root, filename.split('.zip')[0])
                zipped_file = os.path.join(root, filename)
                if not os.path.exists(to_path):
                    os.makedirs(to_path)
                    with zipfile.ZipFile(zipped_file, 'r') as zfile:
                        zfile.extractall(path=to_path)
                    # deletes zip file
                    os.remove(zipped_file)

def exists_zip(directory):
    """ This function returns T/F whether any .zip file exists within the directory, recursively """
    is_zip = False
    for root, dirs, files in os.walk(directory):
        for filename in files:
            if re.search(r'\.zip$', filename):
                is_zip = True
    return is_zip

def unzip_directory_recursively(directory, max_iter=1000):
    print("Does the directory path exist? ", os.path.exists(directory))
    """ Calls unzip_directory until all contained zip files (and new ones from previous calls)
    are unzipped
    """
    iterate = 0
    while exists_zip(directory) and iterate < max_iter:
        unzip_directory(directory)
        iterate += 1
    pre = "Did not " if iterate < max_iter else "Did"
    print(pre, "time out based on max_iter limit of", max_iter, ". Took iterations:", iterate)

假设您的zip文件已经备份,您可以通过调用unzip_directory_recursively(your_directory)来实现所有操作。

3

这个方法适用于我。只需将此脚本与嵌套的zip文件放在同一个目录下即可。它会将zip文件提取到与原始zip文件同名的目录中,并清理原始zip文件。它还会计算嵌套的zip文件中的总文件数。

import os

from zipfile import ZipFile


def unzip (path, total_count):
    for root, dirs, files in os.walk(path):
        for file in files:
            file_name = os.path.join(root, file)
            if (not file_name.endswith('.zip')):
                total_count += 1
            else:
                currentdir = file_name[:-4]
                if not os.path.exists(currentdir):
                    os.makedirs(currentdir)
                with ZipFile(file_name) as zipObj:
                    zipObj.extractall(currentdir)
                os.remove(file_name)
                total_count = unzip(currentdir, total_count)
    return total_count

total_count = unzip ('.', 0)
print(total_count)

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