You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Python3标准库移除文件末尾嵌入的ZIP归档并更新内容

实现方案

要保留前置宿主文件、移除末尾嵌入的ZIP归档,核心是准确定位ZIP在拼接文件中的起始偏移,再将文件截断到该位置即可,全程仅使用Python3标准库即可实现,不需要额外依赖。

核心原理

完整的ZIP归档末尾必然存在中央目录结束记录(EOCD),固定签名为字节序列b'PK\x05\x06':

  • EOCD固定头长度为22字节,尾部最多可携带65535字节的自定义注释,因此只需从文件末尾向前读取最多65557字节,就一定能定位到EOCD签名,不需要读取整个文件
  • 从EOCD固定头中可以解析出两个关键值:中央目录总大小、中央目录相对于ZIP起始位置的偏移量
  • 由于ZIP结构中中央目录紧接在文件内容之后、EOCD紧接在中央目录之后,因此可以直接通过EOCD的位置反推整个ZIP的起始偏移
  • 对于超过4GB、文件数超过65535的ZIP64格式归档,额外解析EOCD前方的ZIP64定位记录即可,逻辑一致

实现代码

import struct

def strip_trailing_zip(file_path: str) -> int:
    """
    移除文件末尾嵌入的ZIP归档,保留前置宿主内容
    返回截断后文件的大小(即宿主内容的字节长度),如果文件末尾无有效ZIP则返回原文件大小
    """
    EOCD_SIGN = b'PK\x05\x06'
    ZIP64_LOC_SIGN = b'PK\x06\x07'
    EOCD_MIN_SIZE = 22
    MAX_COMMENT_SIZE = 0xFFFF
    READ_TAIL_SIZE = EOCD_MIN_SIZE + MAX_COMMENT_SIZE

    with open(file_path, 'rb+') as f:
        f.seek(0, 2)
        file_size = f.tell()
        # 文件过小,不可能包含ZIP
        if file_size < EOCD_MIN_SIZE:
            return file_size
        
        # 读取文件尾部块
        read_size = min(file_size, READ_TAIL_SIZE)
        f.seek(file_size - read_size)
        tail_block = f.read(read_size)

        # 从后往前查找EOCD签名
        eocd_pos_in_tail = tail_block.rfind(EOCD_SIGN)
        if eocd_pos_in_tail == -1:
            return file_size
        eocd_abs_pos = file_size - read_size + eocd_pos_in_tail

        # 解析EOCD固定头
        # 结构:4字节签名 + 3个2字节字段 + 2个4字节字段(中央目录大小、中央目录偏移)+ 2字节注释长度
        eocd_header = tail_block[eocd_pos_in_tail : eocd_pos_in_tail + EOCD_MIN_SIZE]
        _, _, _, _, cd_size, cd_offset, comment_len = struct.unpack('<IHHHHIIH', eocd_header)

        # 处理ZIP64格式:如果普通EOCD里的偏移是0xFFFFFFFF,说明是ZIP64,需要读前面的ZIP64定位器和ZIP64 EOCD
        if cd_offset == 0xFFFFFFFF or cd_size == 0xFFFFFFFF:
            # ZIP64定位器在普通EOCD前方20字节,签名b'PK\x06\x07'
            zip64_loc_pos = eocd_abs_pos - 20
            if zip64_loc_pos < 0:
                return file_size
            f.seek(zip64_loc_pos)
            zip64_loc_header = f.read(20)
            if zip64_loc_header[:4] != ZIP64_LOC_SIGN:
                return file_size
            # 解析ZIP64 EOCD的绝对位置
            _, zip64_eocd_disk, zip64_eocd_offset, total_disks = struct.unpack('<IIQI', zip64_loc_header)
            # 读ZIP64 EOCD头(固定56字节)
            f.seek(zip64_eocd_offset)
            zip64_eocd_header = f.read(56)
            if zip64_eocd_header[:4] != b'PK\x06\x06':
                return file_size
            # 解析ZIP64 EOCD里的中央目录大小和偏移
            _, _, _, _, _, _, _, cd_size, cd_offset, _ = struct.unpack('<IQHHIIQQQQ', zip64_eocd_header)

        # 计算ZIP归档的起始绝对偏移
        zip_start_pos = eocd_abs_pos - cd_size - cd_offset
        if zip_start_pos < 0:
            return file_size
        
        # 截断文件到ZIP起始位置,丢弃后面的ZIP内容
        f.truncate(zip_start_pos)
        return zip_start_pos

使用流程

更新嵌入ZIP的完整操作步骤如下:

  • 先备份原文件,避免操作失误导致数据丢失
  • 调用strip_trailing_zip(目标文件路径),函数会自动移除末尾旧的ZIP归档,保留前置宿主内容
  • 以追加模式'a'打开文件创建ZipFile对象,将需要保留的原归档文件、新增/修改的文件逐一写入,需要删除的文件跳过即可
  • 关闭ZipFile即完成更新,新的ZIP会自动追加到宿主内容末尾,结构和原拼接文件完全一致

注意事项

  • 该方案仅适用于完整ZIP归档直接追加在宿主文件尾部的场景,包括ZipFile a模式生成的拼接文件、ZIP格式隐写文件、ZIP拼接EXE的自解压包等,不兼容ZIP数据拆分插入宿主文件其他位置的情况
  • 代码兼容普通ZIP和ZIP64格式,支持单文件最大4GB以上的大体积归档
  • 所有逻辑仅依赖Python内置的文件IO和struct模块,不需要安装任何第三方依赖

内容的提问来源于stack exchange,提问作者kiv_apple

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 23:13:00