Menu

Python CSV:用 csv 模块读写 CSV 文件

如何在 Python 中读写 CSV 文件:csv 模块、DictReader 和 DictWriter、处理表头和引号,以及什么时候改用 pandas。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

CSV 比看上去简单(也比你想的更棘手)

CSV 文件就是纯文本:行与行之间用换行符分隔,字段之间用逗号分隔。思路就这么简单。但在实际中,你会碰到包含逗号的带引号字符串、字段里嵌着换行符、不同地区的不同惯例(逗号还是分号)、从 Excel 保存的带 BOM 的文件,边界情况多到你自己写 line.split(",") 几乎总是个错误。

Python 内置的 csv 模块能处理所有这些情况。对中小型文件来说,你很少需要用别的东西。

用 csv.reader 读取 CSV

csv.reader 把每一行作为一个字符串列表逐个返回:

import csv

with open("people.csv", newline="") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

有几个不那么显而易见的细节:

  • 一定要给 open 传 newline=""。 csv 模块会自己处理行结束符;不加这个参数,在 Windows 上会多出空行。
  • 每个值都是字符串。 "42" 在你调用 int(...) 之前一直是字符串。CSV 没有类型。
  • 表头行只是普通的一行。 如果你的文件有表头,要么手动跳过第一行,要么改用 DictReader。

跳过表头行

import csv

with open("people.csv", newline="") as f:
    reader = csv.reader(f)
    headers = next(reader)         # pulls the first row out
    for row in reader:
        print(row)

next(reader) 让迭代器前进一步,并返回那一行。

用 DictReader 读取为字典

csv.DictReader 把第一行当作表头,把之后的每一行作为一个字典给你:

import csv

with open("people.csv", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["email"])

这几乎总是你想要的。列名本身就说明了含义,即使源文件中的列换了顺序,你的代码也不会出错。

如果文件没有表头,就用 fieldnames=["name", "email", ...] 显式传入。

用 csv.writer 写入 CSV

csv.writer 把行(列表)转换成 CSV 的行:

import csv

rows = [
    ["name", "age", "city"],
    ["Rosa", 30, "Lisbon"],
    ["Ada", 36, "London"],
]

with open("out.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerows(rows)

writerow(row) 写入一行;writerows(rows) 一次写入整个可迭代对象。当字段包含逗号、引号或换行符时,两者都会自动给字段加上引号,你完全不用操心。

用 DictWriter 写入字典

当你的数据本来就是字典形式时,DictWriter 可以省掉“转换成列表”这一步:

import csv

people = [
    {"name": "Rosa", "age": 30, "city": "Lisbon"},
    {"name": "Ada", "age": 36, "city": "London"},
]

with open("out.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "age", "city"])
    writer.writeheader()
    writer.writerows(people)

fieldnames 参数同时决定表头和列的顺序。字典中不在 fieldnames 里的键会被悄悄丢掉(你也可以用 extrasaction="raise" 让它抛出异常)。

不同的分隔符和引号规则

并不是所有的“CSV”都用逗号。欧洲地区常用 ;,制表符分隔的文件用 \t,有些系统用 |。传入 delimiter= 参数即可:

import csv

with open("data.tsv", newline="") as f:
    reader = csv.reader(f, delimiter="\t")
    for row in reader:
        print(row)

对于引号规则特殊的文件,csv.register_dialect(...) 让你配置一次就能反复使用。对大多数文件来说,默认设置加上 delimiter= 就够了。

编码

CSV 文件是文本,所以有编码。UTF-8 是现在的默认编码;在 Windows 上由 Excel 生成的文件有时会用 cp1252,或者带有 UTF-8 BOM。请显式指定编码:

with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    ...

如果你看到 UnicodeDecodeError,说明文件的编码不是你猜的那种。常见的嫌疑对象有 utf-8-sig(能处理 Excel 的 BOM)、cp1252 和 latin-1,逐个试试。

把 CSV 的行转换成有用的类型

既然每个值读进来都是字符串,解析就得靠你自己:

(StringIO 让我们不需要真实的文件就能运行这个例子,在实际代码中你会用 open(path)。)

对于类型比较棘手的 CSV(日期、可能为空的数字、有十种不同写法的 true/false),可以考虑 pandas,它为其中大部分情况内置了处理惯例。

什么时候改用 pandas

pandas.read_csv(path) 返回一个 DataFrame。一旦你想做下面这些事,它就是合适的数据结构:

  • 筛选行:df[df["active"] == True]
  • 聚合:df.groupby("city")["age"].mean()
  • 与另一张表连接
  • 用简单的格式写回文件
import pandas as pd

df = pd.read_csv("people.csv")
adults = df[df["age"] >= 18]
adults.to_csv("adults.csv", index=False)

对于小型的、按顺序读取的场景,pandas 有点大材小用,而且它是一个很重的依赖(要在虚拟环境中用 pip 安装)。但只要是和数据分析相关的工作,它就是大多数 Python 分析师首选的工具。

流式处理超大文件

csv.reader 本身就是惰性的:它一次只读一行。保持这种方式,也就是逐行迭代(而不是一开始就调用 list(reader)),无论文件多大,内存占用都保持平稳:

import csv

with open("huge.csv", newline="") as f:
    reader = csv.DictReader(f)
    error_count = 0
    for row in reader:
        if row["status"] == "error":
            error_count += 1

print(f"Found {error_count} errors.")

只要你不把所有行都攒进一个列表,处理 10 GB 的文件和处理 10 KB 的文件一样轻松。

几个好习惯

  • 读写 CSV 时,总是给 open 传 newline=""。
  • 只要文件有表头,就用 DictReader/DictWriter,比整数索引更易读。
  • 显式指定编码,尤其是来自 Excel 或非英文来源的文件。
  • 在读取时就转换好类型,这样后面的代码就不用再处理。
  • 当你想“分析”数据,而不只是搬运数据时,就改用 pandas。

接下来

现在你已经能读取 JSON 和 CSV 了。我们要讲的最后一项实用技能是通过网络获取数据,也就是下一篇文档:用 requests 库发送 HTTP 请求。

常见问题

在 Python 中怎么读取 CSV 文件?

使用内置的 csv 模块。csv.reader 把每一行作为一个字符串列表给你;csv.DictReader 把第一行当作表头,把每一行作为一个字典逐个返回。打开文件时加上 newline='',这样 Python 就不会弄乱换行符:with open('data.csv', newline='') as f:。

在 Python 中怎么写入 CSV 文件?

把 csv.writer 和一个以写入模式、带 newline='' 打开的文件配合使用。每一行调用一次 writer.writerow([...]),或者用 writer.writerows([[...], [...]]) 一次写入多行。如果数据是字典形式的,就用 csv.DictWriter,它会自动处理表头。

该用 csv 模块还是 pandas?

快速读写、逐行处理文件,以及不想再多加一个依赖时,用 csv。需要筛选、分组或连接数据时,或者文件大到向量化操作能带来明显差别时,用 pandas。它们能处理同样的文件;怎么选,取决于加载数据之后你要拿它做什么。

为什么在 Windows 上我的 CSV 每行之间都有空行?

你打开文件时没有加 newline=''。csv 模块会自己写入行结束符;不加这个参数,Python 在 Windows 上会再多加一些。无论读还是写,打开 CSV 文件时都要用 open(path, newline='')。

Coddy 编程语言插图

和 Coddy 一起学编程

开始