Pandas读写CSV与txt文件实战指南:从参数详解到踩坑避坑
2026/9/2 12:50:09 网站建设 项目流程

上周处理一批电商订单数据时,我在一个最常见的操作上连续踩了好几个坑:pd.read_csv()读文件,先报编码错误;改成utf-8后中文正常了,但日期列变成了字符串;处理完日期,又发现有两行数据因为字段里多了逗号,被拆成了多余列;最后好不容易清洗完,用to_csv写出去,再读回来,索引又被当成普通列带进了表里。整个过程像一趟寻宝之旅,每一步都以为解决了,下一层问题马上补上。

这件事让我重新想了一个问题:Pandas 读写 txt 和 csv 文件的真正难点,到底在哪里?

它显然不在“读取”和“保存”这两个动作上。动作只是一个函数调用。真正麻烦的,是从“磁盘上的纯文本”到“内存里有类型的 DataFrame”之间的那层映射关系。文件本身没有列名、没有类型、没有空值概念,所有结构化信息都要靠参数告诉 Pandas。参数用对了,一次读取就是一次干净转换;参数用错了,后面所有分析都建立在一堆脏数据上。

这篇文章我会把read_csvto_csv的常用参数和实战经验拆开讲清楚。重点不是背参数清单,而是理解:读文件时,我怎么把文件结构“翻译”成 DataFrame;写文件时,我怎么保证下次能原样读回来。

1. 先搞清楚:Pandas读写文件的难点,真的不在“读”和“写”这两个动作上

1.1 一次真实踩坑:从“能读”到“读对”,隔着一整层映射关系

我最早学 Pandas 时,也以为read_csv("xxx.csv")就是打开文件。直到有一天,我需要处理一份电力负荷的预测数据,文件长这样:

日期,时刻,城市,温度,负荷 2024-01-01,00:00,北京,-3.2,12500.4 2024-01-01,00:00,上海,4.1,9800.2 ...

第一遍直接read_csv,没有任何报错。我以为数据已经可用了。结果df.info()一看:

  • 温度object类型,因为文件里某些行写的是-
  • 日期是字符串,因为原始文件里日期和时间分开两个字段。
  • 负荷中有几行带千分位逗号,导致整列变成 object。

那一刻我才意识到:read_csv默认能读,不代表读得对。Pandas 在背后替我们做了大量“自动猜测”,但猜测不等于事实。文件数据一旦有一点不干净,自动判断就会失真。

1.2 为什么“表头、分隔符、类型、空值”必须由人确认

CSV 全文是纯文本,它本身不区分“数字”和“字符串”。

1,小王,89.5 2,小李,90

读进 Pandas 后,第一列大概率会被识别成 int,第三列会被识别成 float。这个识别过程靠的是 Pandas 对整列内容的抽样推断。可一旦列里有“缺失值”、“特殊字符”、“混合类型”,自动推断就会把整列降级成 object。

所以,真正有经验的用法,不会把read_csv当成一个黑盒函数直接调,而是先做三个判断:

  1. 文件的分隔符是什么?逗号、制表符、空格还是竖线。
  2. 文件有没有表头?第一行是列名还是数据。
  3. 每列的类型是否符合我的预期?日期要用parse_dates,身份证号、手机号要用字符串而不是数字,缺失值要统一指定。

这三件事确认清楚了,read_csv才真正变成“数据接入工具”,而不只是“文件打开工具”。

2. 把read_csv拆成四层,才对得起它几十个参数

read_csv参数很多,初学者最容易犯的错是试图全部记住。我的建议是按数据进入 DataFrame 的顺序,把参数分成四个层级:文件层、解析层、类型层、结构层。

排查问题时也按这个顺序查:文件打不开,看文件层;数据列数不对,看解析层;列类型不对,看类型层;索引和列顺序不对,看结构层。

2.1 最小可运行读取流程

先给一个最常见的完整示例,后面再拆开解释:

import pandas as pd df = pd.read_csv( "sales_data.csv", encoding="utf-8", dtype={"phone": str}, parse_dates=["order_date"], na_values=["", "NULL", "NA"], usecols=["order_id", "order_date", "city", "amount", "phone"], index_col=0, )

这段代码展示了四层参数的一次集中应用。下面逐层拆开。

2.2 第一层:文件读取层,先解决“能不能打开”

文件读取层决定的是:文件在哪里、用什么编码打开、用什么引擎解析。

参数常见取值作用
filepath_or_buffer本地路径、URL、文件对象数据来源
encodingutf-8gbkutf-8-sig文本编码
enginecpython底层解析引擎

编码是最容易踩坑的地方。

国内很多系统导出的 CSV 是 GBK 编码,直接用默认read_csv会报UnicodeDecodeError。这时候要改成:

df = pd.read_csv("download.csv", encoding="gbk")

还有一个很容易忽视的坑:UTF-8 文件带 BOM 头时,列名会变成\ufeff列名解决办法是使用:

df = pd.read_csv("download.csv", encoding="utf-8-sig")

utf-8-sig会自动把 BOM 头去掉,这也是“读下来列名很怪”时最先要检查的点。

引擎参数:实际读文件时,c引擎更快,python引擎更灵活。如果遇到非常规分隔符、复杂引号规则,c引擎解析不了时,可以试试engine="python"。但不要默认使用 python 引擎,性能会差不少。

2.3 第二层:解析层,解决“表长什么样”

解析层决定文件内容怎么被切割成“行和列”。

参数作用
sep/delimiter分隔符,默认是逗号
header哪一行作为列名,默认 0
names手动指定列名
skiprows跳过文件前几行
skip_blank_lines是否跳过空行
on_bad_lines遇到坏行时怎么处理

最常见的需求是:文件开头有几行说明文字,真正表头在第 3 行。

df = pd.read_csv( "report.txt", skiprows=2, # 跳过前两行说明 header=0, # 第三行作为表头 encoding="utf-8-sig" )

如果文件完全没有表头,需要自己指定列名:

df = pd.read_csv( "data_no_header.csv", header=None, names=["id", "name", "amount", "date"] )

on_bad_lines是一个长期被低估的参数。真实数据里,偶尔会有行因为多了一个逗号导致列数不匹配。旧版 Pandas 遇到这种情况会直接报错,现在可以这样处理:

df = pd.read_csv( "messy_data.csv", on_bad_lines="skip" # 跳过坏行;也可以先 warn )

但注意,跳过坏行是最后的手段。遇到坏行,更值得先找出是哪个文件、哪一行、为什么坏了。直接跳过,可能把关键数据丢掉。

2.4 第三层:类型层,决定“每列是什么”

这是新手和进阶使用者差距最大的一层。

参数作用
dtype指定列的类型
parse_dates把指定列解析为日期时间
na_values把哪些值当作缺失值
keep_default_na是否保留默认缺失值集合

先处理身份证号、手机号这一类“长得像数字但不是数字”的列。

df = pd.read_csv( "users.csv", dtype={ "phone": str, # 保留前导0 "id_card": str, # 防止精度丢失 "member_level": "category" } )

如果不指定,Pandas 会把长的数字列自动识别成 int64 或 float64。身份证号一旦超过 15 位,double 类型会丢精度,读进来后末尾几位全变成 0,这几乎是不可逆的损失。

日期解析要尽量在读取时做,不要读取后再循环转换。

df = pd.read_csv( "sales_data.csv", parse_dates=["order_date"] )

这样读进来order_date就是datetime64类型,后续可以直接按月份筛选、按天聚合。

空值处理要在读取时统一指定,不要等读到一半再面对“NA”、“NULL”、“--”各种写法。

df = pd.read_csv( "sales_data.csv", na_values=["", "NA", "NULL", "N/A", "\\N"], keep_default_na=True )

2.5 第四层:结构层,决定“哪列当索引、哪些列先用”

结构层处理的是读进来之后,DataFrame 的“骨架”长什么样。

df = pd.read_csv( "sales_data.csv", usecols=["order_id", "order_date", "city", "amount"], index_col="order_id" )

usecols在文件较大时非常有用,只读需要的列,能明显减少内存占用和解析时间。我一般建议在项目里维护一个“字段白名单”,只用明确需要的字段,而不是全表读进来再 drop。

index_col则根据情况决定。如果文件里第一列是唯一 ID,可以设为索引;如果只是普通流水号,保留为普通列反而更方便。

关于索引有一个高频坑:写入时如果不处理索引,再读回来时索引会被当成一列数据。后面讲to_csv时会重点展开。

3. 读txt不是另一个函数,而是换分隔规则

3.1 txt文件最容易被误判的是分隔符

很多人以为读 txt 要用专门的函数,其实read_csv的核心能力就是处理分隔文本,txt 只是扩展名不同。真正要关心的不是扩展名,而是文件内部用什么分隔符

常见的 txt 格式:

# 制表符分隔 id<TAB>name<TAB>score 1<TAB>张三<TAB>89.5 # 空格分隔 id name score 1 张三 89.5 # 竖线分隔 id|name|score 1|张三|89.5

对应读取方式:

# 制表符 pd.read_csv("data.tsv", sep="\t") # 空格 pd.read_csv("data.txt", sep=r"\s+") # 连续多个空格也能处理 # 竖线 pd.read_csv("data.txt", sep="|")

3.2 常见txt格式与read_csv参数组合

我在处理日志类数据时,最常用的组合是:

df = pd.read_csv( "system_log.txt", sep=r"\s+", header=None, names=["timestamp", "level", "module", "message"], encoding="utf-8", skiprows=1, on_bad_lines="warn" )

这个组合解决的是:日志文件没有规范表头、字段之间用空格或制表符混排、偶有坏行的问题。

如果 txt 文件特别大,比如几 GB 的日志,可以配合nrows先抽样读几行,确认格式后再决定完整读取策略:

df_preview = pd.read_csv("large_log.txt", sep="\t", nrows=20) print(df_preview.dtypes) print(df_preview.head())

3.3 文本文件读进来后的第一件事:类型确认

txt 文件因为没有标准结构,读进来之后“列类型全被猜错”的概率比 csv 更高。我最常遇到的场景是:时间字段变成 object,数字列里混入-NA,读进来变成 float 但业务上应该是 int。

所以我处理 txt 时有一个固定步骤:

  1. 先用nrows=10读一个预览。
  2. 检查每列dtype
  3. 发现有类型不符时,返回read_csvdtypeparse_datesna_values
  4. 确认类型无误后,再全量读取。

这个流程看起来多了一步,实际上能省掉大量后续清洗时间。读取阶段多花 30 秒,能少花后面 3 个小时处理脏类型。

4. to_csv不是保存文件,而是定义“下次怎么读”

4.1 写出前先把“谁读、怎么读”想清楚

很多人在read_csv上花了大量时间,但to_csv只是随手一写。等到下一次读取时才发现:索引被写成了一列、中文乱码、float 精度太多、空值变成空字符串。

写文件时需要想清楚的问题是:这个文件是给人看的,还是给程序读的?给程序读的,规则要严格;给人看的,格式要舒服。

4.2 to_csv关键参数,按重要程度排序

df.to_csv( "clean_data.csv", encoding="utf-8-sig", # 避免 Excel 打开中文乱码 index=False, # 不把索引写入文件 columns=["order_id", "order_date", "city", "amount"], float_format="%.2f", # 控制浮点精度 na_rep="NULL", # 空值写为什么 sep="," )
参数作用典型场景
index是否写入索引默认 True,通常建议 False
encoding写出编码Excel 打开用utf-8-sig
columns只写出指定列避免输出多余列
float_format浮点格式金额保留两位小数
na_rep缺失值的表示默认空字符串
sep分隔符默认逗号
header是否写表头追加数据时可能写 False

其中最容易出问题的是index

如果你不写index=False,文件会多出一列无名的索引。下次读的时候,Pandas 会把它当成普通列读进来,然后你的 DataFrame 又多出一列叫Unnamed: 0的东西。要修复就得df = df.drop(columns=["Unnamed: 0"])

与其每次写文件后清理,不如从一开始写文件时就index=False

4.3 写txt文件的两种常见做法

要写出 txt 格式,本质是改变分隔符:

# 写出制表符分隔的 txt df.to_csv("data.txt", sep="\t", index=False) # 写出竖线分隔的 txt df.to_csv("data_export.txt", sep="|", index=False)

没有单独的to_txt函数,to_csv就是通用的分隔文本写出方法。

4.4 每次写出后都做一次“回读验证”

这是我最想强调的习惯。不管是清洗后的数据,还是从数据库导出的数据,写出文件后,都要立刻用read_csv读回来做一次验证:

df_check = pd.read_csv("clean_data.csv", encoding="utf-8-sig") assert df_check.shape == df.shape, f"Shape mismatch: {df_check.shape} vs {df.shape}" assert list(df_check.columns) == list(df.columns), "Columns changed!"

验证的维度包括:

  • 列数是否一致。
  • 行数是否一致。
  • 关键列的类型是否符合预期。
  • 空值数量是否合理。
  • 数值列的最大最小值是否还在正常范围。

这套断言放在数据处理任务最后,能拦截掉绝大多数的“写出后默默丢数据”问题。

5. 实战:从原始csv到干净DataFrame,再安全写回

5.1 实战数据与目标

假设有一个sales_data.csv,格式约 3000 行,包含以下字段:

order_id,order_date,city,category,amount,note 1001,2024-01-05,上海,数码,2599.00, 1002,2024/1/6,北京,服饰,199.99,会员订单 ...

目标:读成干净的 DataFrame,日期统一成 datetime 类型,金额为 float,没有空值干扰,然后写回sales_clean.csv

5.2 第一步:小样本侦察,不猜结构

先不急着全量读取,用nrows看前几行:

import pandas as pd df_preview = pd.read_csv("sales_data.csv", nrows=10) print(df_preview.head()) print(df_preview.dtypes)

这一步能快速发现三件事:分隔符对不对、编码对不对、表头是否存在。

如果发现order_date列混杂2024-01-052024/1/6两种格式,这就是日期解析需要特别注意的信号。

5.3 第二步:带参数读取,先清洗再分析

根据侦察结果,完整读取:

df = pd.read_csv( "sales_data.csv", encoding="utf-8-sig", parse_dates=["order_date"], dtype={"order_id": str}, na_values=["", "NA", "NULL", "待定"], keep_default_na=True, usecols=["order_id", "order_date", "city", "category", "amount"], )

这里parse_dates会自动解析2024-01-052024/1/6两种格式,统一变成datetime64

order_id用字符串保留,避免转成数字后丢失前导零或变成科学计数法。

5.4 第三步:写出干净版本并回读

# 简单清洗:去除金额为空的记录,排序 df_clean = df.dropna(subset=["amount"]).sort_values("order_date") # 写出,不写索引,金额保留两位小数 df_clean.to_csv( "sales_clean.csv", encoding="utf-8-sig", index=False, float_format="%.2f" ) # 回读验证 df_check = pd.read_csv("sales_clean.csv", encoding="utf-8-sig", parse_dates=["order_date"]) assert df_check.shape == df_clean.shape, "Shape mismatch!" assert str(df_check["order_date"].dtype) == "datetime64[ns]", "Date type changed!" print("验证通过")

5.5 如果数据量很大,先分块看再决定

如果文件有几百 MB 或几个 GB,一次read_csv可能是可行的,但你要清楚内存占用。常见做法是分批读取:

chunk_iter = pd.read_csv("large_file.csv", chunksize=10000)

每批是一个 DataFrame,可以做局部清洗,再合并或写回。分块处理的核心价值不只是省内存,而是让每一步都能看到中间结果,避免一个大文件读进来后才发现整列类型错了。

注意:不要把文件一次性全部读进内存后再慢慢处理。先用nrows确认结构,再用chunksize分批处理,这是处理大文件的固定策略。

6. 遇到报错先别搜答案,按这条链路排查

6.1 按“报错类型”定位问题层级

Pandas 读写文件的报错其实很有规律。我总结了一个排查链路:

  1. FileNotFoundError:先看路径,相对路径基于当前工作目录,不是脚本所在目录。
  2. UnicodeDecodeError:编码问题,换encoding="utf-8"gbkutf-8-sig逐个试。
  3. ParserError:分隔符或引号规则问题,检查sep是否正确。
  4. 不报错但列数变成 1:分隔符识别失败,文件可能不是逗号分隔。
  5. 不报错但dtype全变成 object:类型推断失败,说明列里有脏值,配合na_valuesdtype修正。
  6. 多出Unnamed: 0列:之前to_csv写了索引,需要index=False

6.2 三个高发问题的排查过程

问题一:中文乱码。

第一步,判断文件是 GBK 还是 UTF-8。可以用记事本打开看,或者用 Python 尝试解码:

with open("data.csv", "rb") as f: raw = f.read() for enc in ["utf-8", "gbk", "utf-8-sig"]: try: print(enc, raw[:50].decode(enc)) break except UnicodeDecodeError: continue

第二步,对应修改read_csvencoding参数。

问题二:日期列是字符串。

第一步,确认这列里有没有多种日期格式。统一格式的,直接用parse_dates即可;不统一的,先用pd.to_datetimeerrors="coerce"清洗:

df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")

问题三:数值列里有特殊字符导致整列变 object。

第一步,用df["amount"].astype(str).str.strip()查看特殊值。第二步,决定是替换、删除还是保留。这类问题不要在读取后再逐个处理,而是回到read_csvna_valuesdtype统一解决。

6.3 什么时候不该用Pandas读文件

read_csv很强大,但不是所有文本文件都适合直接交给它。

  • 文件是 JSON 嵌套结构:用pd.read_jsonjson模块先展开。
  • 文件是 Excel:用pd.read_excel,需要openpyxlxlrd
  • 文件是超大非结构化日志:先考虑逐行读取,或者用更合适的数据处理框架。
  • 文件是数据库导出而非常规表格:先用数据库客户端确认导出格式,再决定读取方式。

read_csv应用到不适合的场景,不是工具的问题,是选型问题。

7. 把读写文件沉淀成一个固定套路

7.1 “侦察-结构化-验证”三步法

回顾这些内容,最后沉淀成一套可以复用的流程:

第一步,侦察。

nrows=5nrows=10先读几行,搞清楚:分隔符、编码、表头、列类型、脏值。不猜,用代码看。

第二步,结构化。

根据侦察结果,明确四层参数:文件层(编码、引擎)、解析层(分隔符、表头、跳过行)、类型层(dtype、日期、空值)、结构层(列选择、索引)。写成一个可重复执行的读取代码块。

第三步,验证。

读取完成后,立刻做三件事:看df.info()确认类型;看df.head()确认数据内容;看df.isna().sum()确认空值分布。写文件后,立刻回读验证形状和类型。

这个三步法在单个文件、批量文件、每日定时任务、甚至从数据库导出 CSV 的场景里都适用。

7.2 适合什么场景,不适合什么场景

这套方法适合:

  • 从业务系统导出的 CSV、txt 表格数据。
  • 需要把多个表合并、清洗、再导出给下游的场景。
  • 需要在自动化脚本里稳定读取固定格式文件的场景。
  • 初学者学习 Pandas 数据接入的入门阶段。

不适合:

  • 需要处理几十 GB 以上的超大数据集,先考虑分块或分布式方案。
  • 数据是高度嵌套的 JSON、XML,先做格式转换。
  • 对性能要求极高的实时流式处理,Pandas 读写本身不是为流式设计的。

7.3 一次读写,两个时间点

最后说一个长期经验。

读写文件这件事,真正的成本不在“读写”发生的那一刻,而在两个时间点:读之前对文件结构的判断,和写之后对数据结果的验证。

判断错了,后面清洗全是补救。验证少了,脏数据会在更下游的分析里悄悄污染结论。

所以,不要把read_csvto_csv当成“打开”和“保存”。把它们当成数据工作流的两道闸门。每次过闸时,多确认一次类型,多看一眼空值,多跑一行断言。长期下来,你能少替下游同学背很多锅。

Pandas 的读写函数看起来是入门知识,但真正能稳定处理真实脏数据、能保证文件写出后还能原样读回的人,往往已经踩过足够多的坑。希望这篇文章,能让你在这些坑面前少走一次弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询