要把一个大 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再交给解压软件。不要把两种命令混着用。