使用Python ZipFile提取zip文件中的文件而不保留结构?

73

我想从一个包含子文件夹的.zip中提取所有文件。我希望所有子文件夹中的文件都提取到同一个文件夹中,而不保留原来的结构。目前,我会先将所有文件提取出来,然后将它们移动到一个文件夹中,最后删除之前的子文件夹。具有相同名称的文件会被覆盖。

在编写文件之前是否可以实现这一点呢?

这是一个示例结构:

my_zip/file1.txt
my_zip/dir1/file2.txt
my_zip/dir1/dir2/file3.txt
my_zip/dir3/file4.txt

最后我希望这样:

my_dir/file1.txt
my_dir/file2.txt
my_dir/file3.txt
my_dir/file4.txt

我能在这段代码中添加什么?

import zipfile
my_dir = "D:\\Download\\"
my_zip = "D:\\Download\\my_file.zip"

zip_file = zipfile.ZipFile(my_zip, 'r')
for files in zip_file.namelist():
    zip_file.extract(files, my_dir)
zip_file.close()

如果我通过zip_file.namelist()重命名文件路径,会出现以下错误:

KeyError: "There is no item named 'file2.txt' in the archive"
5个回答

83

这将打开zip存档中的成员文件句柄,提取文件名并将其复制到目标文件(这就是ZipFile.extract的工作原理,不考虑子目录)。

import os
import shutil
import zipfile

my_dir = r"D:\Download"
my_zip = r"D:\Download\my_file.zip"

with zipfile.ZipFile(my_zip) as zip_file:
    for member in zip_file.namelist():
        filename = os.path.basename(member)
        # skip directories
        if not filename:
            continue
    
        # copy file (taken from zipfile's extract)
        source = zip_file.open(member)
        target = open(os.path.join(my_dir, filename), "wb")
        with source, target:
            shutil.copyfileobj(source, target)

1
我正在使用这个,但是现在元数据消失了。你知道保留元数据(创建日期时间)的方法吗? - RvdK
这很好用!但是否有一种包括子目录的方法?将子目录视为正常文件夹并在其中提取? - Micromegas

54

可以遍历ZipFile.infolist()。在返回的ZipInfo对象上,您可以操作filename以删除目录部分,并最终将其提取到指定的目录。

import zipfile
import os

my_dir = "D:\\Download\\"
my_zip = "D:\\Download\\my_file.zip"

with zipfile.ZipFile(my_zip) as zip:
    for zip_info in zip.infolist():
        if zip_info.is_dir():
            continue
        zip_info.filename = os.path.basename(zip_info.filename)
        zip.extract(zip_info, my_dir)

4
依我之见,这个答案比被接受的答案更容易理解,并且如果文件名过滤器进行了适当的修改,它也可以在子目录上运行,例如仅从一个子目录中提取到目标目录。 - Jeronimo
我也更喜欢这个示例,因为它可以通过对fileinfo使用string.replace方法并提取来将目录包含在文件名中。 zip_info.filename = zip_info.filename.replace('/','').replace(':','').replace('?','') - Michael
这个很好用 - 注意在Windows和*nix中斜杠的差异。 - dataman

15

只需将内存中的字节提取出来,计算文件名并自己写入其中,而不是让库来做 - -大多数情况下,只需使用“read()”而不是“extract()”方法:

Python 3.6+更新(2020) - 原始答案中的相同代码,但使用了pathlib.Path,它简化了文件路径操作和其他操作(例如“write_bytes”)

from pathlib import Path
import zipfile
import os

my_dir = Path("D:\\Download\\")
my_zip = my_dir / "my_file.zip"

zip_file = zipfile.ZipFile(my_zip, 'r')
for files in zip_file.namelist():
    data = zip_file.read(files, my_dir)
    myfile_path = my_dir / Path(files.filename).name
    myfile_path.write_bytes(data)
zip_file.close()

没有使用pathlib的原始代码:

import zipfile
import os

my_dir = "D:\\Download\\"
my_zip = "D:\\Download\\my_file.zip"

zip_file = zipfile.ZipFile(my_zip, 'r')
for files in zip_file.namelist():
    data = zip_file.read(files, my_dir)
    # I am almost shure zip represents directory separator
    # char as "/" regardless of OS, but I  don't have DOS or Windos here to test it
    myfile_path = os.path.join(my_dir, files.split("/")[-1])
    myfile = open(myfile_path, "wb")
    myfile.write(data)
    myfile.close()
zip_file.close()

谢谢。我只需要添加一个异常来避免 myfile_path 中的 directory,只需保留文件。 - Thammas

7
类似于 Gerhard Götz 解决方案的概念,但是适用于提取单个文件而不是整个 zip 文件:
with ZipFile(zipPath, 'r') as zipObj:
    zipInfo = zipObj.getinfo(path_in_zip))
    zipInfo.filename = os.path.basename(destination)
    zipObj.extract(zipInfo, os.path.dirname(os.path.realpath(destination)))

-1
如果你遇到了badZipFile错误,可以使用7zip子进程来解压缩归档文件。假设你已经安装了7zip,那么请使用以下代码:
import subprocess
my_dir = destFolder #destination folder
my_zip = destFolder + "/" + filename.zip #file you want to extract
ziploc = "C:/Program Files/7-Zip/7z.exe" #location where 7zip is installed
cmd = [ziploc, 'e',my_zip ,'-o'+ my_dir ,'*.txt' ,'-r' ] 
#extracting only txt files and from all subdirectories
sp = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)

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