Python读取CSV基础:使用csv模块的reader()、逐行读取与类型转换、处理表头与数据分离

说实话,很多人一上来就用Pandas处理CSV。但我觉得,先搞懂Python自带的csv模块,才是真正的「基本功」。你想想看,如果连最底层的读取逻辑都不清楚,出了问题你怎么排查?

我在项目里遇到过好几次——Pandas读出来全是乱码,或者数据类型全变成object。这时候回头用csv.reader()一查,问题立马就暴露了。所以这一章,咱们就把根基打牢。

2.1 为什么选csv模块?

Python标准库里的csv模块,轻量、稳定、零依赖。它不像Pandas那样「全家桶」,但恰恰因为简单,你才能看清每一行数据是怎么进来的。

对比项 csv模块 Pandas
依赖 无(标准库) 需安装
读取速度(小文件) 快 稍慢(加载开销)
内存占用 低(逐行) 高(全量加载)
类型推断 无(全字符串) 自动推断
适用场景 调试、小文件、流式处理 数据分析、大表操作

说白了,csv模块就是给你一把「手术刀」,让你精确控制每一行。而Pandas是「瑞士军刀」,功能多但有时候会自作主张。

2.2 reader() 的核心用法

csv.reader()接受一个文件对象,返回一个迭代器。每次迭代返回一行,类型是列表。嗯,这里要注意——它不会自动跳过空行,也不会处理引号内的换行,这些都得你自己兜底。

import csv

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

这段代码看起来简单,但我踩过一个坑:如果CSV文件是Excel导出的,编码可能是gbk或gb2312。用utf-8读会直接报错。我建议你第一次打开时,用try-except兜一下编码问题。

我的习惯:先读前几行判断编码,再正式读取。用chardet库自动检测也行,但小文件我直接试utf-8和gbk两种。

2.3 逐行读取与类型转换

reader()返回的每一行都是字符串列表。但配方数据里,温度、浓度、时间这些字段,你肯定希望它们是数字。所以类型转换这一步,必须手动做。

import csv

with open('配方数据.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    for row in reader:
        # row[0] 是配方名称,保持字符串
        name = row[0]
        # row[1] 是温度,转成float
        temp = float(row[1])
        # row[2] 是浓度,转成float
        conc = float(row[2])
        # row[3] 是批次号,保持字符串
        batch = row[3]
        print(f'{name}: {temp}°C, {conc}%, 批次{batch}')

为什么会这样?因为CSV本质是纯文本,没有类型信息。你想想看,Excel打开CSV时能自动识别数字,那是因为它做了「猜测」。但Python不会替你猜,它把选择权交给你。

我曾经踩过的坑:有一批数据里,温度字段偶尔出现空值。直接float('')会抛ValueError。后来我加了个判断:temp = float(row[1]) if row[1] else None。记住,生产环境里「脏数据」才是常态。

2.4 处理表头与数据分离

大多数CSV文件第一行是表头。我见过有人直接next(reader)跳过,然后遍历剩下的行。这没问题,但有个细节——表头行本身也可能包含空值或注释,你得先校验一下。

import csv

with open('配方数据.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    header = next(reader)
    # 校验表头是否完整
    expected = ['配方名称', '温度', '浓度', '批次号']
    if header != expected:
        print(f'警告:表头不匹配,期望{expected},实际{header}')
    # 正式读取数据
    for row in reader:
        # 跳过空行
        if not any(row):
            continue
        name, temp, conc, batch = row
        # 类型转换
        temp = float(temp) if temp else None
        conc = float(conc) if conc else None
        print(f'{name}: {temp}, {conc}, {batch}')

我个人习惯把表头和数据分离后,用dict来组织每一行。这样代码可读性更高,也不容易搞错列索引。

import csv

with open('配方数据.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    header = next(reader)
    data = []
    for row in reader:
        if not any(row):
            continue
        record = dict(zip(header, row))
        # 类型转换
        record['温度'] = float(record['温度']) if record['温度'] else None
        record['浓度'] = float(record['浓度']) if record['浓度'] else None
        data.append(record)

# 现在 data 是一个字典列表,用字段名访问
for rec in data:
    print(rec['配方名称'], rec['温度'])
核心思路:表头是「元数据」,数据行是「实例」。分离后,你可以对表头做校验、重命名、甚至动态生成处理逻辑。我在做配方导入系统时,就是靠这个思路实现了「任意列顺序兼容」。

2.5 本章知识结构图

下面这张图,帮你把本章的核心逻辑串起来。从文件打开到数据可用,每一步都有对应的处理要点。

CSV读取核心流程 CSV读取核心流程 打开文件 创建 reader() 分离表头 逐行读取 类型转换 注意事项 • 编码:utf-8 / gbk • 空行:跳过处理 • 脏数据:None兜底 输出结果 • 列表:row[0], row[1]... • 字典:record['温度'] • 类型:str → float/int

2.6 常见问题与避坑

  • 编码错误:用encoding='utf-8-sig'可以自动处理BOM头。Excel导出的UTF-8文件经常带BOM。
  • 引号内换行:如果某个字段包含换行符,reader()会自动处理,但前提是字段被引号包裹。如果没包裹,行数会错乱。
  • 空行:CSV文件末尾经常有空行。用if not any(row): continue跳过。
  • 列数不一致:某一行少了几列,reader()不会报错,但后续索引会越界。我建议每行都检查len(row)。
一个小技巧:用csv.DictReader可以自动把第一行当表头,返回字典。但它的类型转换还是得手动做。我个人更喜欢用reader() + zip(header, row),更灵活。

好了,这一章的内容就这些。记住:csv.reader()是基础,逐行读取是核心,类型转换是必须,表头分离是规范。把这四点吃透,后面批量导入配方数据时,你就能游刃有余了。