Windows 上写 CSV 为什么每行多一个空行?文本模式换行翻译的两个坑
一个查了两小时的问题
读一份别人给的 CSV,用 csv.DictReader 读出来,每行后面都多一个空行,有些行还被拆成了两行。排查半天才发现,是写入时换行符被翻译了两次。
这其实是 Windows 上写文件最常踩、又最不容易一眼看出的两个坑:文本模式的换行翻译,以及 UTF-8 的 BOM。下面把每一处都打到字节层面看清楚。
Windows 文本模式的换行翻译
Windows 的行尾是 CRLF(\r\n)。Python 在文本模式写入时会做换行翻译:你代码里写 \n,落盘时变成 \r\n;读的时候又翻回 \n。这个翻译只在文本模式发生,二进制模式不碰。
我建了四个文件,写入同一份内容 'a\nb\nc\n',只看落盘的字节:
- 默认文本模式写 → 字节是
a\r\nb\r\nc\r\n newline=''写 → 字节是a\nb\nc\n- 二进制写 → 字节是
a\nb\nc\n newline='\r\n'写 → 字节是a\r\nb\r\nc\r\n
第一行结论:文本模式下你写的 \n 会变成 \r\n。很多人以为「我用 \n 换行就一定是 LF」,在 Windows 上并不成立。
CSV 的 \r\r\n 双翻译陷阱
csv.writer 自己就会在每行行尾写 \r\n(这是 CSV 规范规定的,不是 Python 自作主张)。问题出在:如果你用默认文本模式打开文件,Python 先把你写的 \n 翻成 \r\n,csv 模块又在这一层外面再写一层 \r\n,叠出来就是 \r\r\n。
读的时候,多出来的那个 \r 被当成一行的结尾,于是每行后面都多一个空行,数据行被凭空拉长,按行号取数全错位。
所以写 CSV 一定要 newline='',让 csv 模块自己管换行、Python 不再二次翻译:
import csv
with open('out.csv', 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['name', 'id'])
BOM 让第一列多出一个字符
另一个常见问题是:第一列的列名前面多了个看不见的字符,导致 row['name'] 一直报 KeyError。
用 Excel「另存为 UTF-8 CSV」会在文件头写 BOM(字节 ef bb bf)。如果你用 utf-8 编码读,BOM 会留在第一列名前面,变成 '\ufeffname';用 utf-8-sig 读,BOM 在解码时自动剥掉,得到干净的 'name'。
实测:建一个用 utf-8-sig 写的文件,内容是 name,id,字节是:
ef bb bf 6e 61 6d 65 2c 69 64
然后两种编码读回来:
- 用
utf-8读,第一行是'\ufeffname,id' - 用
utf-8-sig读,第一行是'name,id'
utf-8-sig 这个编码名已经说明用途:读的时候跳过 BOM,写的时候自动加上 BOM。
网上常见的另一种说法是「导出时主动加 BOM 防乱码」,那是专门给 Excel 双击打开用的。一旦文件要交给 Python 去读,BOM 反而会让第一列列名带 \ufeff,所以读取侧统一用 utf-8-sig 更稳——有就剥掉,没有就当普通 UTF-8。
处理 CSV 时我一般直接上 utf-8-sig:
import csv
with open('data.csv', newline='', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
rows = list(reader)
GBK 那个坑顺带说一下
老系统导出的中文文件可能是 GBK。我测了 GBK 写的中文文件,'中文测试' 4 个字占 8 字节。用 utf-8 去读会报:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0
报错信息里那个 0xd6 就是 GBK 的中文字节特征。处理办法:先用 utf-8-sig 读,失败再退到 gbk,或者从源头统一编码,别让程序去猜。
判断文件有没有 BOM 的快速办法
看头三个字节:
raw = open('data.csv', 'rb').read(3)
has_bom = raw == b'\xef\xbb\xbf'
或者读第一行再 lstrip('\ufeff') 兜一手。但注意这只是补救,正确做法还是选对编码。
一句话
Windows 写 CSV 记得 newline='' 避免 \r\r\n 双翻译空行;遇到第一列多不可见字符,用 utf-8-sig 读剥 BOM;GBK 文件别用 utf-8 硬读。
- 点赞
- 收藏
- 关注作者
评论(0)