Python 分片下载 + 合并的实现

8次阅读

分片下载需动态计算range:起始为i×chunk_size,结束为min((i+1)×chunk_size−1, content-Length−1),用bytes={start}-{end};线程须各写独立临时文件,合并时用shutil.copyfileobj流式追加写入。

Python 分片下载 + 合并的实现

requests 分片下载时 Range 头怎么设才不越界

分片下载失败,十有八九是 Range 值算错了——特别是最后一个分片,容易设成超出文件总大小,触发 416 Range Not Satisfiable 错误。

关键不是“按固定大小切”,而是根据服务器返回的 Content-Length 动态算结尾字节位置:

  • 先发 HEAD 请求拿到 Content-Length(比如 1234567
  • 设分片大小为 chunk_size = 1024 * 1024(1MB),则第 i 片起始为 i * chunk_size
  • 结束位置取 min((i + 1) * chunk_size - 1, content_length - 1),注意减 1(Range 是闭区间)
  • 请求头必须带 headers={'Range': f'bytes={start}-{end}'},不能漏 bytes=

多线程写入同一个文件会损坏数据吗

会。直接用多个线程对同一文件句柄调用 write(),不加锁、不预分配,结果就是字节错乱、内容覆盖——哪怕你算好了每个线程该写哪段。

安全做法只有一条:每个线程写自己的临时文件,合并阶段再顺序拼接。

立即学习Python免费学习笔记(深入)”;

  • 分片下载时,给每片生成唯一临时名,如 f"part_{i:04d}.tmp"
  • 下载完全部分片后,按序读取这些 .tmp 文件,用 open(..., 'ab') 追加写入目标文件
  • 别用内存拼接大文件(比如 b''.join(chunks)),内存爆掉比下载慢更致命

合并时用 shutil.copyfileobj 还是 open().read()

shutil.copyfileobj。它底层用小缓冲块(默认 64KB)流式读写,内存占用稳定;而 open().read() 会把整个分片一次性加载进内存,100MB 分片就占 100MB 内存,N 片并发下载时极易 OOM。

实操示例(合并单个分片):

with open("part_0001.tmp", "rb") as fsrc:     with open("output.zip", "ab") as fdst:         shutil.copyfileobj(fsrc, fdst)
  • shutil.copyfileobj 默认缓冲区够用,不用改 length 参数
  • 确保目标文件以 "ab" 模式打开(追加二进制),不是 "wb"
  • 如果分片数多,合并循环里别重复 open("output.zip", "ab"),应该在外层打开一次,传进去

断点续传怎么判断哪些分片已下载完成

不能靠文件存在就认为下载完成——可能写了一半就中断了。得校验实际字节数是否匹配预期。

每个分片下载前,先检查对应临时文件:

  • 若文件不存在 → 正常下载
  • 若存在但 os.path.getsize(path) != expected_size → 删除重下(expected_size = end - start + 1
  • 若大小匹配 → 跳过,直接进入合并流程
  • 别用 os.path.exists() 单独判断,那是坑

临时文件名建议包含起始偏移和长度(如 part_0_1048575.tmp),方便调试时一眼看出范围。

最麻烦的其实是网络波动导致部分分片反复失败,这时候重试逻辑要配超时和指数退避,而不是死循环重试。

text=ZqhQzanResources