zip文件压缩解压

发布时间 2023-11-07 21:36:45作者: 守望人间

zip文件压缩解压

使用 python 操作 zip 文件,压缩和解压。

压缩

压缩文件时,可以将压缩文件保存在本地或保存在内存中,将压缩文件保存在内存中时,方便后续使用,比如直接返回给前端,也就不用再执行删除操作。

import zipfile
from pathlib import Path
from io import BytesIO


def zip_to_file():
    """压缩文件保存到文件中"""
    zip_file = Path('./test.zip')
    with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
        # 将字符串写入文件压缩
        zip_obj.writestr('test.txt', 'test file')
        # 压缩本地文件
        f = Path('./test.json')
        if f.exists():
            zip_obj.write(f, 'test.json')


def zip_to_mem():
    """压缩文件保存到内存中"""
    with BytesIO() as bio, zipfile.ZipFile(bio, 'w', zipfile.ZIP_DEFLATED) as zip_obj:  # 使用 BytesIO 接收压缩文件
        # 将字符串写入文件压缩
        zip_obj.writestr('test.txt', 'test file')
        # 压缩本地文件
        f = Path('./test.json')
        if f.exists():
            zip_obj.write(f, 'test.json')
        # 保存数据时,需要先手动 close,否则最后写入的文件会缺少一部分数据
        zip_obj.close()
        # 将内存中的数据保存到文件中方便展示效果,实际中可以直接发送给前端或其他处理
        with open('test.zip', 'wb') as fw:
            fw.write(bio.getvalue())


if __name__ == '__main__':
    zip_to_mem()

问题

当把压缩文件保存在 BytesIO 中时,按照以下方式保存时,使用 zipfile 去解压压缩文件,报错zipfile.BadZipFile: File is not a zip file,但用其他压缩软件可以正常解压。

with BytesIO() as bio, zipfile.ZipFile(bio, 'w', zipfile.ZIP_DEFLATED) as zip_obj:  # 使用 BytesIO 接收压缩文件
    # 将字符串写入文件压缩
    zip_obj.writestr('test.txt', 'test file')
    # 压缩本地文件
    f = Path('./test.json')
    if f.exists():
        zip_obj.write(f, 'test.json')
    # 将内存中的数据保存到文件中方便展示效果,实际中可以直接发送给前端或其他处理
    with open('test.zip', 'wb') as fw:
        fw.write(bio.getvalue())

检查 bio 中数据后,发现在 ZipFile 对象close方法中还会调用_write_end_record写入数据,而使用上面的保存方式时,在写入本地文件时,ZipFile 对象还没有调用close方法,导致最后写入的数据缺少一部分,因此在写入本地文件前,手动调用close方法。

异常数据:b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x95\x8d[W\xc1kd\xf2\x0b\x00\x00\x00\t\x00\x00\x00\x08\x00\x00\x00test.txt+I-.QH\xcb\xccI\x05\x00'
正常数据:b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x95\x8d[W\xc1kd\xf2\x0b\x00\x00\x00\t\x00\x00\x00\x08\x00\x00\x00test.txt+I-.QH\xcb\xccI\x05\x00PK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\x95\x8d[W\xc1kd\xf2\x0b\x00\x00\x00\t\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00test.txtPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x006\x00\x00\x001\x00\x00\x00\x00\x00'

解压

可以读取本地压缩文件或内存中压缩文件,然后解压到本地目录或内存中

import json
import zipfile
from pathlib import Path


def unzip_to_file():
    """压缩到文件中"""
    zip_file = Path('./test.zip')
    with zipfile.ZipFile(zip_file, 'r') as zip_obj:
        # 直接解压在当前目录
        zip_obj.extractall()


def unzip_to_mem():
    """解压到内存中"""
    zip_file = Path('./test.zip')
    with zipfile.ZipFile(zip_file, 'r') as zip_obj:
        # 循环压缩包中的所有文件
        for file_info in zip_obj.filelist:
            # 预防压缩炸弹,限制解压文件大小
            if file_info.file_size > 1024 * 1024 * 2:
                print(f'{file_info.filename} 文件大小异常')
                break
            if file_info.filename == 'test.txt':
                # 读取文件打印文件数据
                print(zip_obj.open(file_info).read())


def unzip_from_to_mem():
    """解压到内存中"""
    zip_file = Path('./test.zip')
    # 示例为了演示,先使用 open 打开文件,再传入 ZipFile,实际中,数据可能来自于前端上传等
    with open(zip_file, 'rb') as f, zipfile.ZipFile(f, 'r') as zip_obj:
        # 循环压缩包中的所有文件
        for file_info in zip_obj.filelist:
            # 预防压缩炸弹,限制解压文件大小
            if file_info.file_size > 1024 * 1024 * 2:
                print(f'{file_info.filename} 文件大小异常')
                continue
            if file_info.filename == 'test.json':
                # 读取文件打印文件数据
                print(json.loads(zip_obj.open(file_info).read()))


if __name__ == '__main__':
    unzip_from_to_mem()

PS

  1. 在读取内存中压缩文件时,示例是使用 open 打开文件,仅作为示例,实际中,应该是前端上传数据或其他方式
  2. 当使用 flask 接收上传的压缩文件时,如果后面会多次使用该数据,需要先将数据读取出来方便后续使用bytes_io = BytesIO(request.files.get('file').stream.read())