因为默认请求不带range头,服务器返回完整文件而非部分内容,导致中断后必须重传且浪费带宽;需先查本地文件大小,发送range: bytes=start-请求,用'ab'模式写入,并校验206状态码。

为什么直接用 requests.get() 无法断点续传
因为默认请求不带 Range 头,服务器不会返回部分内容,而是从头开始发整个文件。即使你本地已下载一半,requests.get() 仍会重新拉取全部,浪费带宽且无法恢复中断。
真正支持断点续传,必须:主动检查本地文件长度 → 发送带 Range: bytes=<var>start</var>- 的请求 → 以 ab(追加写)模式写入文件。
- 服务端需支持
Accept-Ranges: bytes(可用curl -I <var>url</var>查看响应头) - 若响应状态码不是
206 Partial Content,说明服务端不支持或拒绝范围请求 - 注意:某些 CDN 或 Nginx 默认关闭
range支持,需配置enable_ranges on;
如何构造带 Range 的请求并安全写入文件
核心逻辑是先读取本地文件当前大小,作为下次请求的起始偏移;再用 requests.get(..., headers={'Range': f'bytes={file_size}-'}, stream=True) 获取剩余内容。
关键细节:
- 务必设
stream=True,否则response.content会加载全部响应体,失去流式续传意义 - 打开文件必须用
open(..., 'ab')—— 不能用'wb'(会清空),也不能用'a'(文本模式写二进制会出错) - 检查
response.status_code:应为206;若返回200,说明服务端忽略Range,需降级为全量重下或报错 - 写入前建议校验
Content-Range响应头,确认起始位置与预期一致(例如bytes 1024-9999/10000)
怎么处理网络中断和重试逻辑
断点续传真正的难点不在首次请求,而在多次失败后的状态一致性:文件大小、已写位置、HTTP 连接异常都需要协同处理。
实操建议:
- 每次写入前,用
os.path.getsize()动态获取当前文件大小,而不是依赖内存变量(防止中间被其他进程修改) - 对
requests.exceptions.ConnectionError、Timeout等做指数退避重试(如time.sleep(2 ** retry_count)),最多 3–5 次 - 避免在重试循环里重复发送
HEAD请求查总大小——它可能不准确(比如服务端动态生成文件),应以首次成功响应中的Content-Length或Content-Range为准 - 下载完成前,可临时写入
.downloading后缀,防止程序崩溃后残留不完整文件被误用
要不要用 urllib3 或 aiohttp 替代 requests
requests 完全够用,但要注意它底层 urllib3 对 Range 请求的支持是完整的;而 aiohttp 在异步场景下需手动管理 Range 和文件写入锁,复杂度陡增,除非你已有 asyncio 架构,否则没必要切换。
真正值得替换的情况只有一种:需要并发下载多个大文件且对内存敏感。此时可考虑 urllib3.PoolManager 复用连接,或用 httpx(支持同步/异步 + 自动 Range 探测)。
但多数场景下,老老实实用 requests + 显式 Range 头最稳——毕竟断点续传的核心不是并发能力,而是状态可控和错误可追溯。
最后提醒:别依赖第三方“断点续传库”,很多只是封装了 Range 请求,却没处理文件大小竞争、服务端不返回 Content-Range、或重定向后丢失 Range 头等真实坑点。自己控制每一步,才真正可控。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











