Skip to content

要把一个大 zip 拆成 WinRAR / 7-Zip 能认的分卷(name.z01、name.z02、…、最后一个仍叫 name.zip),不是简单按字节切开。第一个分卷前面要多 4 字节头:50 4B 07 08(也就是 PK\x07\x08)。单个 zip 里已经有一份类似的头,分卷格式等于再垫一次。

思路:先打一个完整 zip,再按「分卷大小」切。第一卷写入 4 字节头,后面只跟 分卷大小 - 4 字节的数据;其后每卷读满一个分卷大小;最后一卷名叫 .zip。

python
import os
import zipfile
from shutil import rmtree

def zip_by_volume(file_path, block_size):
    file_size = os.path.getsize(file_path)
    path, file_name = os.path.split(file_path)
    zip_file = file_path + ".zip"
    with zipfile.ZipFile(zip_file, "w") as zf:
        zf.write(file_path, arcname=file_name)
    if file_size <= block_size:
        return zip_file

    save_dir = os.path.join(path, file_name + "_split")
    if os.path.exists(save_dir):
        rmtree(save_dir)
    os.mkdir(save_dir)

    base = os.path.splitext(file_name)[0]
    with open(zip_file, "rb") as fp:
        packed_size = os.path.getsize(zip_file)
        count = packed_size // block_size + (1 if packed_size % block_size else 0)
        for i in range(1, count + 1):
            suffix = "zip" if i == count else "z{:02d}".format(i)
            out_name = os.path.join(save_dir, f"{base}.{suffix}")
            with open(out_name, "wb") as f:
                if i == 1:
                    f.write(b"\x50\x4b\x07\x08")
                    f.write(fp.read(block_size - 4))
                else:
                    f.write(fp.read(block_size))
    os.remove(zip_file)
    return save_dir

if __name__ == "__main__":
    src = "/path/to/large.bin"
    volume_size = 100 * 1024 * 1024
    print(zip_by_volume(src, volume_size))

缺点:先写一整份临时 zip,再切一遍,磁盘写两次。用内存缓冲对超大文件不现实。

注意另一种「.zip.001 + .zip.002」是按块切开的裸拼接,和上面的 spanning zip 不是同一种。那种在 Windows 上可以:

bat
copy /B file.zip.001+file.zip.002+file.zip.003 file.zip

再交给解压软件。不要把两种命令混着用。

测试开发工程师 · 专注自动化与系统架构 | 邮箱: hansblog@atumsoul.win