直接用pandas.read_excel处理百万行excel会oom,因其默认全量加载、类型推断、nan填充及xml解析,导致内存膨胀3–5倍;应改用openpyxl的read_only+iter_rows流式读取,并配合django bulk_create分批插入。

为什么直接用pandas.read_excel会OOM
处理百万行Excel时,pandas.read_excel 默认把整张表加载进内存,还额外复制索引、类型推断、填充NaN——单列字符串就可能膨胀3–5倍。哪怕你只读一列,它仍会解析全量XML结构(.xlsx本质是zip+XML)。这不是代码写得不好,是设计如此。
- Excel文件本身未压缩:100万行 × 10列 ≈ 200MB原始数据,pandas常占1.5–2GB内存
-
openpyxl或xlsxwriter的默认模式也是全量DOM加载,不适用于流式读写 - Django ORM批量插入若用
Model.objects.bulk_create()但没控制batch_size,会一次性攒100万条实例对象,触发Python GC压力和数据库连接超时
用openpyxl的read_only + yield逐行读取
关键不是“不用openpyxl”,而是必须开启read_only=True并配合iter_rows(),跳过样式、公式、合并单元格等无关信息,让底层以SAX方式解析XML流。
- 打开工作簿时指定
read_only=True和data_only=True(跳过公式求值) - 用
ws.iter_rows(min_row=2, values_only=True)跳过表头,且返回tuple而非Cell对象 - 每读1000行就调用一次
bulk_create(..., batch_size=1000),避免Django缓存模型实例 - 别用
list(ws.iter_rows(...))——这又全载入内存了
from openpyxl import load_workbook
<p>wb = load_workbook(filename="data.xlsx", read_only=True, data_only=True)
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):</p><h1>转成model字段字典,跳过空行</h1><pre class="brush:python;toolbar:false;">if not any(row): continue
records.append(MyModel(field1=row[0], field2=row[1]))
if len(records) >= 1000:
MyModel.objects.bulk_create(records, batch_size=1000)
records.clear()wb.close() # 必须显式close,否则文件句柄泄漏
导出用django-import-export的ChunkedQuerySetIterator
django-import-export默认导出会把整个QuerySet转成Python list,百万行直接崩。它的ChunkedQuerySetIterator才是解法——它按chunk_size分页查库,每次只取1000条,边查边写,内存恒定在几MB。
- 继承
resources.ModelResource,重写export()方法 - 传入
queryset.iterator(chunk_size=1000)给self.export_resource() - 用
openpyxl.Workbook(write_only=True)创建写入流式工作簿 - 别用
response.write()拼接Excel二进制——改用StreamingHttpResponse+ 生成器
from openpyxl.writer.excel import save_virtual_workbook
from django.http import StreamingHttpResponse
<p>def export_xlsx_view(request):
queryset = MyModel.objects.filter(...)
resource = MyModelResource()
wb = Workbook(write_only=True)
ws = wb.create_sheet()
ws.append(resource.get_export_headers())
for chunk in queryset.iterator(chunk_size=1000):
for obj in chunk:
ws.append(resource.export_resource(obj))
response = StreamingHttpResponse(
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={'Content-Disposition': 'attachment; filename="export.xlsx"'}
)
response.streaming_content = iter([save_virtual_workbook(wb)])
return response</p>
绕开Excel格式:用CSV中转 + nginx X-Accel-Redirect
真正上生产,百万级导出不该走Python生成.xlsx——CPU和内存双高,还容易被gunicorn worker timeout干掉。更稳的做法是:Django只生成CSV(纯文本、无格式、零依赖),再由nginx用X-Accel-Redirect直接吐文件,完全不经过Python进程。
- 用
csv.writer写临时文件到/var/www/export/xxx.csv,路径权限设为nginx可读 - Django视图只做权限校验和生成唯一token,然后返回
X-Accel-Redirect: /export-protected/xxx.csv - nginx配置
location /export-protected/ { internal; alias /var/www/export/; } - 导入同理:前端先上传CSV,后端用
csv.DictReader+bulk_create流式入库
Excel格式只是用户习惯,不是技术必须。真要.xlsx,最后用ssconvert(libreoffice命令行)异步转,或者让用户自己用Excel打开CSV。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











