streaminghttpresponse 通过逐块生成并立即发送数据防止内存溢出,而 httpresponse 会一次性加载全部内容导致内存耗尽;需用迭代器配合手动编码、转义和 bom 控制实现安全 csv 流式导出。

StreamingHttpResponse 为什么能防内存溢出
普通 HttpResponse 会把整个导出内容(比如百万行 CSV)先拼成一个大字符串或字节流,再一次性写入响应体——这直接吃光 Python 进程的内存。而 StreamingHttpResponse 接收一个可迭代对象(如生成器),每 yield 一小块数据(例如一行、一千行),Django 就立刻 flush 到客户端,内存里只留当前 chunk,不累积。
关键点:它不等你“做完全部”,而是边产边送。但这也意味着你不能在生成过程中随意修改 HTTP 头(比如中途加 Content-Length),也不能用 render() 这类预渲染函数。
怎么写一个安全的 CSV 流式生成器
别用 csv.writer 直接写到 StringIO 或 BytesIO——那又回到内存堆积的老路。要让生成器每次 yield 原生 bytes,且确保换行符、编码、BOM 都手动控制。
- 用
yield返回bytes,不是str;开头加 UTF-8 BOM(b'\xef\xbb\xbf')避免 Excel 打开乱码 - 字段含逗号、换行、双引号时,必须按 RFC 4180 转义:用双引号包裹字段,字段内双引号变成两个双引号(
"→"") - 每行结尾用
\r\n(Windows 兼容),不要用\n - 数据库查询必须用
.iterator(chunk_size=2000),否则QuerySet仍会缓存全部结果
示例片段:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
def csv_stream():
yield b'\xef\xbb\xbf' # BOM
yield '姓名,城市,注册时间\r\n'.encode('utf-8')
for obj in MyModel.objects.filter(...).iterator(chunk_size=2000):
row = f'"{obj.name.replace(\'"\', \'""\')}",{obj.city},"{obj.created_at}"\r\n'
yield row.encode('utf-8')
常见报错和绕过方法
django.core.exceptions.SuspiciousOperation: Attempting to set unsafe header —— 这通常是因为你在流式响应中试图动态设置 Content-Disposition 以外的头(比如 Content-Length)。流式响应无法预知总长度,所以 Django 禁止你设它。
- 只允许设
Content-Type、Content-Disposition、Cache-Control等白名单头 - 文件名含中文?用
urlquote编码:f'attachment; filename="{urlquote("用户列表.csv")}"' - 用 Gunicorn/UWSGI 部署时,反向代理(如 Nginx)可能缓冲响应,导致流式失效;需关掉
proxy_buffering off;并设proxy_buffer_size 128k;
比 CSV 更重的场景:Excel 导出怎么办
openpyxl 和 xlsxwriter 都不支持真正的流式写入——它们必须 hold 住整个工作簿结构才能序列化为字节流。硬上会导致内存爆炸。
- 真要导 Excel,优先转成 CSV 流式输出(绝大多数业务场景够用)
- 非 Excel 不可?用
xlsxwriter的Workbook的close()前,把BytesIO指针重置到开头,再分块read(8192)yield;但这只是“伪流式”,内存峰值仍取决于单个 sheet 行数 - 更稳的方案:异步任务(Celery)生成文件存到对象存储(如 S3),返回下载链接——用户等几秒,服务端不卡
流式导出不是万能银弹,它解决的是“实时吐数据”问题,而不是“省计算资源”问题。生成逻辑本身仍要高效,比如避免在循环里做 N+1 查询、不用 list() 强制求值 QuerySet。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










