news 2026/9/6 13:09:01

Python 读写大 CSV 文件实战:csv 模块的引号转义、编码 BOM 与流式处理不爆内存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 读写大 CSV 文件实战:csv 模块的引号转义、编码 BOM 与流式处理不爆内存

Python 读写大 CSV 文件实战:csv 模块的引号转义、编码 BOM 与流式处理不爆内存

处理 CSV 看起来是 Python 里最没技术含量的活:open一下按逗号split不就完了?直到你的 CSV 里某个字段本身带逗号(比如地址「北京市, 朝阳区」),或者带换行、带引号,split(',')立刻把一行切成好几段,数据全乱。再或者用 Excel 打开你生成的 CSV,中文全是乱码;又或者读一个 2GB 的文件,一个read()直接把内存吃爆。

这些坑标准库csv模块基本都替你处理了,但你得知道怎么用对。这篇把 CSV 读写里最高频的三类问题——引号转义、编码/BOM、大文件流式处理——一次讲透。

别用 split(‘,’) 手撕 CSV

先看为什么手写解析必翻车。假设有这么一行数据,地址字段里带了逗号:

1,张三,"北京市, 朝阳区",28

按 CSV 规范,带逗号的字段要用双引号包起来。你要是line.split(','):

line='1,张三,"北京市, 朝阳区",28'print(line.split(','))# ['1', '张三', '"北京市', ' 朝阳区"', '28'] ← 5 段!地址被切成两半,引号还留着

彻底错了。而csv模块知道引号规则:

importcsv,io line='1,张三,"北京市, 朝阳区",28'reader=csv.reader(io.StringIO(line))print(next(reader))# ['1', '张三', '北京市, 朝阳区', '28'] ← 正确,4 段,引号也去掉了

CSV 的转义规则比想象中复杂:字段里有逗号、换行、双引号时要用双引号包裹,字段内的双引号要写成两个双引号""。这些规则手写迟早出错,csv模块全处理好了。结论:永远别用字符串切割解析 CSV,用csv模块。

读:用 DictReader 按列名取,别数下标

基础读法是csv.reader,返回每行的列表,靠下标取值:

importcsvwithopen('users.csv',newline='',encoding='utf-8')asf:reader=csv.reader(f)header=next(reader)# 第一行是表头forrowinreader:print(row[1],row[2])# 靠下标 1、2 取,可读性差、易错

下标法有个大问题:列一多、或者上游调整了列顺序,row[2]到底是啥全靠数,极易错位。用DictReader按列名取:

withopen('users.csv',newline='',encoding='utf-8')asf:reader=csv.DictReader(f)# 自动把第一行当表头forrowinreader:print(row['name'],row['city'])# 按列名取,清晰且抗列顺序变化

注意那个newline=''——这是最容易漏又最坑的参数。打开 CSV 文件给 csv 模块用时,必须传newline=''。因为 csv 模块要自己处理行内的\r\n(字段里可能包含换行),如果不传,Python 的通用换行转换会和 csv 的处理打架,在 Windows 上会每行之间多出一个空行,或者字段内换行被�is错误处理。记死:csv 读写,open一律带newline=''

写:字段带逗号/换行,交给 writer 自动加引号

写 CSV 同理,别自己拼字符串。csv.writer会自动判断哪些字段需要加引号:

importcsv rows=[['id','name','address'],[1,'张三','北京市, 朝阳区'],# 带逗号[2,'李四','含"引号"的名字'],# 带引号[3,'王五','第一行\n第二行'],# 带换行]withopen('out.csv','w',newline='',encoding='utf-8')asf:writer=csv.writer(f)writer.writerows(rows)

生成的文件里,带特殊字符的字段被自动正确转义:

id,name,address 1,张三,"北京市, 朝阳区" 2,李四,"含""引号""的名字" 3,王五,"第一行 第二行"

注意第 2 行的引号被转义成了"",第 3 行的字段因为含换行被整体用引号包住——这些都是csv.writer自动做的,手写绝对会漏。

写字典数据用DictWriter,还能顺手用writeheader()写表头:

withopen('out.csv','w',newline='',encoding='utf-8')asf:writer=csv.DictWriter(f,fieldnames=['id','name','address'])writer.writeheader()writer.writerow({'id':1,'name':'张三','address':'北京市, 朝阳区'})

DictWriter有个好处:如果 dict 里的 key 和fieldnames对不上(多了个 key),会直接抛ValueError,帮你抓住数据里的脏字段,而不是静默漏写。

编码坑:Excel 打开中文乱码,加 BOM

你用utf-8写的 CSV,自己 Python 读没问题,但同事用Excel(尤其是 Windows 版)双击打开,中文全是乱码。这是因为 Excel 在没有明确编码标记时,默认按系统本地编码(简中环境是 GBK)去猜,把 UTF-8 的中文认成了乱码。

解法是用utf-8-sig编码写——它会在文件开头写一个 BOM(字节顺序标记),Excel 看到 BOM 就知道「这是 UTF-8」,正确显示:

# 给 Excel 用的 CSV,用 utf-8-sig 加 BOMwithopen('for_excel.csv','w',newline='',encoding='utf-8-sig')asf:writer=csv.writer(f)writer.writerow(['姓名','城市'])writer.writerow(['张三','北京'])

反过来,读一个带 BOM 的文件时,也用utf-8-sig,它会自动吃掉 BOM。如果你用普通utf-8读带 BOM 的文件,第一个字段名会莫名多个前缀:

# 用普通 utf-8 读带 BOM 的文件:表头第一个 key 会带 withopen('for_excel.csv',encoding='utf-8')asf:reader=csv.DictReader(f)print(reader.fieldnames)# ['姓名', '城市'] ← 第一个 key 脏了!# 正确:用 utf-8-sig 读,自动去 BOMwithopen('for_excel.csv',encoding='utf-8-sig')asf:reader=csv.DictReader(f)print(reader.fieldnames)# ['姓名', '城市']

这个前缀极其隐蔽:row['姓名']会 KeyError,但打印出来看着一模一样,排查半天。规则:和 Excel 打交道就用utf-8-sig,读写都用它最省心。

大文件:逐行流式处理,别一次读进内存

处理几百 MB 甚至几 GB 的 CSV,新手常见错误是f.read()list(reader)把整个文件读进内存,直接 OOM。

好消息是csv.reader本身就是惰性迭代器——你 for 循环它时,它一次只读一行,内存占用和文件大小无关:

importcsv# 正确:逐行迭代,内存恒定,几 GB 也不怕defcount_by_city(path):fromcollectionsimportCounter counter=Counter()withopen(path,newline='',encoding='utf-8')asf:reader=csv.DictReader(f)forrowinreader:# 一次一行,不会把整个文件读进内存counter[row['city']]+=1returncounter

反例(别这么写):

withopen('huge.csv',newline='',encoding='utf-8')asf:rows=list(csv.DictReader(f))# ← 整个文件塞进内存,大文件直接 OOMforrowinrows:...

只要你不主动list()它、不 append 到一个大列表里,for 循环逐行处理就是流式的。想边读边写(比如清洗大文件),同时开读和写两个流,处理一行写一行:

defclean_csv(src,dst):withopen(src,newline='',encoding='utf-8')asfin,\open(dst,'w',newline='',encoding='utf-8-sig')asfout:reader=csv.DictReader(fin)writer=csv.DictWriter(fout,fieldnames=reader.fieldnames)writer.writeheader()forrowinreader:row['name']=row['name'].strip()# 逐行清洗ifrow['city']:# 过滤掉没城市的writer.writerow(row)# 全程内存只驻留一行,处理 10GB 文件也稳

一个隐藏坑:超大字段报 field larger than field limit

如果某个字段特别大(比如一整段 JSON、base64 内容塞进一个单元格),读的时候可能报:

_csv.Error: field larger than field limit (131072)

csv 模块默认单字段上限约 128KB。确实有超大字段就调大限制:

importcsv,sys csv.field_size_limit(sys.maxsize)# 放开单字段大小限制

但先想清楚:字段真有必要这么大吗?通常这种超大字段是数据设计有问题(该拆表或存文件路径),放开限制是权宜之计。

小结

  • 永远用csv模块,别split(','):带逗号、换行、引号的字段,手写切割必翻车,csv按规范自动处理转义。
  • 读用DictReader按列名取(抗列顺序变化),写用DictWriter+writeheader();open一律带newline='',否则 Windows 上多空行、字段内换行出错。
  • 编码:和 Excel 打交道用utf-8-sig(写加 BOM 防乱码,读自动去 BOM);用普通utf-8读带 BOM 文件,第一个列名会藏个导致 KeyError。
  • 大文件:csv.reader/DictReader本身是惰性迭代器,for 循环逐行处理内存恒定;别list(reader)f.read()一次读进内存;边读边写同时开两个流。
  • 超大字段报field larger than field limit时用csv.field_size_limit(sys.maxsize)放开,但先反思字段该不该这么大。

一句话记忆:CSV 三件套记牢——解析交给 csv 模块、opennewline=''、跟 Excel 打交道用utf-8-sig;大文件靠迭代器天然流式,别手贱list()它。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 13:07:13

自托管自动化平台Dagychu详解:Docker Compose部署与任务管理实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 13:03:34

互补协同的防腐哲学:环氧底漆与氟碳面漆的“黄金搭档“

重防腐涂装领域有一个被反复验证的设计原则:让每一层涂层做自己最擅长的事。环氧底漆与氟碳面漆的组合,正是这条原则的终极体现。环氧树脂天生怕晒,但附着力与屏蔽性无可替代;氟碳树脂耐候性冠绝群伦,但直接与钢铁基材…

作者头像 李华
网站建设 2026/9/6 13:02:29

国产加密软件项目案例复盘:研发企业文档泄密隐患整改全过程

0.背景:企业现状、原有痛点、项目目标本次案例主体为一家装备制造研发企业,内网终端约 120 台,包含研发设计、工艺、财务、行政岗位,大量 CAD 图纸、工艺文档、BOM 清单存储在办公电脑与文件服务器。企业原有安全手段仅依靠简单制…

作者头像 李华
网站建设 2026/9/6 13:02:27

最美丁香结

丁香结每一个人的一生中都可能会遇到各种结,但遇到丁香结可能则是另一种幸运,我们要直面人生中的各种结,才能在学习和生活中走得更远。结,如同那系铃般,需要有人系,也需要有人解;也如同那乱了线…

作者头像 李华
网站建设 2026/9/6 13:01:48

CentOS-MinIO解决ext4硬盘inode占满问题(xfs动态扩容inode空间占比)

问题描述因小图片较多,导致Inode占用100%(挂载存储格式为ext4),磁盘19T空间虽然还有82%但是无法写入数据,导致minio各节点无法同步,最终导致节点无法启动查看minio状态,提示:no space left on device解决方…

作者头像 李华
网站建设 2026/9/6 12:59:35

IAR原生跨平台IDE发布:Linux嵌入式开发告别命令行折腾

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华