结论:excel打开django导出csv中文乱码是因未在响应体开头写入utf-8 bom头。必须在httpresponse创建后立即write(codecs.bom_utf8),再设content-disposition,最后用csv.writer写数据;streaminghttpresponse则需首个yield返回bom。

Excel打开Django导出的CSV中文乱码,根本不是编码写错了
直接说结论:response.write(codecs.BOM_UTF8) 没加,或者加得不对位置。Excel不是不支持UTF-8,而是它压根不看文件内容,只靠BOM头判断编码。没BOM,就默认用系统ANSI(比如GBK)去硬解UTF-8字节流——3字节中文被拆成两段,自然变成“涓浗”“婀栧寳鐪?”这类乱码。
这不是Django的问题,也不是Python写错了编码,是Excel解析逻辑本身的限制。你用记事本、VS Code、Sublime打开都正常,唯独Excel异常,基本就能锁定是BOM缺失。
Django HttpResponse导出CSV必须手动写BOM,且只能写一次、必须在开头
常见错误是把BOM塞进csv.writer里,或者写在Content-Disposition之后——这完全无效。BOM必须是HTTP响应体的第一个字节。
-
response = HttpResponse(content_type='text/csv')创建后立刻调用response.write(codecs.BOM_UTF8) - 再设置
response['Content-Disposition'] - 最后才用
csv.writer(response)写数据行 - 如果用了
StreamingHttpResponse,BOM必须作为第一个yield块发出,后续块不能重复写
别用 open(..., 'w', encoding='utf-8') 先写磁盘再读取返回
这种写法看似简单,实则埋雷:
- 多线程/并发下文件句柄可能冲突或覆盖
- 大文件会吃光内存(一次性读入再返回)
- 绕过Django的响应流机制,BOM容易漏加或位置错乱
- 临时文件权限、清理、路径安全都得自己兜底
正确做法是全程走内存流:io.StringIO 配合 codecs.BOM_UTF8,或更推荐直接往 HttpResponse 写字节流。
QuerySet导出别用 model_to_dict(),优先选 values_list()
乱码只是表象,背后常混着更隐蔽的问题:
-
model_to_dict()会触发完整Model实例化,外键字段变对象、日期变datetime、空值变None、property字段可能抛异常 - 字段顺序不可控,导出列和预期对不上
- 性能差,尤其大表:10万行可能多查10万次数据库
- 应该用
qs.values_list('id', 'name', 'created_at'),字段名显式声明,返回元组,无额外开销
如果字段含外键ID,直接写 'user_id';含JSONField,确保已序列化为字符串;含自定义格式(如状态中文名),提前在QuerySet里用 annotate() 处理好。
BOM位置、QuerySet字段控制、流式响应三者必须同时到位,缺一不可。最容易忽略的是:用StreamingHttpResponse时,BOM写在第一个yield里,但有人误写在生成器函数外面,结果BOM根本没发出去。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











