# 聊聊Python里的CSV模块有过两年Python开发经验的人大概率都用过CSV文件。这东西看起来简单但用起来有不少门道。它到底是个什么东西CSV全称是Comma-Separated Values逗号分隔值。就是把表格数据用纯文本存起来。每一行是一行数据每个字段用逗号隔开。比如姓名,年龄,城市 张三,28,北京 李四,32,上海以前我在一家电商公司干活后端服务之间传数据用的就是CSV。当时觉得这玩意儿low后来才明白csv在很多场景下确实是最合适的选择——简单、通用、任何编辑器都能打开。有一点值得提很多人以为CSV就真的只靠逗号分隔。实际上Python的csv模块考虑了更复杂的情况比如字段里包含逗号该怎么办字段本身有换行符怎么办。标准做法是用引号把字段包起来或者用双引号转义。它能解决什么问题CSV最大的用处是在不同系统之间传递表格数据。举个实际例子之前我们团队需要把数据库里两千多万条用户数据导出给数据部门做分析。用Excel文件太大了打不开。用数据库直连网络安全策略不允许。最后方案就是导出成CSV十几GB的文本文件用gzip压缩一下扔给数据部门。另一个常见场景是数据迁移。从旧系统导出CSV脚本处理一下再导入新系统。这种事我干过不下二十次每次都能发现CSV的一些小坑。还有一个容易被忽视的点CSV可以用来做配置。有些项目把配置写成CSV而不是JSON或YAML方便产品经理或运营人员直接编辑。怎么用起来Python自带csv模块不用额外安装。用法很简单但有几个地方新手容易踩坑。读取CSV文件的基本写法importcsvwithopen(data.csv,r,encodingutf-8)asf:readercsv.reader(f)forrowinreader:print(row)这段代码会把每一行数据变成一个列表。csv.reader返回的是一个迭代器不会一次性把整个文件读进内存这点在处理大文件时很关键。写入稍微有点区别withopen(output.csv,w,newline,encodingutf-8-sig)asf:writercsv.writer(f)writer.writerow([姓名,年龄,城市])writer.writerow([张三,28,北京])注意newline这个参数。不加的话Windows系统下会多出空行因为csv模块自己处理换行和文件系统的换行控制冲突了。还有编码问题。UTF-8可以跨平台但如果要在Excel里打开用utf-8-sig编码更好——带BOM的UTF-8Excel才能正确识别。字典形式的读写也很实用withopen(data.csv,r)asf:readercsv.DictReader(f)forrowinreader:print(row[姓名],row[年龄])withopen(output.csv,w,newline)asf:fieldnames[姓名,年龄,城市]writercsv.DictWriter(f,fieldnamesfieldnames)writer.writeheader()writer.writerow({姓名:张三,年龄:28,城市:北京})一些实际经验处理大文件时用csv.reader而不是手动按行读取加split(,)。这不仅仅是习惯问题csv.reader能正确处理带引号的字段自己写split处理不了张三,李四这样的字段——逗号在引号内不应该被当成分隔符。分隔符问题经常遇到。有些人用tab分隔有些系统用分号。可以指定delimiter参数readercsv.reader(f,delimiter\t)有一种情况坑了我很久——空行中的空字段。CSV规范里一行数据末尾的空字段可以省略也可以留空。不同的CSV生成器的处理方式不一样。对于关键字段读进来后最好检查一下是不是None或空字符串。另一个问题日期格式。不同系统输出的日期格式差别很大2024-01-15和01/15/2024并存的情况很常见。统一转换成标准格式再处理是个好习惯。处理坏数据也很重要。一个文件里可能有十万行是好的就一行有问题。用try...except捕获异常把问题行记录下来然后跳过比程序直接崩溃要实用得多。之前处理一次对账数据就因为一行有奇怪的编码字符程序跑了五个小时突然挂了整个人都不好了。和其他格式的比较CSV、JSON、Excel、Parquet这些格式各有适用场景。CSV vs JSONCSV更节省空间尤其数据量大时。JSON的好处是能表示嵌套结构CSV只能存平面表格。如果你的数据有复杂的层级关系比如用户信息和订单列表用JSON更合适。如果就是一张简简单单的表格CSV更轻量。CSV vs Excel文件主要区别是CSV只有数据不保存格式、公式、多sheet这些信息。有些同事喜欢用Excel保存数据然后发现文件打不开了——Excel的二进制格式容易损坏。CSV是纯文本几乎不可能损坏。但Excel能存多张表、能设置单元格颜色、能写公式这些CSV做不到。如果需要这些功能可以考虑用openpyxl库处理Excel文件。CSV vs ParquetParquet是列式存储格式压缩率高读取速度快尤其适合做数据分析。体积能比CSV小好几倍读取部分数据时不需要读整个文件。但Parquet的兼容性不如CSV很多工具不支持直接打开。日常开发中小规模数据直接用CSV大数据量分析场景才考虑Parquet。CSV vs SQLite这个对比有点意思。SQLite是一个完整的数据库引擎支持SQL查# # Python SQLite3一个被低估的数据伴侣他是什么SQLite本身是一个很特别的存在。不像MySQL或者PostgreSQL那样需要单独安装一个服务端进程SQLite就是一个库直接嵌入到你的程序里。Python标准库里带的sqlite3模块就是用Python封装了这个库。你不需要额外装任何东西只要Python环境没问题import sqlite3就能直接用。有意思的是虽然叫lite但SQLite其实很重。它支持ACID事务、支持大部分SQL标准、支持BLOB、支持全文索引。很多公司拿它做移动端数据库、嵌入式设备的数据存储甚至在某些场景下替代MySQL。只是它适合的是单机、低并发、小数据量的场景。很多人把sqlite3当成临时工具用完就扔。但它的设计哲学很有意思——把所有数据放在一个文件里访问数据时直接读写这个文件不通过网络没有额外的服务进程。这就意味着它的延迟非常低启动速度极快。他能做什么最常见的用法就是当本地配置文件。JSON和YAML做配置文件的问题是当配置项一多查找和修改就变得很麻烦。用数据库的好处是可以按条件查询。比如一个爬虫程序需要记录每个URL的状态、上次抓取时间、抓取结果。如果用JSON存更新一个URL的状态就得先读出整个文件改完再写回去爬虫跑起来就是一个很大的IO负担。用SQLite就简单多了UPDATE一条记录就行了。另一个典型场景是数据分析和日志。很多Python数据分析脚本第一步往往是把日志文件解析成CSV。但如果日志量大CSV文件查找就很痛苦。可以先把日志解析进SQLite然后直接用SQL做查询。比如找出昨天的所有异常请求、“按照接口维度统计访问次数”用SQL比用Pandas快得多——当然数据量不大时。还有一个常见的需求是测试。写单元测试的时候总是需要mock数据库。但SQLite支持内存模式直接创建一个:memory:的连接测试代码就能像操作真实数据库一样操作它。测试跑完数据自动销毁。怎么使用最基本的用法分四步走创建连接、获取游标、执行SQL、提交事务。importsqlite3 connsqlite3.connect(data.db)cursorconn.cursor()cursor.execute( CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) )cursor.execute(INSERT INTO articles (title, content) VALUES (?, ?),(Python SQLite3入门,这是一篇关于SQLite3的博客...))conn.commit()conn.close()这里面有一个关键细节参数化查询。很多人学着学着就偷懒用f-string拼接SQL比如cursor.execute(fSELECT * FROM articles WHERE title{title})。这在生产环境绝对不能用。原因有两个第一是SQL注入风险第二是字符串拼接容易出错。用?做占位符传入元组作为参数字符串的转义和类型处理都由底层库搞定。另一个经常被忽略的点是事务处理。SQLite默认是自动提交模式也就是说每条DML语句执行完就自动提交。但实际开发中经常需要批量操作。比如要插入一万条数据如果一条一条自动提交速度会很慢。可以显式开启一个事务执行完所有操作再提交。connsqlite3.connect(data.db)cursorconn.cursor()cursor.execute(BEGIN)try:foriteminitems:cursor.execute(INSERT INTO articles (title, content) VALUES (?, ?),(item[title],item[content]))conn.commit()except:conn.rollback()这样写的好处是要么全部成功要么全部回滚。而且因为只提交一次写入速度会快很多。最佳实践关于连接管理见过不少代码在每次操作时都新建连接用完了就关。SQLite的创建连接开销很小但也不是零。比较好的做法是每个线程或者整个进程只创建一个连接反复使用。如果程序是单线程的一个连接的用法完全够用。多线程环境下SQLite默认有一个连接锁多个线程同时写会被阻塞。这时候可以用连接池或者直接使用check_same_threadFalse参数但要注意保证同一时间只有一个线程在写。关于性能优化有一个容易踩的坑是写太多索引。索引能加快查询但会让写入变慢而且是每次写入都要更新所有索引。所以只给需要的列建索引就够了不要给每一列都建。还有一个提升写入速度的技巧关闭同步模式。SQLite在写入时会等待数据真正写进磁盘才返回这保证了数据安全性但也让写入变慢。如果数据安全不是首要考虑——比如缓存数据、临时数据——可以设置PRAGMA synchronous OFF写入速度能快一个数量级。关于查询优化有个很实用的小工具EXPLAIN QUERY PLAN。执行cursor.execute(EXPLAIN QUERY PLAN SELECT ...)就能看到查询计划知道查询走了哪些索引有没有做全表扫描。这对分析慢查询很有帮助。和同类技术对比跟CSV对比SQLite多了一点重。CSV文件可以用任何编辑器打开看读得懂。SQLite文件二进制格式得用工具才能查看。但CSV没有索引没有类型检查没有事务。数据量一超过几十万行CSV查询起来就很痛苦了。日常小项目用CSV没问题但数据量一大或者查询条件复杂一点SQLite的优越性就体现出来了。跟MySQL对比SQLite弱在并发。MySQL有独立的服务进程可以同时服务多客户端。SQLite只能一个进程写其他进程只能读。并发量一上去SQLite就扛不住了。但部署简单不需要单独维护一个数据库服务不需要做主从备份。小团队开发、个人项目、移动应用用SQLite就很合适。跟JSON对比JSON的优点是灵活结构可以随时变。而SQLite需要事先定义好表结构。如果数据结构经常变用JSON也许更方便。但JSON的查询能力很差要按条件过滤就得遍历整个文件。数据量一大遍历的开销就很明显。而且JSON没有事务机制写入过程中断电会导致数据损坏。最后提一下DuckDB。这是近几年新出的嵌入式数据库也是单文件也支持SQL查询但面向的是分析型场景。SQLite在OLTP在线事务处理方面表现更好适合频繁的小体量读写。DuckDB在OLAP在线分析处理方面更擅长适合大数据量的聚合查询。各自有适用的场景选型时最好根据读写模式和查询复杂度来决定。询、索引、事务。CSV只是一个简单的数据格式。但如果数据量不大又需要做查询用SQLite比CSV更方便。反过来如果只是简单的数据交换SQLite又显得重了。看场景说话。实际项目里我也见过混用的方案。先用CSV把数据快速导出来用Python脚本清洗转换最后导入数据库中。这样既利用了CSV的简单通用又借助Python的能力做复杂处理。一个工具解决不了所有问题组合才是常态。