登录
首页 >  文章 >  python教程

Python流式下载内存优化技巧

时间:2026-02-17 23:32:42 466浏览 收藏

本文深入剖析了Python中使用requests进行大文件流式下载时的关键内存优化策略,直击“不加stream=True导致内存暴增甚至OOM”的痛点,强调必须显式启用流模式并科学分块读取;详解iter_content(8192)为何是兼顾性能、内存与稳定性的黄金选择,对比手动write与shutil.copyfileobj()在容错性上的本质差异,并揭露超时与重试机制在流场景下的常见误区——真正决定下载健壮性的,不是代码行数,而是对网络中断、磁盘满、空闲超时、重定向陷阱等真实边界问题的系统性应对。

Python 流式下载的内存优化技巧

requests.get() 不加 stream=True 会吃光内存

直接用 requests.get("https://big-file.com/data.zip") 下载大文件,响应体默认全部加载进内存,哪怕文件 500MB,Python 进程 RSS 就可能瞬间涨到 600MB+,还容易触发 OOM。这不是 bug,是 requests 的默认行为——它得帮你把 response.content 准备好。

必须显式打开流模式:requests.get(url, stream=True)。这之后 response.content 不再可用(会触发读取整个响应的副作用),你得靠 response.iter_content() 或手动调 response.raw 分块读。

  • 不加 stream=True:适合小响应(10MB,就别碰
  • 加了之后:必须自己管理读取循环,不能依赖 .json().text(它们会强制读完)
  • 注意:HTTP 重定向(302)默认会被 requests 自动跟随,且跟随后的响应也受 stream=True 控制——这点常被忽略,导致你以为开了流,其实重定向后又全载入了

iter_content(chunk_size=8192) 是最稳的分块读法

response.iter_content() 是 requests 官方推荐的流式读取方式,比直接读 response.raw 更安全:它自动处理压缩(如 gzip)、解码、连接中断重试逻辑(在 chunk 级别)。

关键在 chunk_size 参数——不是越大越好,也不是越小越省。实测 8KB(8192)是多数场景下的甜点值:

  • chunk_size=1:系统调用太频繁,CPU 负担翻倍,吞吐反而下降
  • chunk_size=1024*1024(1MB):单次分配大 buffer,GC 压力大;网络抖动时,一整块卡住,延迟感知明显
  • chunk_size=8192:平衡内存占用、系统调用次数和缓存友好性;SSD/HDD 写入也更顺
  • 如果目标是边下边解压(如 tar.gz),建议保持 chunk_size 为 8192,并用 zlib.decompressobj() 流式解压,不要攒满再解

用 shutil.copyfileobj() 替代手动 write() 更可靠

很多人写流式下载,习惯这么干:

for chunk in response.iter_content(8192):
    f.write(chunk)

看起来没问题,但漏掉了两个现实问题:磁盘满、权限拒绝。这些错误在 f.write() 时才抛,而此时 chunk 已经从 socket 读出、丢在内存里了——你没法优雅回退。

shutil.copyfileobj() 内部做了缓冲区复用,且对 IOError 更敏感,更重要的是:它支持传入 length 参数控制总拷贝上限,能防磁盘爆满。

  • 正确写法:shutil.copyfileobj(response.raw, f, length=8192)
  • 必须用 response.raw(不是 response),否则压缩中间层可能干扰
  • 确保 f 是以 wb 模式打开的,且没开 buffering(buffering=0 在二进制模式下无效,别试)
  • 如果要校验 hash,别在写入时算——先写临时文件,写完再 hashlib.blake2b(f.read()),避免 IO 和 CPU 绑死

超时和重试必须分开配,别只设 timeout=(3, 30)

timeout=(3, 30) 只控制单次请求的 connect + read 超时,对流式下载几乎没用:read 超时是从第一个字节开始计,但大文件传输中,可能前 10 秒有数据,后面卡住 2 分钟——这时 timeout 不会中断。

真正需要的是「空闲超时」+ 「重试策略」:

  • requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=10, max_retries=3) 配连接池和重试
  • 手动监控读取间隔:记录上一次 iter_content() 返回非空 chunk 的时间,超过 60 秒无新数据,就主动 response.close() 并重试
  • 别依赖 requests.packages.urllib3.util.retry.Retrystatus_forcelist 来重试 503——流式响应一旦发了 header,503 就不会来了,得靠空闲检测

流式下载的麻烦不在代码长短,而在边界是否被想全:网络断了怎么续、磁盘满了怎么停、服务端悄悄关连接你怎么感知。这些没写进文档,但每次上线都会撞上。

到这里,我们也就讲完了《Python流式下载内存优化技巧》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>