不能靠'a'模式自动判断是否写表头,需手动检查文件是否存在及是否已有表头;推荐用'a+'模式配合seek读取首行验证,再决定是否写header,同时显式指定encoding='utf-8-sig'和newline=''。

用 open() + a 模式追加 CSV 行,但不重复写表头
直接结论:不能靠 a 模式自动判断“有没有表头”,它只管在文件末尾追加字节。是否写表头,得你自己判断文件是否为空或已存在——否则每次都会多一行表头。
常见错误现象:csv.writer(f).writerow(header) 放在循环外但没做文件存在性检查,导致第二次运行脚本时,表头被重复写入;或者用 os.path.getsize() 判断空文件,却忽略了文件存在但内容为空(比如只有换行符)的情况。
- 先用
os.path.exists()检查文件是否存在,而不是只看大小 - 如果文件存在,用
open(..., 'r')读第一行,用csv.Sniffer().has_header()或简单比对字段名更稳妥(尤其当表头含空格、引号时) - 更轻量的做法:打开文件时用
'a+'模式,.seek(0)读开头,再.seek(0, 2)回到末尾,避免多次 open/close
csv.writer 追加单行 vs 多行:writerow() 和 writerows() 的行为差异
两者都只是把数据转成 CSV 字符串并写入当前文件指针位置,不会自动换行或补逗号——换行符由你传入的数据决定(比如 writerow(['a', 'b']) 会在末尾加 \n,前提是初始化 csv.writer 时没禁用 lineterminator)。
使用场景:单行追加适合日志类实时写入;批量写入用 writerows() 能减少 I/O 次数,但要注意内存占用。
立即学习“Python免费学习笔记(深入)”;
-
writerow()接收一个可迭代对象(如列表),写一行;传入字符串会被当作字符序列,导致每字一列 -
writerows()接收可迭代的可迭代对象(如列表套列表),内部循环调用writerow() - 默认
lineterminator='\r\n'(Windows)或'\n'(Unix),若跨平台读取异常,显式指定lineterminator='\n'
用 pd.DataFrame.to_csv(..., mode='a', header=False) 看似省事,但有隐藏坑
这个方式确实能跳过表头,但 pandas 在 mode='a' 下不会校验已有 CSV 的列结构。如果 DataFrame 列名顺序、数量与原文件不一致,追加后 CSV 就错位了——它不会报错,也不会对齐字段。
性能影响:每次调用 to_csv 都会重新解析整个文件路径、编码、分隔符等,即使只是追加一行;而原生 csv 模块开销小得多。
- 仅当确定列结构完全一致、且数据量不大时才用
to_csv(mode='a') -
header=False只是跳过写表头,不等于“自动匹配表头”,列顺序必须和原文件严格一致 - 如果原 CSV 用
quoting=csv.QUOTE_MINIMAL,而to_csv默认是QUOTE_BOUNDARY,引号规则可能不兼容
追加写入时的编码与换行一致性问题
CSV 不是二进制格式,但编码和换行符不统一,会导致 Excel 打开乱码、行数错乱、甚至解析失败。尤其在 Windows 写、Linux 读,或用 Notepad++ 和 VS Code 交替编辑时特别明显。
错误现象:Excel 显示“#VALUE!”、某列内容突然跑到下一行、中文变成问号或方块。
- 始终显式指定
encoding='utf-8-sig'(Windows 上 Excel 友好),而不是'utf-8' - 打开文件时用
newline=''(这是csv模块文档明确要求的),否则 Windows 下可能多出空行 - 不要依赖系统默认换行符;
csv.writer的lineterminator设为'\n'即可,底层会按平台适配
csv.writer 追加。细节藏在判断逻辑里,而不是靠模式或参数自动解决。










