正确做法是用bytesio创建缓冲区,传给openpyxl.workbook或pd.excelwriter(engine='openpyxl'),写完后.seek(0),再用send_file(..., as_attachment=true, download_name="report.xlsx", mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")响应。

Flask里用openpyxl生成Excel并响应下载,别用pandas.DataFrame.to_excel()直接返回
直接在路由中调用 df.to_excel() 并返回文件内容,会触发 ValueError: I/O operation on closed file —— 因为 to_excel() 默认用上下文管理器关闭了 BytesIO 对象。正确做法是显式控制文件对象生命周期:用 BytesIO 创建缓冲区,传给 openpyxl.Workbook 或 pd.ExcelWriter(engine设为 openpyxl),写完后 .seek(0) 重置指针,再用 send_file 响应。
- 推荐优先用
openpyxl手动构建:对样式、合并单元格、公式等有精确控制需求时更可靠 - 若只是导出结构化数据,可用
pandas+ExcelWriter,但必须指定engine='openpyxl'且避免使用with自动关闭 -
send_file必须传入as_attachment=True,否则浏览器可能尝试渲染而非下载
下载响应头必须设置Content-Disposition和Content-Type
缺了这两个头,Chrome 可能把 Excel 当作 HTML 渲染,Safari 可能静默失败。Flask 的 send_file 默认不设 Content-Type 为 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,也不强制下载行为。
- 手动构造响应时,用
make_response+Response设置headers,但更简单的是用send_file(..., as_attachment=True, download_name="report.xlsx")(注意不是attachment_filename,那是旧版参数) -
download_name必须带.xlsx后缀,否则部分浏览器(如 Edge)可能忽略类型推断 - 不要依赖
mimetype参数自动推断:send_file对BytesIO推断常为text/plain,必须显式传mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
动态数据写入时,注意openpyxl的行列索引从1开始
这是最常踩的坑:pandas 和 Python 列表习惯是 0-based,但 openpyxl 的 ws.cell(row=1, column=1) 或 ws["A1"] 都是 1-based。混用会导致数据错行、标题漏写或覆盖空白行。
- 写标题行用
for i, col in enumerate(headers, 1): ws.cell(row=1, column=i, value=col) - 写数据行用
for r_idx, row_data in enumerate(data, 2): # 从第2行开始 - 避免用
ws.append()写带样式的行:它总在末尾追加且无法控制字体/边框;需逐单元格赋值 + 设置.font/.border
生产环境要防止内存泄漏:每次请求都新建Workbook,别复用全局实例
有人图省事把 Workbook 实例放在模块顶层,结果并发请求时写入冲突、样式错乱、甚至 RuntimeError: Workbook already saved。Flask 路由函数是每次请求独立执行的,所有资源必须在函数内创建、使用、返回。
- 不要:
WB = Workbook()放在函数外;要:wb = Workbook(); ws = wb.active在路由函数内部 - 大数据量(>10万行)时,
openpyxl内存占用高,考虑改用xlsxwriter(只写不读)或流式生成 CSV(用io.StringIO+csv.writer) - 调试时可在返回前加
wb.save("/tmp/debug.xlsx")本地保存验证,但上线必须删掉,否则多进程下路径冲突
生成逻辑本身不复杂,难的是边界处理:空数据集要不要返回空表、中文列名是否乱码(确保源数据是 str 不是 bytes)、时间字段是否被转成浮点数(openpyxl 存 datetime 对象即可,别转字符串)。这些细节不卡在代码里,而卡在你第一次打开生成的 Excel 文件时看到的那一刻。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











