这次我们来看 Pandas 读写 txt 和 csv 文件。做数据分析的同学应该都有同感:read_csv和to_csv几乎是每天都要碰的函数,但大多数人的用法停留在最简单的“读进来、存出去”。一旦遇到表头缺失、编码乱码、分隔符不是逗号、大文件内存不够、批量导出命名冲突这些问题,又开始翻文档、搜百度,一上午就没了。
这篇文章把read_csv和to_csv的常用参数、典型场景和坑位一次讲透,全部围绕真实数据分析流程来写。你不仅能看懂每个参数是干什么的,还能直接复制代码改路径跑起来,从基础读取到批量处理一条线打通。
文章会覆盖这些内容:Pandas 核心能力速览、环境准备、read_csv参数详解(表头、列筛选、索引列、分隔符、缺失值、类型、分块读取)、to_csv参数详解(索引控制、编码、追加、压缩、精度控制)、TXT 文件读写、完整实战案例、常见问题排查、工程化建议。
1. Pandas 读写能力速览
先给出一张总览表,后面所有内容都围绕这张表展开。
| 能力项 | 说明 |
|---|---|
| 核心函数 | read_csv、to_csv,可同时处理 csv 和 txt |
| 支持的文本格式 | csv(逗号分隔)、tsv(制表符分隔)、空格/正则分隔符文本 |
| 文件编码 | UTF-8、GBK、GB2312、utf-8-sig 等,通过encoding参数指定 |
| 表头处理 | 自动识别表头、无表头指定names、跳过表头 |
| 列筛选 | usecols按列名或列下标读取指定列 |
| 索引列 | index_col指定某一列作为行索引 |
| 缺失值处理 | na_values自定义缺失值标记,na_rep控制写出占位 |
| 大文件处理 | chunksize分块迭代,避免一次性读入内存 |
| 数据写出 | 保存为 csv、txt、压缩包(gzip/zip) |
| 追加写入 | mode='a'向已有文件追加数据 |
| 批量任务 | 配合glob/pathlib批量读取和命名导出 |
| API 能力 | read_csv支持从 URL、文件对象直接读取 |
| 学习成本 | 参数多,但核心掌握十来个就够解决 95% 场景 |
材料里的热搜词集中在“python 读取 csv”“pandas 导入 csv”“pandas 转换数据类型”“csv 保存”等方向,说明这是新手到进阶都会卡住的点。这篇文章的目标是让你看完之后,处理日常 csv/txt 文件不再求人。
2. 适用场景与使用边界
read_csv和to_csv适合这些场景:
- 读取 Excel 另存的 csv 文件、数据库导出的 csv 文件、爬虫抓取的表格数据。
- 把清洗、聚合、转换后的 DataFrame 保存为 csv 给其他同事或系统使用。
- 批量合并多个结构相同的 csv 文件。
- 跨系统交换数据,csv 是最低门槛的通用格式。
- 保存模型特征工程中间结果、训练集切分结果、日志解析结果。
- 读取部分业务系统生成的 txt 日志、空格分隔的文本数据。
不适合或需要小心的场景:
- 文件超过几个 GB 时,直接
pd.read_csv()容易内存爆炸,要用分块或换用 Polars、Dask、数据库。 - 数据包含复杂嵌套结构(如 JSON 里套数组),csv 不适合表达这种层级关系。
- 需要频繁随机读写和更新单行数据时,应该用数据库或 parquet。
- 数据量很大且需要长期分析时,parquet 比 csv 更省空间、读写更快。
- 涉及用户隐私数据、商业数据时,导出的 csv 要注意脱敏、访问权限和加密存储,不能把手机号、身份证等敏感字段明文丢在共享目录里。
csv 文件的本质是纯文本,虽然简单,但编码、换行符、分隔符、转义规则都会带来坑。后续每一节都会针对这些边界给出具体处理方式。
3. 环境准备与前置条件
在动手之前,先确认环境。
操作系统方面,Windows、Linux、macOS 都可以,命令只有微小的平台差异。Python 版本建议 3.8 及以上。如果你是新环境,先安装 Pandas:
pip install pandas如果你使用 Anaconda,Pandas 通常已经预装,可以使用conda install pandas更新到需要的版本。安装完成后,在 Python 交互环境或脚本里验证:
import pandas as pd print(pd.__version__)正常情况下会输出一个版本号。如果提示No module named 'pandas',说明 Pandas 没有安装成功,需要回到 pip 安装步骤,并检查当前终端是不是对应你要用的 Python 环境。
关于 PyCharm,如果你用的是 PyCharm 社区版或专业版,直接在下方 Terminal 执行 pip 安装即可。如果是在项目里配置了虚拟环境,注意先激活虚拟环境再装依赖。
准备测试文件时,可以先手动创建一个简单的 csv。下面这段代码会在当前目录生成一个用于测试的students.csv:
import pandas as pd demo_df = pd.DataFrame({ "name": ["张三", "李四", "王五", "赵六"], "class": ["一班", "二班", "一班", "三班"], "score": [88, 92, 76, 85] }) demo_df.to_csv("students.csv", index=False, encoding="utf-8") print("测试文件已生成")运行后,目录下会多出一个students.csv,后面的示例都基于这个文件展开。
4. read_csv 基础读取与参数详解
这一节是重点。pd.read_csv()的参数非常多,但实际项目里高频使用的是下面这些,我按功能分组讲。
4.1 最简读取
import pandas as pd df = pd.read_csv("students.csv") print(df)输出:
name class score 0 张三 一班 88 1 李四 二班 92 2 王五 一班 76 3 赵六 三班 85默认行为是:第一行作为列名,后续行作为数据,自动生成从 0 开始的行索引。如果你的文件就是标准 csv,这样写就够了。
4.2 处理表头:header 和 names
实际场景中,很多 csv 文件没有表头,或者表头是中文、带空格、带 BOM,直接读取会出问题。
文件没有表头,要用header=None配合names指定列名:
df = pd.read_csv("students_no_header.csv", header=None, names=["姓名", "班级", "分数"])如果没有names,Pandas 会自动用0, 1, 2...作为列名,后期处理很别扭。
想跳过原始表头,直接指定自己的列名:
df = pd.read_csv("students.csv", header=0, names=["stu_name", "stu_class", "stu_score"])这里header=0表示跳过第一行原始表头,用names替代。
表头前面有无关行,可以用skiprows跳过:
df = pd.read_csv("messy.csv", skiprows=2)这样会跳过文件最前面的 2 行,然后自动识别新表头。
4.3 指定列与索引列
一个 csv 可能有几十列,但你只需要其中几列。全量读入再用df[["列名"]]筛选,会浪费内存和时间。usecols可以直接在读取阶段筛选:
df = pd.read_csv("students.csv", usecols=["name", "score"])也可以按列下标筛选,比如读取第 0 列和第 2 列:
df = pd.read_csv("students.csv", usecols=[0, 2])这个参数在处理宽表时非常实用。
如果希望某一列成为 DataFrame 的行索引,用index_col:
df = pd.read_csv("students.csv", index_col=0) print(df)输出:
class score name 张三 一班 88 李四 二班 92 王五 一班 76 赵六 三班 85index_col可以传列名,也可以传下标。多级索引时还可以传列表,如index_col=["year", "month"],但日常少用。
4.4 分隔符:sep 与正则表达式
csv 的“c”是 comma,但现实里的文本文件分隔符五花八门:制表符、空格、分号、竖线。用sep参数解决。
读取制表符分隔的 txt 文件:
df = pd.read_csv("students.tsv", sep="\t")读取分号分隔的文件:
df = pd.read_csv("students_semicolon.csv", sep=";")读取空格分隔,但多个空格数量不一时,用正则表达式\s+:
df = pd.read_csv("students.txt", sep=r"\s+")注意:sep如果是正则表达式,建议加上engine='python',避免默认 C 引擎的兼容性限制:
df = pd.read_csv("students.txt", sep=r"\s+", engine="python")4.5 缺失值处理:na_values 与 keep_default_na
csv 文件里缺失值可能表示为空字符串、NA、NULL、N/A、-等。Pandas 默认把空字符串、NA、NaN等识别为缺失值,但自定义标记需要手动指定:
df = pd.read_csv("students_with_na.csv", na_values=["NA", "NULL", "-"])这样文件里的-就会变成NaN,后续dropna()、fillna()才能正确处理。
如果你不想让 Pandas 把空字符串当缺失值,而是保留为普通字符串,可以设置:
df = pd.read_csv("students.csv", keep_default_na=False)这个参数在做文本处理时比较有用,但要注意,关闭默认缺失值识别后,空单元格会成为空字符串,而不是NaN。
4.6 数据类型指定:dtype
Pandas 读取大文件时,默认会自动推断每列类型。但自动推断有一个常见坑:id列明明是一串数字,但某些行前导 0 被自动忽略了;或者某列大部分是数字,中间混入少量文本,整个列变成object。
这时用dtype强制指定:
df = pd.read_csv("students.csv", dtype={"score": float, "name": str})实际业务里最典型的是手机号、学号、身份证号、订单号:
df = pd.read_csv("users.csv", dtype={"phone": str, "user_id": str})如果不指定,phone列里前导 0 会被丢掉,后面匹配和展示时就出大问题。
dtype还有一个好处:可以避免大文件读取时自动类型推断额外消耗的时间。converters参数适合做列级转换,比如把字符串转成日期,但parse_dates通常更直接:
df = pd.read_csv("sales.csv", parse_dates=["order_date"])4.7 大文件分块读取:chunksize
当文件大到几 GB 时,一次pd.read_csv()会占满内存,直接卡死。正确做法是分块读取:
chunk_iter = pd.read_csv("big_data.csv", chunksize=10000) for chunk in chunk_iter: # 对每一块做处理 print(chunk.shape)chunksize=10000表示每次读取 1 万行。chunk_iter是一个可迭代对象,每轮返回一个 DataFrame。
如果需要统计一个大文件的行数,不需要全部读入:
row_count = 0 for chunk in pd.read_csv("big_data.csv", chunksize=10000): row_count += len(chunk) print(row_count)如果文件特别大,又只需要部分列,配合usecols可以减少内存占用。
4.8 读取网络 URL 文件
read_csv可以直接读取网络地址:
url = "https://example.com/data.csv" df = pd.read_csv(url)不过在国内访问外部地址可能不稳定,更稳妥的做法是先下载到本地再读取。频繁请求时要注意目标网站的使用条款和访问频率。
5. to_csv 保存数据与参数实战
read_csv是把文本读成 DataFrame,to_csv是把 DataFrame 写回文本。方向相反,坑也一样多。
5.1 最简保存
df.to_csv("output.csv")这样保存的结果会带上行索引,生成的文件第一列是Unnamed: 0,这是最常见的坑。绝大多数业务场景不需要行索引,所以标准写法是:
df.to_csv("output.csv", index=False)5.2 控制索引与表头
index控制是否写出行索引:
df.to_csv("output.csv", index=False)如果想保存索引,又希望索引列有名字,可以这样:
df.to_csv("output_with_index.csv", index=True)header控制是否写出列名:
# 不写表头,常用于追加数据 df.to_csv("output.csv", header=False)5.3 解决 Excel 打开乱码:encoding="utf-8-sig"
中文数据保存为 csv 后,用 Excel 打开经常显示乱码。原因通常是 Windows 版 Excel 对 UTF-8 不带 BOM 的识别有问题。解决方式是用utf-8-sig编码:
df.to_csv("students_utf8_sig.csv", index=False, encoding="utf-8-sig")如果明确要和旧系统交换数据,系统要求 GBK 编码,可以这样写:
df.to_csv("students_gbk.csv", index=False, encoding="gbk")读取时也要对应:
df = pd.read_csv("students_gbk.csv", encoding="gbk")5.4 只保存指定列
如果 DataFrame 有很多列,导出时只想保留部分列,用columns参数:
df.to_csv("students_part.csv", columns=["name", "score"], index=False)5.5 自定义分隔符输出
想把 DataFrame 保存为 txt 或 tsv,修改sep:
df.to_csv("students.tsv", sep="\t", index=False)如果不指定sep,默认是逗号。
5.6 追加写入:mode="a"
向已有文件追加数据,需要mode="a"。追加时一般不需要表头:
new_data.to_csv("all_data.csv", mode="a", header=False, index=False)这里有几个要注意的点:文件编码要和已有文件保持一致,否则乱码;列顺序要和已有表头一致,否则数据错位。追加前最好先读取文件确认结构。
5.7 压缩保存
大数据量保存为 csv 后体积很大,可以直接输出压缩包:
df.to_csv("students.csv.gz", index=False, compression="gzip")读取时read_csv自动识别:
df = pd.read_csv("students.csv.gz")如果保存为 zip:
df.to_csv("students.csv.zip", index=False, compression="zip")5.8 控制小数精度与缺失值表示
浮点数导出时位数很长,可以用float_format控制:
df.to_csv("students_float.csv", index=False, float_format="%.2f")缺失值在 csv 文件中默认显示为空字符串,想自定义:
df.to_csv("students_na.csv", index=False, na_rep="NULL")6. 读写 TXT 文件:read_table 与 sep 配合
Pandas 没有单独的read_txt,读写 txt 文件仍然是复用read_csv/to_csv,只是换分隔符而已。
最常见的 txt 数据有两种:制表符分隔、空格分隔。
制表符分隔文件:
df = pd.read_csv("data.tsv", sep="\t")空格分隔文件,且列之间可能有多个空格:
df = pd.read_csv("data.txt", sep=r"\s+")无表头 txt 文件:
df = pd.read_csv("data.txt", sep="\t", header=None, names=["col1", "col2", "col3"])写出为 txt:
df.to_csv("data.txt", sep="\t", index=False)用sep统一处理 csv 和 txt,是 Pandas 最方便的一点:不需要学习两套 API。
如果你拿到的文件扩展名是.data、.log、.dat,只要内容结构是分隔符文本,read_csv都能处理,关键是找到正确的sep。
7. 实战:CSV 数据清洗后批量导出
下面做一个完整的综合练习:批量读取多个 csv 文件,完成简单清洗,再统一导出。
假设./data/raw目录下有三个格式相同的 csv 文件,每列包含name、score、remark,score可能是字符串或缺失。用glob配合 Pandas 处理:
import pandas as pd import glob import os input_dir = "./data/raw" output_path = "./data/clean/all_result.csv" file_list = glob.glob(os.path.join(input_dir, "*.csv")) print("找到文件数量:", len(file_list)) df_list = [] for file_path in file_list: df = pd.read_csv(file_path, encoding="utf-8", dtype={"name": str}, na_values=["-", "NULL"]) # 去除空白列名 df.columns = df.columns.str.strip() # 删除完全重复的行 df = df.drop_duplicates() # score 转数值,错误值变成 NaN df["score"] = pd.to_numeric(df["score"], errors="coerce") df_list.append(df) result = pd.concat(df_list, ignore_index=True) print("合并后总行数:", len(result)) # 导出为 utf-8-sig,Excel 直接打开不乱码 result.to_csv(output_path, index=False, encoding="utf-8-sig", float_format="%.2f") print("已导出:", output_path)关键点:
glob.glob拿到目录下所有 csv 路径。na_values把-和NULL统一识别为缺失。drop_duplicates()删除完全重复的行。pd.to_numeric(errors="coerce")把无法转成数字的值变成NaN,避免整列变成字符串。- 最后用
utf-8-sig导出,方便给同事用 Excel 打开。
如果业务上要求每个原始文件单独输出一份清洗后的结果,可以这样:
clean_dir = "./data/clean" os.makedirs(clean_dir, exist_ok=True) for file_path in file_list: df = pd.read_csv(file_path, encoding="utf-8") df.columns = df.columns.str.strip() df = df.drop_duplicates() base_name = os.path.basename(file_path) save_path = os.path.join(clean_dir, "clean_" + base_name) df.to_csv(save_path, index=False, encoding="utf-8-sig") print("保存:", save_path)这个模式可以扩展到几百个文件,核心思路是:目录遍历、逐文件处理、命名规则统一。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 读取 csv 后中文乱码 | 文件编码与指定编码不一致 | 用文本编辑器打开看原始编码,或用chardet判断 | 读取时指定正确编码,如encoding="gbk" |
| 导出的 csv 用 Excel 打开乱码 | 保存时用了utf-8,没有带 BOM | 用文本编辑器打开确认内容 | 保存时使用encoding="utf-8-sig" |
第一列出现Unnamed: 0 | to_csv时没有设置index=False | 查看文件第一行列名 | 保存时index=False |
读取报ParserError: Error tokenizing data | 文件列数不一致,或有额外分隔符 | 查看出错行;打开文件确认分隔符 | 指定sep,加error_bad_lines=False(新版用on_bad_lines='skip') |
| 数字列读出来是字符串 | 列中混入非数字字符,或指定了错误 dtype | df.dtypes查看类型 | 用pd.to_numeric(..., errors="coerce")转换 |
| 前导 0 丢失 | 读取时自动推断为数值类型 | 查看列值长度 | 读取时dtype={"列名": str} |
| 读取大文件卡死或内存溢出 | 一次性读入全部数据 | 查看文件大小和内存 | 使用chunksize分块读取 |
| 追加写入后数据错位 | 列顺序或分隔符不一致 | 打开文件肉眼核对 | 追加前统一列顺序,header=False |
| 空值没有识别为 NaN | 自定义缺失标记未指定 | 打印df.isna().sum() | 使用na_values指定标记 |
| 文件路径含中文报错 | Windows 下编码处理问题 | 查看报错信息 | 使用原始字符串或pathlib.Path,避免手写转义 |
新版 Pandas 中,error_bad_lines参数已经被移除,替代方案是on_bad_lines:
df = pd.read_csv("messy.csv", on_bad_lines="skip")9. 最佳实践与使用建议
- 第一次读取新文件时,先只读前几行看结构:
df_preview = pd.read_csv("data.csv", nrows=5) print(df_preview)nrows是排查文件格式问题的利器,不用等大文件全部读入。
不要直接改原始数据文件。清洗、转换后的结果另存一份,保留原始文件作为数据源。
文件目录区分
raw、clean、output。原始数据、中间结果、最终交付分开放,避免后续找不到谁是谁。批量处理一定要加日志或打印进度。文件少时无所谓,几百个文件处理到一半中断,没有日志会非常被动。
追加写入前统一列名和顺序,最好先检查目标文件是否存在以及是否为空。
编码问题要在项目最开始就定好。团队内部统一 UTF-8 或 utf-8-sig,和旧系统交换时单独导出 GBK 版本。
涉及手机号、身份证号等敏感数据时,导出 csv 前先做脱敏处理,并限制文件的访问权限。
大文件导出时可以考虑压缩格式:
df.to_csv("output.csv.gz", index=False, compression="gzip")- 如果数据量到了几千万行,csv 已经不是最佳选择,考虑 parquet、数据库或者分区存储。
10. 总结与下一步
read_csv和to_csv看起来简单,实际用到项目里,编码、表头、列类型、大文件、批量导出,每一项都是细节。这次把最常用的参数和几个完整场景过了一遍,下次写pd.read_csv("xxx.csv")之前,先花十秒确认三件事:文件是什么编码、有没有表头、分隔符是什么。三个问题想清楚,能省下大量调试时间。
如果想继续深入,下一步可以看这几个方向:Pandas 的read_excel和 Excel 多 Sheet 处理、merge和concat多表合并、groupby聚合统计、以及 Parquet 格式在大数据场景下的应用。建议把这篇里的示例代码在本地环境跑一遍,然后拿自己手头真实数据做一次清洗导出练习,几天后你会发现自己处理表格文件的效率有明显提升。建议收藏备用。