Ruby 解析 CSV 文件完全指南:从导入到 ETL 管道
本文全面讲解 Ruby 标准库 CSV 的用法:带表头读取、类型转换、Enumerable 链式处理、大文件流式解析与 CSV 生成,覆盖数据导入和 ETL 场景的实战技巧。
Ruby 标准库自带完整的 CSV 处理类:小文件用 CSV.read + headers: true 拿到行对象数组,大文件用 CSV.foreach 逐行流式处理,配合 converters 自动转型和 Enumerable 方法链即可构建高效的数据导入管道,全程无需第三方 gem。
CSV 库的设计哲学
Ruby 的 CSV 库不是简单的"按逗号 split"——它完整实现了 RFC 4180:带引号的字段、字段内换行、转义引号这些坑都处理好了。永远不要手写 line.split(',') 解析 CSV,那是 bug 发生器。
基础读取
require 'csv'
# 数组模式:每行是数组
CSV.foreach('users.csv') { |row| p row }
# 表头模式:每行是 CSV::Row,按列名取值
CSV.foreach('users.csv', headers: true) do |row|
puts "#{row['name']} - #{row['email']}"
end
# 一次性全读(仅小文件)
table = CSV.read('users.csv', headers: true)
headers: true 把首行当列名,后续每行变成可按名字索引的 Row 对象,配合 header_converters: :symbol 还能用 row[:name] 符号取值。
类型转换:让数据自己变干净
CSV 里一切都是字符串,手动转型啰嗦且易错。converters 声明式解决:
CSV.foreach('orders.csv',
headers: true,
converters: [:numeric, :date]) do |row|
row['amount'] # => 1299.0(Float)
row['paid_at'] # => #<Date: 2026-10-08>
end
内置转换器有 :integer、:float、:numeric、:date、:date_time,也可以传 lambda 自定义——比如把 "是/否" 转成布尔值。
Enumerable 链式处理
CSV.read 返回的 Table 支持 Enumerable,转换过滤一气呵成:
table = CSV.read('sales.csv', headers: true, converters: :numeric)
total = table.select { |r| r['region'] == '华东' }
.sum { |r| r['amount'] }
ETL 场景的经典管道:读入 → 过滤脏行 → 字段映射 → 批量入库。记得在管道入口做表头校验,源文件多一列少一列时及早报错。
大文件流式处理
百万行级文件千万别 CSV.read——内存直接爆炸。CSV.foreach 逐行流式处理,内存恒定:
batch = []
CSV.foreach('huge.csv', headers: true) do |row|
batch << transform(row)
if batch.size >= 1000
User.insert_all(batch) # 批量入库
batch = []
end
end
User.insert_all(batch) if batch.any?
分批 + 批量插入(insert_all)是导入性能的两个数量级开关:单行 insert 导入百万行要几小时,分批 bulk insert 只要几分钟。
生成 CSV
CSV.generate do |csv|
csv << ['姓名', '邮箱']
users.each { |u| csv << [u.name, u.email] }
end
# 或直接写文件
CSV.open('export.csv', 'w', write_headers: true,
headers: %w[name email]) do |csv|
users.each { |u| csv << [u.name, u.email] }
end
导出给 Excel 用户时加 write_headers 和 BOM("\uFEFF" 开头),中文列名才不会在 Excel 里乱码。
常见问题(FAQ)
Q:解析报 MalformedCSVError 怎么办?
通常是字段内引号未转义或编码不对。先用文本编辑器确认文件编码(转 UTF-8),再检查生成方是否正确转义;实在不行可用 liberal_parsing: true 容错模式,但要在结果里校验字段数。
Q:GBK 编码的老文件怎么读?
指定外部编码:CSV.foreach('old.csv', encoding: 'GBK:UTF-8', headers: true),读取时自动转码成 UTF-8。
Q:分列符不是逗号(如制表符、分号)怎么办?
用 col_sep 选项:CSV.foreach('data.tsv', col_sep: "\t");欧洲格式的分号 CSV 用 col_sep: ';'。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。