使用Python将文件夹添加到zip文件中

63

我想创建一个zip文件。将一个文件夹添加到zip文件中,然后将一堆文件添加到该文件夹中。

所以我希望最终得到的是一个包含文件夹及其中文件的zip文件。

我不知道在zip文件中放置文件夹是否存在不良实践,但谷歌没有给我任何相关信息。

我开始尝试以下代码:

def addFolderToZip(myZipFile,folder):
    folder = folder.encode('ascii') #convert path to ascii for ZipFile Method
    for file in glob.glob(folder+"/*"):
            if os.path.isfile(file):
                print file
                myZipFile.write(file, os.path.basename(file), zipfile.ZIP_DEFLATED)
            elif os.path.isdir(file):
                addFolderToZip(myZipFile,file)

def createZipFile(filename,files,folders):
    curTime=strftime("__%Y_%m_%d", time.localtime())
    filename=filename+curTime;
    print filename
    zipFilename=utils.getFileName("files", filename+".zip")
    myZipFile = zipfile.ZipFile( zipFilename, "w" ) # Open the zip file for writing 
    for file in files:
        file = file.encode('ascii') #convert path to ascii for ZipFile Method
        if os.path.isfile(file):
            (filepath, filename) = os.path.split(file)
            myZipFile.write( file, filename, zipfile.ZIP_DEFLATED )

    for folder in  folders:   
        addFolderToZip(myZipFile,folder)  
    myZipFile.close()
    return (1,zipFilename)


(success,filename)=createZipFile(planName,files,folders);

翻译自:http://mail.python.org/pipermail/python-list/2006-August/396166.html

这段代码可以将目标文件夹(包括其子文件夹)中的所有文件压缩到一个单独的zip文件中,同时去除所有文件夹。 但是我无法添加整个文件夹。

如果我将文件夹路径传递给myZipFile.write,我会得到以下错误:

IOError:[Errno 13] 权限被拒绝:'..\packed\bin'

非常感谢您的任何帮助。

相关问题:如何使用Python(版本2.5)压缩文件夹的内容?

13个回答

72

你也可以使用shutil

import shutil

zip_name = 'path\to\zip_file'
directory_name = 'path\to\directory'

# Create 'path\to\zip_file.zip'
shutil.make_archive(zip_name, 'zip', directory_name)

这将把整个文件夹放入压缩包中。


5
仅适用于Python 2.7及以上版本。 - jfs
1
关于该方法的一个有趣之处是,即使desired_zipfile_name_no已经具有.zip扩展名,它也会将.zip附加到其末尾。然后,它返回带有新扩展名的完整路径。(您可以使用除zip以外的其他格式,我期望它也会附加它们的扩展名。) - zekel
2
它不保留符号链接。 - ppetraki
这看起来不错,但我无法修改压缩文件的目标路径,我尝试将dir作为第四个参数添加,但它不起作用。 - Summer Sun
1
谢谢!看起来更像了。我很困惑ZipFile.write(path, arcname)如果path是一个文件夹,它只会创建空文件夹并忽略path的内容。这有什么用?!?!为什么这是标准行为!?!? :| - ewerybody

59

好的,在我理解你的需求后,问题变得很简单,只需要使用 zipfile.write 的第二个参数即可,你可以使用任何你想要的东西:

import zipfile
myZipFile = zipfile.ZipFile("zip.zip", "w" )
myZipFile.write("test.py", "dir\\test.py", zipfile.ZIP_DEFLATED )

创建一个zip文件,其中test.py将被提取到名为dir的目录中。

编辑: 我曾经需要在zip文件中创建一个空目录:这是可能的。 在上面的代码之后,只需从zip文件中删除test.py文件,该文件将消失,但空目录将保留。


而且更好的是,对于tarfile也是同样的方式,如果您曾经创建过一个:)在tarfile中,参数称为arcname表示存档名称。 - Matthieu M.
5
要跨平台地运行此操作,您需要使用os.path.join("dir","test.py")。 - ktec
对于目录条目本身(包括空目录),我认为您可以将它们像文件一样传递给.write。但是对于目录,请使用zipfile.ZIP_STORED。我添加了一个更详细的答案。 - z0r
1
请注意,所提到的第二个参数是 arcname 参数,它确定了 zip 归档文件中文件的路径。 - Danferno
不要忘记使用.close()关闭文件句柄或者使用with上下文管理器。 - ggorlen
显示剩余2条评论

16

ZIP文件没有目录结构,它只有一堆路径名和它们的内容。这些路径名应该相对于一个想象的根文件夹(即ZIP文件本身)。在ZIP文件中,"../" 前缀没有定义的含义。

假设您有一个文件 a,您想将其存储在 ZIP 文件中的一个“文件夹”内。当将文件存储在 ZIP 文件中时,只需使用文件夹名称作为文件名的前缀即可:

zipi= zipfile.ZipInfo()
zipi.filename= "folder/a" # this is what you want
zipi.date_time= time.localtime(os.path.getmtime("a"))[:6]
zipi.compress_type= zipfile.ZIP_DEFLATED
filedata= open("a", "rb").read()

zipfile1.writestr(zipi, filedata) # zipfile1 is a zipfile.ZipFile instance

我不知道有哪些ZIP实现可以在ZIP文件中包含一个文件夹。我可以想到一种解决方法(将一个虚拟文件名存储在zip“文件夹”中,在提取时应该忽略它),但不能跨实现移植。


4
我明白了。如果我没理解错的话,一个zip文件不包含任何文件夹。但是,如果一个文件名中有路径分隔符,它将被大多数压缩软件显示为文件夹中的文件。并且在解压缩时也会以这种方式创建? - Mizipzor
1
正确。这很难做到,因为归档工具会正确转义文件名中的路径分隔符。但是,你的Python程序可以强制使用未转义的名称。 - S.Lott
在Python中创建条目的正确方法是使用writestr(directory, '')。在macOS上,对于目录会创建零字节的条目。 - Cameron Lowell Palmer

13
import zipfile
import os


class ZipUtilities:

    def toZip(self, file, filename):
        zip_file = zipfile.ZipFile(filename, 'w')
        if os.path.isfile(file):
                    zip_file.write(file)
            else:
                    self.addFolderToZip(zip_file, file)
        zip_file.close()

    def addFolderToZip(self, zip_file, folder): 
        for file in os.listdir(folder):
            full_path = os.path.join(folder, file)
            if os.path.isfile(full_path):
                print 'File added: ' + str(full_path)
                zip_file.write(full_path)
            elif os.path.isdir(full_path):
                print 'Entering folder: ' + str(full_path)
                self.addFolderToZip(zip_file, full_path)

def main():
    utilities = ZipUtilities()
    filename = 'TEMP.zip'
    directory = 'TEMP'
    utilities.toZip(directory, filename)

main()

我正在运行:

python tozip.py

这是日志:

havok@fireshield:~$ python tozip.py

File added: TEMP/NARF (7ª copia)
Entering folder: TEMP/TEMP2
File added: TEMP/TEMP2/NERF (otra copia)
File added: TEMP/TEMP2/NERF (copia)
File added: TEMP/TEMP2/NARF
File added: TEMP/TEMP2/NARF (copia)
File added: TEMP/TEMP2/NARF (otra copia)
Entering folder: TEMP/TEMP2/TEMP3
File added: TEMP/TEMP2/TEMP3/DOCUMENTO DEL FINAL
File added: TEMP/TEMP2/TEMP3/DOCUMENTO DEL FINAL (copia)
File added: TEMP/TEMP2/NERF
File added: TEMP/NARF (copia) (otra copia)
File added: TEMP/NARF (copia) (copia)
File added: TEMP/NARF (6ª copia)
File added: TEMP/NERF (copia) (otra copia)
File added: TEMP/NERF (4ª copia)
File added: TEMP/NERF (otra copia)
File added: TEMP/NERF (3ª copia)
File added: TEMP/NERF (6ª copia)
File added: TEMP/NERF (copia)
File added: TEMP/NERF (5ª copia)
File added: TEMP/NARF (8ª copia)
File added: TEMP/NARF (3ª copia)
File added: TEMP/NARF (5ª copia)
File added: TEMP/NERF (copia) (3ª copia)
File added: TEMP/NARF
File added: TEMP/NERF (copia) (copia)
File added: TEMP/NERF (8ª copia)
File added: TEMP/NERF (7ª copia)
File added: TEMP/NARF (copia)
File added: TEMP/NARF (otra copia)
File added: TEMP/NARF (4ª copia)
File added: TEMP/NERF
File added: TEMP/NARF (copia) (3ª copia)

正如您所看到的,它可以工作,存档也没问题。这是一个递归函数,可以压缩整个文件夹。唯一的问题是它不会创建空文件夹。

干杯。


代码规范提示:在函数前添加@staticmethod装饰器,使用ZipUtilities.代替self. - J. Birkner

5
下面是将整个目录压缩成zip文件的一些代码。
这似乎在Windows和Linux上都可以创建zip文件。输出文件似乎可以在Windows(内置的压缩文件夹功能,WinZip和7-Zip)和Linux上正确提取。但是,zip文件中的空目录似乎是一个棘手的问题。下面的解决方案似乎有效,但是在Linux上运行"zipinfo"的输出令人担忧。此外,zip档案中的空目录权限设置不正确。这似乎需要进行更深入的研究。
我从这个velocity reviews thread这个python mailing list thread中获得了一些信息。
请注意,此函数旨在将文件放入zip档案中,其中要么没有父目录,要么只有一个父目录,因此它将修剪文件系统路径中的任何前导目录,并将它们排除在zip档案路径之外。这通常是您想要将目录制作成可以在不同位置提取的zip文件时的情况。

关键字参数:

dirPath -- 要归档的目录的字符串路径。这是唯一必需的参数。它可以是绝对路径或相对路径,但只包括一个或零个前导目录将被包含在zip归档中。

zipFilePath -- 输出zip文件的字符串路径。这可以是绝对路径或相对路径。如果zip文件已经存在,它将被更新。如果不存在,它将被创建。如果您想从头开始替换它,请在调用此函数之前删除它。(默认为 dirPath + ".zip")

includeDirInZip -- 布尔值,指示是否应包括顶级目录在归档中或省略。(默认为True)

(请注意,StackOverflow似乎无法漂亮地打印我的python代码,所以我在这里将我的文档字符串转换为帖子文本)

#!/usr/bin/python
import os
import zipfile

def zipdir(dirPath=None, zipFilePath=None, includeDirInZip=True):

    if not zipFilePath:
        zipFilePath = dirPath + ".zip"
    if not os.path.isdir(dirPath):
        raise OSError("dirPath argument must point to a directory. "
            "'%s' does not." % dirPath)
    parentDir, dirToZip = os.path.split(dirPath)
    #Little nested function to prepare the proper archive path
    def trimPath(path):
        archivePath = path.replace(parentDir, "", 1)
        if parentDir:
            archivePath = archivePath.replace(os.path.sep, "", 1)
        if not includeDirInZip:
            archivePath = archivePath.replace(dirToZip + os.path.sep, "", 1)
        return os.path.normcase(archivePath)

    outFile = zipfile.ZipFile(zipFilePath, "w",
        compression=zipfile.ZIP_DEFLATED)
    for (archiveDirPath, dirNames, fileNames) in os.walk(dirPath):
        for fileName in fileNames:
            filePath = os.path.join(archiveDirPath, fileName)
            outFile.write(filePath, trimPath(filePath))
        #Make sure we get empty directories as well
        if not fileNames and not dirNames:
            zipInfo = zipfile.ZipInfo(trimPath(archiveDirPath) + "/")
            #some web sites suggest doing
            #zipInfo.external_attr = 16
            #or
            #zipInfo.external_attr = 48
            #Here to allow for inserting an empty directory.  Still TBD/TODO.
            outFile.writestr(zipInfo, "")
    outFile.close()

这里有一些示例用法。请注意,如果您的dirPath参数有多个前导目录,则默认情况下仅包括最后一个。将includeDirInZip=False传递以省略所有前导目录。
zipdir("foo") #Just give it a dir and get a .zip file
zipdir("foo", "foo2.zip") #Get a .zip file with a specific file name
zipdir("foo", "foo3nodir.zip", False) #Omit the top level directory
zipdir("../test1/foo", "foo4nopardirs.zip")

4

对我来说最简单的方法是使用 zipfile CLI(命令行界面)。zipfile CLI 可以将文件或文件夹作为参数,并将它们递归地添加到归档文件中。

如果您有以下文件层次结构:

- file1.txt
- folder1 
   - file2.txt
   - file3.txt

如果你希望将所有内容存档到'result.zip'中,你只需写下:

python -m zipfile -c result.zip file1.txt folder

如果您想在Python代码中使用并导入zipfile模块,可以按照以下方式调用其主函数:

import zipfile
zipfile.main(['-c', 'result.zip', 'file1.md', 'folder'])

1
如果您想了解如何操作,请查看以下代码:https://github.com/python/cpython/blob/3.10/Lib/zipfile.py - Danilo Toro

3

这是我用来压缩文件夹的函数:

import os
import os.path
import zipfile

def zip_dir(dirpath, zippath):
    fzip = zipfile.ZipFile(zippath, 'w', zipfile.ZIP_DEFLATED)
    basedir = os.path.dirname(dirpath) + '/' 
    for root, dirs, files in os.walk(dirpath):
        if os.path.basename(root)[0] == '.':
            continue #skip hidden directories        
        dirname = root.replace(basedir, '')
        for f in files:
            if f[-1] == '~' or (f[0] == '.' and f != '.htaccess'):
                #skip backup files and all hidden files except .htaccess
                continue
            fzip.write(root + '/' + f, dirname + '/' + f)
    fzip.close()

3
如果您查看使用Info-ZIP创建的zip文件,您会发现目录确实被列出:
$ zip foo.zip -r foo
  adding: foo/ (stored 0%)
  adding: foo/foo.jpg (deflated 84%)
$ less foo.zip
  Archive:  foo.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
       0  Stored        0   0% 2013-08-18 14:32 00000000  foo/
  476320  Defl:N    77941  84% 2013-08-18 14:31 55a52268  foo/foo.jpg
--------          -------  ---                            -------
  476320            77941  84%                            2 files

注意目录条目长度为零且未压缩。似乎你可以通过按名称编写目录来实现相同的功能,但强制不使用压缩即可在Python中实现。
if os.path.isdir(name):
    zf.write(name, arcname=arcname, compress_type=zipfile.ZIP_STORED)
else:
    zf.write(name, arcname=arcname, compress_type=zipfile.ZIP_DEFLATED)

有可能值得确保 arcname/ 结尾。


2

在添加了一些导入后,您的代码在我的计算机上可以正常运行。请问您是如何调用脚本的?或者您能告诉我们“..\packed\bin”目录的文件夹结构吗?

我使用以下参数来调用您的代码:

planName='test.zip'
files=['z.py',]
folders=['c:\\temp']
(success,filename)=createZipFile(planName,files,folders)

`


用这些参数调用代码将创建一个名为test.zip的文件,其中包含z.py和c:\temp中的所有文件,但不包括文件夹。只是一个包含许多文件的zip文件。但我在相关问题中找到了一个似乎能够实现我想要的答案。我会进一步研究那个答案。 - Mizipzor
是的,也许你可以举个例子来调用代码,这样你的错误就会显示出来。 - RSabet
没有错误。代码创建了一个zip文件和所有的内容。问题在于当文件被解压缩时,我希望创建文件夹,或者包含文件夹的zip文件(取决于你的看法)。 - Mizipzor

0
非常感谢这个有用的函数!我发现它非常实用,因为我也在寻求帮助。不过,也许稍微修改一下会更有用。
basedir = os.path.dirname(dirpath) + '/'

会是

basedir = os.path.dirname(dirpath + '/')

因为我发现如果我想要压缩文件夹“Example”,它位于“C:\ folder \ path \ notWanted \ to \ zip \ Example”,

在Windows中,我得到了:

dirpath = 'C:\folder\path\notWanted\to\zip\Example'
basedir = 'C:\folder\path\notWanted\to\zip\Example/'
dirname = 'C:\folder\path\notWanted\to\zip\Example\Example\Subfolder_etc'

但我想你的代码应该会给出

dirpath = 'C:\folder\path\notWanted\to\zip\Example'
basedir = 'C:\folder\path\notWanted\to\zip\Example\'
dirname = '\Subfolder_etc'

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