直接to_csv()会内存爆炸,因其默认将整个DataFrame加载进内存完成格式化后再写入;应改用csv.writer配合itertuples()逐行流式写入,并手动处理NaN、时间、分类等特殊类型。

为什么直接 to_csv() 会内存爆炸
当你调用 df.to_csv("large.csv") 写入上千万行数据时,Pandas 默认会先把整个 DataFrame 加载进内存、完成所有格式化(如日期转换、空值处理、引号包裹),再一次性刷到磁盘。即使原始 CSV 只有 1GB,中间可能占用 3–4GB 内存——尤其在 object 列多、字符串长、或启用了 index=True 时更明显。
这不是 bug,是设计使然:Pandas 的 to_csv() 面向“完整数据集导出”,不是流式管道。
用 chunksize + iterrows() 不是真流式
很多人误以为分块读取再逐块写入就是流式,但以下写法仍不推荐:
for chunk in pd.read_csv("input.csv", chunksize=50000):
chunk.to_csv("output.csv", mode="a", header=False)问题在于:chunk.to_csv() 每次仍会把整块数据在内存中格式化成字符串再写入,且反复打开/追加文件会造成大量小 IO,性能差;更严重的是,如果某列含换行符或逗号,to_csv() 的默认引号逻辑可能在 chunk 边界处被破坏(比如一个跨行的字符串被切开)。
立即学习“Python免费学习笔记(深入)”;
真正需要的是:从源头按行生成、逐行编码、单次打开文件、无中间 DataFrame 构造。
用 csv.writer 手动控制写入流程
绕过 Pandas 的序列化层,用 Python 标准库 csv.writer 直接写原生 Python 数据(list/tuple/dict),才能实现低内存、可控、稳定的流式输出。关键点:
- 先用
df.columns.tolist()获取字段名,手动写 header - 用
df.itertuples(index=False, name=None)迭代每行——比iterrows()快 5–10 倍,且返回 tuple,无 dtype 转换开销 - 确保
open()使用newline=""(Windows 下避免双换行) - 对特殊值(
None,np.nan,pd.NaT)提前统一转为""或其他占位符
示例核心片段:
import csv
import pandas as pd
import numpy as np
<p>with open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(df.columns.tolist()) # 写 header
for row in df.itertuples(index=False, name=None):</p><h1>将 nan/None/NaT 转为空字符串(按需调整)</h1><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;"> clean_row = [
"" if pd.isna(x) else x for x in row
]
writer.writerow(clean_row)</code>处理时间、分类、嵌套等复杂列类型
直接用 itertuples() 输出 <code>datetime64 或 category 列会得到 numpy.datetime64 或 pd.Categorical 对象,csv.writer 无法直接序列化,会报 TypeError。
必须显式转换:
-
datetime64→.strftime("%Y-%m-%d %H:%M:%S")或.isoformat() -
category→ 先用df[col].astype(str)预处理整列,或在循环内用str(x) -
list/dict类型(如有)→ 用json.dumps(x)序列化,注意引号和转义
不要依赖 str(row) 或 repr(),它们输出不可控、不兼容 CSV 规范。
真正难的不是写几百行代码,而是保证每一列的数据类型在写入前已明确、可序列化、且与 header 对齐——漏掉一列的 pd.Timestamp 转换,就卡在第 200 万行报错。


















