3. 读取CSV文件:csv.reader逐行读取与分隔符、引号处理

CSV文件读取,说白了就是跟文本格式打交道。Python自带的csv模块,我用了快十年,一直觉得它设计得挺巧妙——既简单,又留足了扩展空间。

咱们先看最基础的操作:用csv.reader逐行读取一个标准CSV文件。你想想看,一个CSV文件本质上就是纯文本,每行是一条记录,每列用逗号隔开。但实际项目里,哪有这么规规矩矩的数据?

3.1 基础读取:csv.reader入门

先来个最简单的例子。假设我们有一个ingredients.csv文件,内容是这样的:

name,quantity,unit,expiry_date
面粉,500,g,2025-06-30
糖,200,g,2025-08-15
鸡蛋,6,个,2025-05-20
黄油,250,g,2025-07-10

用csv.reader读取它,代码非常直接:

import csv

with open('ingredients.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

输出结果:

['name', 'quantity', 'unit', 'expiry_date']
['面粉', '500', 'g', '2025-06-30']
['糖', '200', 'g', '2025-08-15']
['鸡蛋', '6', '个', '2025-05-20']
['黄油', '250', 'g', '2025-07-10']

嗯,这里要注意:csv.reader返回的是一个迭代器,每次迭代返回一个列表。每一行都被拆成了字段列表,所有字段默认都是字符串类型。数字500、200并不会自动转成整数——这个后面做数据校验时会处理。

我的习惯:读取CSV时,我通常会把第一行单独拿出来当表头,后面的数据行再逐条处理。用next(reader)就能拿到表头行,非常方便。

3.2 处理不同分隔符:delimiter参数

实际工作中,CSV文件的分隔符可不一定都是逗号。我在项目中遇到过好几次,客户发来的数据文件用的是制表符(TSV格式),或者分号、竖线。这时候delimiter参数就派上用场了。

分隔符类型 常见场景 delimiter参数值
逗号 , 标准CSV文件 delimiter=','
制表符 \t TSV文件、Excel导出 delimiter='\t'
分号 ; 欧洲地区CSV(Excel默认) delimiter=';'
竖线 | 日志文件、特殊格式 delimiter='|'
空格 简单文本数据 delimiter=' '

举个例子,如果文件是用制表符分隔的:

import csv

with open('ingredients.tsv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f, delimiter='\t')
    for row in reader:
        print(row)
我曾经踩过的坑:有一次处理欧洲客户的数据,Excel导出的CSV默认用分号做分隔符,而我直接用逗号去读,结果整行数据被当成一个字段。排查了半天才发现是分隔符的问题。从那以后,我拿到文件第一件事就是先看一眼原始数据,确认分隔符。

3.3 引号处理:quoting参数

CSV文件里,如果字段本身包含逗号、换行符或者引号,就需要用引号把整个字段包起来。比如:

name,description,quantity
"面粉,高筋","适合做面包,口感筋道",500
糖,"用于调味",200

这里csv.reader默认就能正确处理引号——它知道引号内的逗号不是分隔符。但有些特殊情况,需要手动控制引号处理行为。

quoting参数有4种模式:

常量 值 含义
csv.QUOTE_ALL 1 所有字段都加引号
csv.QUOTE_MINIMAL 0 只在必要时加引号(默认)
csv.QUOTE_NONNUMERIC 2 非数字字段加引号
csv.QUOTE_NONE 3 不加引号,遇到引号当作普通字符

我个人最常用的是QUOTE_MINIMAL,这是默认行为,基本够用。但有一种情况要特别注意:

import csv

# 假设文件里字段包含引号本身
# 比如:他说"你好",500
with open('data.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f, quoting=csv.QUOTE_NONE, escapechar='\\')
    for row in reader:
        print(row)

当设置QUOTE_NONE时,如果字段里还有引号,必须配合escapechar使用,否则会报错。这个组合我在处理一些不规范的老系统导出数据时用过几次。

核心要点:引号处理的关键在于理解「引号是CSV的语法结构,不是数据内容」。默认情况下,csv.reader会自动剥离包裹字段的引号,你拿到的数据是干净的字段值,不包含外层引号。

3.4 知识结构总览

下面这张图,把CSV读取的核心流程和参数关系梳理清楚了:

CSV读取核心流程 CSV源文件 open() 打开文件 csv.reader() 核心参数配置 delimiter 分隔符设置 逗号/制表符/分号/竖线 quoting 引号处理模式 ALL/MINIMAL/NONNUMERIC/NONE escapechar 转义字符 配合QUOTE_NONE使用 逐行输出字段列表 💡 读取流程 文件 → 打开 → 配置 → 逐行解析

3.5 综合示例:处理真实配方数据

把上面这些知识点串起来,写一个能处理多种格式的读取函数。我在做配方数据迁移时,就写过类似的工具:

import csv

def read_csv_flexible(filepath, delimiter=None, encoding='utf-8'):
    """
    智能读取CSV文件,自动检测分隔符
    """
    # 如果没有指定分隔符,尝试自动检测
    if delimiter is None:
        with open(filepath, 'r', encoding=encoding) as f:
            sample = f.read(1024)
            # 统计各种分隔符出现次数
            comma_count = sample.count(',')
            tab_count = sample.count('\t')
            semicolon_count = sample.count(';')
            
            # 选择出现最多的分隔符
            counts = {
                ',': comma_count,
                '\t': tab_count,
                ';': semicolon_count
            }
            delimiter = max(counts, key=counts.get)
            print(f"自动检测到分隔符: {repr(delimiter)}")
    
    # 读取文件
    with open(filepath, 'r', encoding=encoding) as f:
        reader = csv.reader(f, delimiter=delimiter)
        headers = next(reader)  # 提取表头
        data = []
        for row_num, row in enumerate(reader, start=2):
            # 跳过空行
            if not any(row):
                continue
            data.append(row)
    
    return headers, data

# 使用示例
headers, rows = read_csv_flexible('recipe_data.csv')
print(f"表头: {headers}")
print(f"数据行数: {len(rows)}")
避坑指南:自动检测分隔符虽然方便,但遇到数据里逗号和制表符数量接近时容易误判。我一般会在检测后加一个校验步骤——检查每行的字段数是否一致,如果不一致就提示用户手动指定。

3.6 常见问题与排查思路

读取CSV时,我遇到过最多的问题就这几类:

  • 编码问题:文件是GBK编码,用UTF-8读就乱码。解决办法是先用chardet库检测编码,或者直接指定encoding='gbk'。
  • 空行问题:文件末尾或中间有空行,csv.reader会返回空列表。加个if not row: continue就能跳过。
  • 字段数量不一致:某一行少了一列,读取时不会报错,但后续处理会出问题。我习惯在读完后检查每行的字段数是否等于表头数。
  • 引号不匹配:某个字段的引号没闭合,会导致csv.reader把后续多行当成一个字段。这种情况比较棘手,通常需要手动修复源文件。
我曾经踩过的坑:有一次处理一个10万行的配方数据,读取到第8万行时突然报错,原因是某一行里有一个未闭合的引号。排查了整整两个小时,最后用二分法定位到问题行。从那以后,我写了个小工具,先做一遍「预检」——逐行检查引号是否成对、字段数是否一致,没问题再正式读取。

嗯,CSV读取这块,说白了就是「打开文件、配置参数、逐行解析」三步走。参数配置是核心——delimiter决定怎么切分字段,quoting决定怎么处理引号。这两个参数搞明白了,市面上99%的CSV文件你都能搞定。