motor异步查询结果需用async for逐批获取并同步写入xlsxwriter,不可直接传asynccursor;须提前处理objectid、datetime、嵌套字段等格式,最后务必同步调用workbook.close()。

Motor异步查询结果不能直接喂给XlsxWriter
因为 Motor 返回的是 AsyncCursor,而 XlsxWriter 是纯同步库,不支持 await 或 async for。直接尝试 workbook.write_row() 传入一个 awaitable 会报 TypeError: object AsyncIOMotorCursor can't be used in 'await' expression(如果误加了 await)或更隐蔽的空数据问题(如果漏 await)。必须显式驱动异步迭代并逐批获取文档。
- 正确做法是用
async for doc in cursor:在协程内消费游标 - 不能在
workbook.add_worksheet()外部调用异步逻辑——所有写入必须发生在同一个事件循环上下文中 - 若数据量大,别把全部文档 load 到内存再写;改用分批
fetch_next+write_row流式写入
用run\_sync()包装XlsxWriter写入操作会导致阻塞整个事件循环
有人试图用 loop.run_in_executor() 把 workbook.close() 丢进线程池,但这治标不治本:XlsxWriter 内部有大量文件缓冲、压缩、XML 构建操作,本身不是 CPU 密集型但 I/O 路径深,且 Workbook 实例不能跨线程共享。更糟的是,workbook 创建和 worksheet.write_row() 都必须在同一线程完成,否则触发 RuntimeError: Workbook has been closed 或静默失败。
- 推荐方案:在主协程中创建
Workbook,用async for拉取数据,同步调用write_row()——只要单次写入耗时短(毫秒级),不会显著拖慢事件循环 - 避免在
write_row()中做 JSON 序列化、日期格式转换等重操作;提前在async for循环里处理好字段值 - 若必须导出超大集合(>10万行),考虑用
cursor.batch_size(1000)控制每次 fetch 的文档数,缓解内存压力
日期、ObjectId、嵌套字典写入Excel时格式错乱或报错
XlsxWriter 不识别 ObjectId、datetime 或嵌套结构,默认调用 str() 输出(如 ObjectId('...')),既难读又无法被 Excel 当作日期解析。直接写 doc['created_at'] 可能触发 TypeError: Unsupported type <class></class>(某些版本)。
- 对
ObjectId:统一转成字符串,用str(doc['_id']) - 对
datetime:转为datetime.datetime对象(Motor 默认返回的就是),XlsxWriter 能自动识别并写入 Excel 日期序列值;但需确保时区为 naive 或 UTC,否则可能偏移 - 对嵌套字段(如
doc['user']['name']):提前扁平化,或用json.dumps()转成字符串(注意设置ensure_ascii=False防中文乱码) - 空值(
None)会被写成空白单元格,符合预期;但NaN或Decimal需显式转float或str
导出完成后文件损坏或打不开
最常见原因是没调用 workbook.close(),或在 close() 前就结束了协程(比如忘了 await 主函数)。XlsxWriter 缓冲区未 flush,生成的 .xlsx 文件缺少末尾 ZIP 结束标记,Excel 打开时报“文件已损坏,是否尝试恢复?”。
- 务必在协程末尾同步调用
workbook.close()——它不是异步方法,但必须执行 - 使用
try/finally包裹写入逻辑,确保即使中间异常也能 close - 不要用
with open(...)手动写入字节流覆盖 XlsxWriter 行为;它内部管理 ZIP 结构,直接写文件会破坏格式 - 导出路径含中文或空格时,确保 Python 进程有写权限,且路径字符串是
str类型(非 bytes)
workbook.close() 的调用时机和 batch_size 对内存的实际影响——这两处一松懈,导出任务就会静默失败或卡死。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











