文章目录
前言
本文主要介绍zipfile模块下ZipFile类的使用方法,包括对现在zip文件中读取及创建。
一、ZIP文件的读取
1、ZipFile类的四种模式
zipfile模块中的ZipFile类的使用方法类似 Python 内置的 open() 函数,允许使用不同的模式打开 ZIP文件,如下:
模式 | 描述 |
---|---|
r | 读取模式 |
w | 写入模式 |
a | 追加模式 |
x | 独占模式 |
- 读取模式
示例:打开"test.zip"文件。
import zipfile
with zipfile.ZipFile("test.zip",mode="r") as files:
files.printdir()
ZipFile()初始化的第一个参数可以是一个字符串,表示需要打开的ZIP文件的路径,这个参数也可以接受文件对象和路径类对象。
ZipFile()初始化的第二个参数是一个单字母的字符串,表示用于打开文件模式。
printdir()函数提供了一种在屏幕上显示底层ZIP文件内容的快捷方法。其输出的易读的表格形式,有三列信息:
- File Name
- Modified
- Size
- 写入模式
示例:将"hello.txt"添加到"test.zip"中,可以使用写入模式(“w”)。
import zipfile
with zipfile.ZipFile("test.zip","w") as files:
files.write("hello.txt")
files.printdir()
从上面的例子可以看到使用"w"模式写入"hello.txt"文件覆盖了原有的内容。
注意: 使用"w"模式写入内容,如果目标ZIP文件存在,会覆盖原有的内容,重新写入新的内容,有可能导致原有的内容丢失,要小心使用。如果目标ZIP文件不存在,会自动创建一个zip文件,并写入要传入的内容。
- 追加模式
追加模式(“a”)允许将新的文件内容追加到现在的目标ZIP文件中,不会覆盖原有的文件,因此原有的内容是安全的。如果目标ZIP文件不存在,则"a"模式会为您创建一个新文件,然后追加作为参数,传入.write()的任何文件。
示例:往"test.zip"目标文件中追加"new_hello.txt"。
import zipfile
with zipfile.ZipFile("test.zip","a") as files:
files.write("new_hello.txt")
files.printdir()
- 独占模式
独占模式(“x”)允许您独占地创建新的ZIP文件并将新的成员文件写入其中,当想要创建一个新的ZIP文件而不覆盖现有文件时,使用独占模式。
示例:往已经存在"test.zip"目标文件中追加"article.txt"。
import zipfile
with zipfile.ZipFile("test.zip","x") as files:
files.write("article.txt")
注意: 如果目标ZIP文件已存在,则会得到FileExistsError。
2、解析ZIP文件中的元数据
方法 | 描述 |
---|---|
.getinfo(filename) | 返回一个关于filename提供的成员文件的信息的ZipInfo对象,注意filename必须包含底层zip文件中目标文件的路径。 |
.infolist() | 返回一个ZipInfo对象列表,每个文件占一项。 |
.namelist() | 返回一个包含底层归档中所有成员文件名的列表。该列表中的名称是.getinfo()的有效参数。 |
- getinfo(filename)
示例:返回"test.zip"关于"hello.txt"成员文件的ZipInfo对象。
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
info = files.getinfo("hello.txt")
info.filename #'hello.txt'
info.file_size #11
info.compress_size # 11
info.date_time # (2023, 11, 30, 16, 51, 0)
getinfo(filename)将成员文件作为参数,并返回一个包含其信息的ZipInfo对象。
ZipInfo对象有一些属性:
ZipInfo.filename:文件名。
ZipInfo.file_size:未压缩原始文件的大小(以字节为单位)。
ZipInfo.compress_size:压缩原始文件的大小(以字节为单位)。
ZipInfo.date_time:最后修改日期。
注意: 默认情况下,ZipFile 不会压缩输入文件以将其添加到最终归档中。这就是上例中 file_size 和 compress_size 大小相同的原因。
- infolist()
示例:返回"test.zip"的所有成员文件的ZipInfo对象列表。
import zipfile
import datetime
with zipfile.ZipFile("test.zip","r") as files:
for info in files.infolist():
print(f"Filename:{info.filename}")
print(f"Modified:{datetime.datetime(*info.date_time)}")
print(f"Normal size:{info.file_size} bytes")
print(f"Compress size:{info.compress_size} bytes")
print("-"*20)
# 输出:
# Filename:hello.txt
# Modified:2023-11-30 16:51:00
# Normal size:11 bytes
# Compress size:11 bytes
# --------------------
# Filename:new_hello.txt
# Modified:2023-11-30 16:55:00
# Normal size:51 bytes
# Compress size:51 bytes
# --------------------
for循环迭代来自.infolist()的ZipInfo对象,检索文件名,最后修改日期、未压缩大小以及每个成员文件的压缩后大小。
- namelist()
示例1:快速检查并列出成员文件的名称。
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
for filename in files.namelist():
print(filename)
# 输出:
# hello.txt
# new_hello.txt
示例2:有一个zip文件,其中包含了不同类型的成员文件(.docx、.xlsx、.txt等)。我们只需要获取有关.txt文件的信息,并且不需要获取完整信息,只需要提取.date_time,即最后修改日期。可以先按扩展名过滤文件并仅在.docx文件上调用.getinfo()。
import os
import fnmatch
import zipfile
matches = []
extension="txt"
with zipfile.ZipFile("test.zip","r") as files:
for filename in files.namelist():
if fnmatch.fnmatch(filename , f"*.{extension}"):
matches.append(filename)
info = files.getinfo(filename)
print(info.date_time)
print(matches)
# 输出:
# hello.txt的最后创建时间: (2023, 11, 30, 16, 51, 0)
# new_hello.txt的最后创建时间: (2023, 11, 30, 16, 55, 0)
# ['hello.txt', 'new_hello.txt']
3、解析ZIP文件中的内容
- .read()
使用.read(),需要使用读取(“r”)或者追加(“w”)模式打开ZIP文件,注意,.read()以字节流的形式返回目标文件的内容。
示例1:
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
for line in files.read("new_hello.txt").split(b"\r\n"):
print(line)
注意:.read()以字节流形式返回目标文件的内容,在些示例中,以换行符"\r\n"作为分隔符,使用.split()将流拆分为行。因为.split()正在一个字节对象上进行操作,所以需要在字符串前添加前导b作为参数。
示例2:
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
for line in files.read("new_hello.txt",pwd=b"test").split(b"\n"):
print(line)
ZipFile.read()还接受名为pwd的第二个位置参数。提供密码 test 来读取加密文件。pwd参数接受字节类型的值。
如果在未提供所需密码的情况下对加密文件使用.read(),则会报错RuntimeError: File 'new_hello.txt' is encrypted, password required for extraction
。
注意: Python 的 zipfile 支持解密。但是它不支持创建加密 ZIP 文件。这就是需要使用外部文件归档工具来加密文件的原因。
示例3:ZipFile.setpassword()
import zipfile
with zipfile.ZipFile() as files:
files.setpassword(b'abc')
for file in files.namelist():
print(file)
print("-"*20)
for line in files.read(file).split(b"\n"):
print(line)
使用 .setpassword() 只需要提供一次密码,ZipFile 使用该唯一密码来解密所有成员文件。这种访求可以避免每次调用.read()或其他接受pwd参数的方法时提供密码。但如果ZIP文件每个成员文件的密码不一样的的话,就需要使用 .read() 和 pwd 参数为每个成员文件提供特定的密码。
注意: 当使用 pwd 参数时,会覆盖已经通过的 .setpassword() 设置的任何归档级密码。
- .open()
示例1:.open()与"r"模式组合使用。
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
with file.open("new_hello.txt","r") as hello:
for line in hello:
print(line)
示例2:.open()与"w"模式组合使用。
以追加模式打开test.zip,然后通过"w"模式调.open()的创建"new_world.txt"。返回一个支持.write()的类文件对象,允许将字节写入新创建的文件。
import zipfile
with zipfile.ZipFile("test.zip","a") as files:
with files.open("new_world.txt","w") as new_world:
new_world.write(b"welcome to world!")
with zipfile.ZipFile("test.zip","r") as files:
files.printdir()
print("-"*20)
files.read("new_world.txt")
在些示例中,将 b"welcome to world!" 写入"new_world.txt"。当执行流退出内部的with语句时,Python将输入字节写入成员文件。当外部的with语句退出时,Python会将"new_hello.txt"写入底层ZIP文件"test.zip"。
注意: 需要为.open()提供一个非现有的文件名,如果使用底层归档中已经存在的文件名,那么会最终得到一个重复的文件和一个UserWarning异常。
- 读取文本内容
.read()和.write()这两种方法都仅适用于字节。
方法一:bytes.decode()
ZipFile.read() 以字节形式返回目标成员文件的内容,故 .decode() 可以直接对这些字节进行操作。.decode() 方法使用给定的字符编码格式将 bytes 对象解码为字符串。
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
text = files.read("new_hello.txt").decode("utf-8")
print(text)
在此示例中,将"hello.txt"的内容作为字节读取。然后调用 .decode() 来将字节解码为使用 UTF-8 作为编码的字符串。
注意: 简体中文读者,最常见的一个乱码错误是,中文文本文件使用 Windows 默认的 GB2312 编码保存,而此处被以默认的 UTF-8 解码。因此见到许多“烫烫烫的锟斤拷”时不必惊慌,指定使用 GB2312 再尝试读取一次,很可能便解决了问题。
方法二:io.TextIOWrapper()
示例:使用 io.TextIOWrapper() 将"new_hello.txt"成员文件作为文字流读取。
import io
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
with files.open("new_hello.txt","r") as hello:
for line in io.TextIOWrapper(hello,"utf-8"):
print(line.strip())
在些示例中,内层with语句从"test.zip"归档中打开成员文件"new_hello.txt",然后生成的二进制类文件对象hello作为参数传递给io.TextIOWrapper。通过使用UTF-8字符编码格式解码hello内容来创建有缓冲的文字流。因此,可以直接从目标成员文件中获得文字流。
4、读取ZIP文件下的成员文件
- .extract() 一次提取一个文件。
此方法接受一个成员文件的名称,并将其提取到由 path 指定的目录。目标 path 默认为当前目录:
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
files.extract("hello.txt",path=".//test")
在些示例中,现在"hello.txt"将会出现在 .//test 目录中。
如果目标文件名已存在于输出目录中,则 .extract() 将不经请求确认直接覆盖它。
如果输出目录不存在,则 .extract() 会为您创建它。注意 .extract() 返回提取文件的路径。
示例:提取指定类型文件。比如 “test.zip” 文件下有两个 .txt 文件和一个 .py 文件。现在只提取 .txt 文件。
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
for file in files.namelist():
if file.endswith(".txt"):
files.extract(file,".//extract_test")
with 语句打开 “test.zip” 以供读取。循环使用 namelist() 遍历归档中的每个文件,而条件语句检查文件名是否以 .txt 扩展名结尾。如果是,则使用 .extract() 将手头的文件解压到目标目录 .//extract_test。
- .extractall() 一次提取所有文件
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
files.extractall(".//test")
“test.zip” 中的所有当前内容将出现在./ 目录中。
如果将一个不存在的目录传递给 .extractall(),那么该方法会自动创建这个目录。
如果目标目录中已经存在任何成员文件,那么 .extractall() 将不经请求确认直接覆盖掉它们,所以要小心。
如果只需要从给定归档中提取一些成员文件,则可以使用 members 参数。此参数接受成员文件列表,即手头归档中全部文件列表的子集。
- .extract() 与 .extractall() 方法一样,都用于加密文件。在这种情况下,需要提供所需的 pwd 参数或使用 .setpassword() 设置归档级密码。
示例1:
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
for file in files.namelist():
if file.endswith(".txt"):
files.extract(file,".//extract_test",pwd=b"abc")
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
files.setpassword(b"abc")
for file in files.namelist():
if file.endswith(".txt"):
files.extract(file,".//extract_test")
示例2:
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
files.extractall(".//test",pwd=b"abc")
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
files.setpassword(b"abc")
files.extractall(".//test")
5、确保打开的是有效的ZIP文件
- try…expect…
import zipfile
try:
with zipfile.ZipFile("test.zip") as files:
files.printdir()
except zipfile.BadZipFile as error:
print(error)
如果示例中可以成功打开"test.zip"文件而不引发BadZipFile异常。test.zip是有效的ZIP格式;如果无法成功打开test.zip文件,那么该文件不是有效的ZIP文件。
- is_zipfile()
该函数接受一个保存文件系统中ZIP文件路径的filename参数,此参数可以接受字符串,类文件或路径对象。
如果filename是有效的ZIP文件,则该函数返回True,否则返回False。
import zipfile
if zipfile.is_zipfile("test.zip"):
with zipfile.ZipFile("test.zip","r") as files:
files.printdirt()
else:
print("File is not a zip file")
6、使用后关闭ZIP文件
- close()
import zipfile
with zipfile.ZipFile("test.zip","r") as files:
files.printdir()
files.close()
二、ZIP文件的创建
1、从多个常规文件创建ZIP文件
- 将多个相关的文件创建ZIP归档。
示例1:
import zipfile
filenames=["article.txt","movies.txt"]
with zipfile.ZipFile("multiple_files.zip","w") as zip_files:
for filename in filenames:
zip_files.write(zip_files)
for()循环遍历输入的文件列表,并使用.write()将它们写入底层文件ZIP文件,一旦流程退出 with 语句,ZipFile 会自动关闭归档,保存更改。现在获得一个包含原始文件列表中所有文件的 multiple_files.zip 归档。
2、从目录创建ZIP文件
- 将一个目录中的内容打包到ZIP文件中归档。
示例1:目录中不包括子目录。在 .//test 中,只有三个普通文件。
import pathlib
import zipfile
directory = pathlib.Path(".//test") #从源目录中创建一个pathlib.Path对象
with zipfile.ZipFile("directory_tree.zip","w") as zip_files:
for file_path in directory.iterdir(): #pathlib.Path对象对iterdir()的调用会返回一个遍历底层目录中条目的迭代器
zip_files.write(file_path,arcname=file_path.name)
with zipfile.ZipFile("directory_tree.zip","r") as zip_files:
zip_files.printdir()
在此示例中,
使用 pathlib.Path.iterdir() 递归遍历 .//test 下条目,然后将每个文件写入目录ZIP归档。
使用 file_path.name 传递给 .write() 的第二个参数。此参数名为 arcname,保存着生成的归档中成员文件的名称。
注意: 如果不将 file_path.name 传递给 arcname ,那么默认源目录将作为ZIP文件的源目录。
示例2:目录下有普通文件和一个包含文件的子目录。在 .//test 中,包含有三个普通文件和一个包含2个文件的子目录test_1。
import pathlib
import zipfile
directory = pathlib.Path(".//test") #从源目录中创建一个pathlib.Path对象
with zipfile.ZipFile("directory_tree.zip","w") as zip_files:
for file_path in directory.rglob("*"): #使用pathlib.Path.rglob()递归遍历 .//test 下的目录树
zip_files.write(file_path,arcname=file_path.relative_to(directory))
with zipfile.ZipFile("directory_tree.zip","r") as zip_files:
zip_files.printdir()
在此示例中,
使用 pathlib.Path.rglob() 递归遍历 .//test 下的目录树,然后将每个文件和子目录写入目录ZIP归档。
使用 pathlib.Path.relative_to() 来获取每个文件的相对路径,然后将其结果传递给.write()的第二个参数 arcname ,这样生成的 ZIP文件 最终具有与源目录相同的内部结构,注意: 如果你希望源上当作为ZIP文件的根目录,则可以去掉这个参数。
3、压缩文件和目录
compression 方法是 ZipFile 初始化方法的第三个参数,如果想在 ZIP文件 创建时压缩文件,则可以将此参数设置为以下常量之一:
常量 | 压缩方法 | 所需模块 |
---|---|---|
zipfile.ZIP_DEFLATED | Deflate | zlib |
zipfile.ZIP_BZIP2 | Bzip2 | bz2 |
zipfile.ZIP_LZMA | LZMA | lzma |
在压缩文件时,另一个与 ZipFile 相关参数是 compresslevel。些参数控制使用的压缩级别,使用 Deflate 方法,compresslevel 可以取从 0 到 9 的整数。使用 Bzip2 方法,可以传递从 1 到 9 的整数。在这两种情况下,当压缩级别增加时,压缩率会更高,压缩速度会更慢。
示例1:使用 Deflate 方法来归档和压缩给定目录的内容(最常用的方法)。
import pathlib
from zipfile import ZipFile,ZIP_DEFLATED
directory = pathlib.Path(".//test")
with zipfile.ZipFile("directory_new.zip","w",ZIP_DEFLATED,compresslevel=9) as zip_files:
for file_path in directory.rglob("*"):
zip_files.write(file_path,arcname=file_path.relative_to(directory))
在些示例中,将9传递给copresslevel以获得最大压缩。提供此参数时要使用关键字参数,这是因为compresslevel 并非 ZipFile 初始化时的第四个位置参数。
运行此代码后,在当前目录中会出现一个"directory_new.zip"文件,你会发现这个文件的大小与原始的directory_tree.zip相比较,有显著减小。
示例2:压缩的.//test 目录下的文件大于 4 GB 。
import pathlib
from zipfile import ZipFile,ZIP_DEFLATED
directory = pathlib.Path(".//test")
with zipfile.ZipFile("data.zip","w",ZIP_DEFLATED,compresslevel=9) as zip_files:
for file_path in directory.rglob("*"):
zip_files.write(file_path,arcname=file_path.relative_to(directory))
报错:RuntimeError: File size unexpectedly exceeded ZIP64 limit
原因:
ZipFile 的初始化时,接受名为 allowZip64 的第四个参数。这是一个布尔参数,告诉 ZipFile 为大于 4 GB 的文件使用 .zip64 扩展。默认情况下为False。
解决方案:传入 allowZip64=True 参数来创建 ZIP64 格式的压缩文件。
import pathlib
from zipfile import ZipFile,ZIP_DEFLATED
directory = pathlib.Path(".//test")
with zipfile.ZipFile("data.zip","w",ZIP_DEFLATED,compresslevel=9,allowZip64=True) as zip_files:
for file_path in directory.rglob("*"):
zip_files.write(file_path,arcname=file_path.relative_to(directory))
4、依次创建ZIP文件
示例:要创建一个包含或不包含内容的初始 ZIP 文件,然后在新成员文件可用时立即追加它们。
import zipfile
def append_member(zip_file,member):
with zipfile.ZipFile(zip_file,mode="a") as zip_files:
zip_files.write(member)
def get_file_from_stream():
for file in files:
yield file
files=["hello.txt","movies.txt","new_hello.txt"]
for filename in get_file_from_stream():
append_member("incremental.zip",filename)
with zipfile.ZipFile("incremental.zip",mode="r") as zip_files:
zip_files.printdir()
在此示例中,append_member() 函数将文件(member)追加到输入 ZIP归档(zip_file),该函数会在每次被调用时打开和关闭目标归档。
get_file_from_stream() 函数是一个生成器函数,用于模拟处理的文件流。
最后,for循环调用 append_member() 函数,将成员文件依次添加到 incremental.zip中。
三、使用命令行运行zipfile
使用方法:
Usage:
zipfile.py -l zipfile.zip # Show listing of a zipfile
zipfile.py -t zipfile.zip # Test if a zipfile is valid
zipfile.py -e zipfile.zip target # Extract zipfile into target dir
zipfile.py -c zipfile.zip src ... # Create zipfile from sources
- 1、查看ZIP文件的成员文件信息
语法:python -m zipfile -l ZIP文件名
示例:
/data1/iap/dingji/Python-3.6.5/python -m zipfile -l data.zip
运行结果:
File Name Modified Size
data_cj_2024020_20240215.csv 2024-02-16 19:07:36 2548251
data_high_2024020_20240215.csv 2024-02-16 19:07:42 32446
从示例中, 可以看出,和zipinfo对象的printdir()的输出结果一样。
- 2、判断是否为有效的ZIP文件
语法:python -m zipfile -t ZIP文件名
示例1:data.zip文件存在。
python -m zipfile -t data.zip
运行结果:
Done testing
- 示例2:data_1.zip文件不存在。
python -m zipfile -l data_1.zip
运行结果:
FileNotFoundError: [Errno 2] No such file or directory: 'data_1.zip'
- 3、提取ZIP成员文件到目标路径下面
语法:python -m zipfile -e ZIP文件名 新的路径
示例:
python -m zipfile -e data.zip .//test
运行此命令后,工作目录中将有一个新的 .//test 文件夹,里面包含了data.zip 的所有成员文件。
- 4、创建ZIP文件
语法:python -m zipfile -c 新建ZIP文件名 成员文件1 成员文件2 …
示例:
python -m zipfile -c new_data.zip data_cj_2024020_20240215.csv data_high_2024020_20240215.csv
此命令创建一个 new_data.zip 文件,其中包含data_cj_2024020_20240215.csv、data_high_2024020_20240215.csv文件。