Python zipfile 只借助内存进行压缩与解压缩

Python zipfile 模块压缩与解压缩通常是对物理磁盘文件进行操作,比如参照官方的例子,生成压缩文件的代码是
with zipfile.ZipFile('spam.zip', 'w') as myzip:
    myzip.write('eggs.txt')
    myzip.write('beef.txt')
这样就生成了一个包含两个文件的压缩包 spam.zip, 相当于命令 zip spam.zip egges.txt beef.txt 的效果。用  unzip -l spam.zip 命令就能看到其中的两个文件。相应的解压缩的代码如下
with zipfile.ZipFile('spam.zip', 'r') as myzip:
    print(myzip.filelist())  # 可获得压缩包中的文件列表信息
    myzip.extractall()
同样是把压缩包 spam.zip 解压缩文件到当前目录中,相当于命令 unzip spam.zip 的效果。

前面顺便也是熟悉一下 zipfile 模块的常见用法,但有时候我们可能从数据库中,从网络上收到的是字节数据,希望直接处理字节的压缩解压缩,而不借助于中间的磁盘文件,因为通过磁盘文件来处理必须进行善后处理以及可能的资源的竞争,在内存宽裕的情况下效率也是个问题。

在 Java 中处理内存数据经常用到的是 ByteArrayInputStreamByteArrayOutputStream,而在 Python 中承担相同角色的就是 io.BytesIO()。下面来看如何使用它在内存进行双向操作

通过内存生成压缩文件字节内容

本例放到压缩包中的内容是字节,生成的压缩包也是字节表现形式
 1import zipfile
 2import io
 3
 4file = io.BytesIO()
 5with zipfile.ZipFile(file, 'w', zipfile.ZIP_DEFLATED) as myzip:
 6    myzip.writestr('eggs.txt', 'Here are eggs')
 7    myzip.writestr('beef.txt', b'I am beef')
 8    myzip.writestr('veggs/pickle.txt', 'pickles')
 9
10zip_data = file.getvalue()
11
12with open('spam.zip', 'wb') as zipfile:
13    zipfile.write(zip_data) 

只需调用 myzip.writestr(filename, original_content) 方法就行,数据可以是字符串或 bytes,filename 部分还可以有目录结构

不指定 zipfile.ZIP_DEFLATED 的话不会对数据进行压缩,只是存储。

上面产生的  zip_data 就是压缩包的字节内容,我们把 zip_data 字节数据保存到  spam.zip 压缩文件,然后用 unzip -l spam.zip 命令查看
1$ unzip -l spam.zip
2Archive:  spam.zip
3  Length      Date    Time    Name
4---------  ---------- -----   ----
5       13  10-13-2021 22:17   eggs.txt
6        9  10-13-2021 22:17   beef.txt
7        7  10-13-2021 22:17   veggs/pickle.txt
8---------                     -------
9       29                     3 files

如果解压一下,会看到目录结构与我们设想的一致,三个文件 eggs.txt, beef.txtveggs/picle.txt 中的内容分别是字符符 Here are eggs, I am beef, 和 pickles

解压缩 zip 字节内容为字节数据

现在我们直接使用上一步产生的 spam.zip 文件内容,首先假定输入为字节数据,然后窥探其中每一个条目的文件信息与内容
 1import zipfile
 2import io
 3import os
 4
 5
 6def read_zipfiles(path, folder=''):
 7    for member in path.iterdir():
 8        filename = os.path.join(folder, member.name)
 9        if member.is_file():
10            print(filename, ':', member.read_text()) # member.read_bytes()
11        else:
12            read_zipfiles(member, filename)
13
14
15with open('spam.zip', 'rb') as myzip:
16    zip_data = myzip.read()
17
18
19with zipfile.ZipFile(io.BytesIO(zip_data)) as zip_file:
20    read_zipfiles(zipfile.Path(zip_file))

这里的代码使用到了递归一直罗列压缩包中的条目,压缩包中文件的内容可用 read_text()read_bytes() 分别读出为文本或字节。这是此代码执行后的输出
$ python tt.py
eggs.txt : Here are eggs
beef.txt : I am beef
veggs/pickle.txt : pickles
如果我们明确的知道(比如约定了)压缩包中不会有目录层次,则可不用递归来处理。

链接:
  1. Zipfile module for Python3.6: write to Bytes instead of Files for Odoo
  2. Python Zipfile
永久链接 https://yanbin.blog/python-zipfile-compress-decompress-in-memory/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。