尧图精选

Pandas读写CSV与txt文件实战指南:从参数详解到踩坑避坑

🕒 发布时间:2026/9/2 12:50:26 📁 来源:尧图网络
上周处理一批电商订单数据时我在一个最常见的操作上连续踩了好几个坑pd.read_csv()读文件先报编码错误改成utf-8后中文正常了但日期列变成了字符串处理完日期又发现有两行数据因为字段里多了逗号被拆成了多余列最后好不容易清洗完用to_csv写出去再读回来索引又被当成普通列带进了表里。整个过程像一趟寻宝之旅每一步都以为解决了下一层问题马上补上。这件事让我重新想了一个问题Pandas 读写 txt 和 csv 文件的真正难点到底在哪里它显然不在“读取”和“保存”这两个动作上。动作只是一个函数调用。真正麻烦的是从“磁盘上的纯文本”到“内存里有类型的 DataFrame”之间的那层映射关系。文件本身没有列名、没有类型、没有空值概念所有结构化信息都要靠参数告诉 Pandas。参数用对了一次读取就是一次干净转换参数用错了后面所有分析都建立在一堆脏数据上。这篇文章我会把read_csv、to_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当成一个黑盒函数直接调而是先做三个判断文件的分隔符是什么逗号、制表符、空格还是竖线。文件有没有表头第一行是列名还是数据。每列的类型是否符合我的预期日期要用parse_dates身份证号、手机号要用字符串而不是数字缺失值要统一指定。这三件事确认清楚了read_csv才真正变成“数据接入工具”而不只是“文件打开工具”。2. 把read_csv拆成四层才对得起它几十个参数read_csv参数很多初学者最容易犯的错是试图全部记住。我的建议是按数据进入 DataFrame 的顺序把参数分成四个层级文件层、解析层、类型层、结构层。排查问题时也按这个顺序查文件打不开看文件层数据列数不对看解析层列类型不对看类型层索引和列顺序不对看结构层。2.1 最小可运行读取流程先给一个最常见的完整示例后面再拆开解释import pandas as pd df pd.read_csv( sales_data.csv, encodingutf-8, dtype{phone: str}, parse_dates[order_date], na_values[, NULL, NA], usecols[order_id, order_date, city, amount, phone], index_col0, )这段代码展示了四层参数的一次集中应用。下面逐层拆开。2.2 第一层文件读取层先解决“能不能打开”文件读取层决定的是文件在哪里、用什么编码打开、用什么引擎解析。参数常见取值作用filepath_or_buffer本地路径、URL、文件对象数据来源encodingutf-8、gbk、utf-8-sig文本编码enginec、python底层解析引擎编码是最容易踩坑的地方。国内很多系统导出的 CSV 是 GBK 编码直接用默认read_csv会报UnicodeDecodeError。这时候要改成df pd.read_csv(download.csv, encodinggbk)还有一个很容易忽视的坑UTF-8 文件带 BOM 头时列名会变成\ufeff列名。解决办法是使用df pd.read_csv(download.csv, encodingutf-8-sig)utf-8-sig会自动把 BOM 头去掉这也是“读下来列名很怪”时最先要检查的点。引擎参数实际读文件时c引擎更快python引擎更灵活。如果遇到非常规分隔符、复杂引号规则c引擎解析不了时可以试试enginepython。但不要默认使用 python 引擎性能会差不少。2.3 第二层解析层解决“表长什么样”解析层决定文件内容怎么被切割成“行和列”。参数作用sep/delimiter分隔符默认是逗号header哪一行作为列名默认 0names手动指定列名skiprows跳过文件前几行skip_blank_lines是否跳过空行on_bad_lines遇到坏行时怎么处理最常见的需求是文件开头有几行说明文字真正表头在第 3 行。df pd.read_csv( report.txt, skiprows2, # 跳过前两行说明 header0, # 第三行作为表头 encodingutf-8-sig )如果文件完全没有表头需要自己指定列名df pd.read_csv( data_no_header.csv, headerNone, names[id, name, amount, date] )on_bad_lines是一个长期被低估的参数。真实数据里偶尔会有行因为多了一个逗号导致列数不匹配。旧版 Pandas 遇到这种情况会直接报错现在可以这样处理df pd.read_csv( messy_data.csv, on_bad_linesskip # 跳过坏行也可以先 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_naTrue )2.5 第四层结构层决定“哪列当索引、哪些列先用”结构层处理的是读进来之后DataFrame 的“骨架”长什么样。df pd.read_csv( sales_data.csv, usecols[order_id, order_date, city, amount], index_colorder_id )usecols在文件较大时非常有用只读需要的列能明显减少内存占用和解析时间。我一般建议在项目里维护一个“字段白名单”只用明确需要的字段而不是全表读进来再 drop。index_col则根据情况决定。如果文件里第一列是唯一 ID可以设为索引如果只是普通流水号保留为普通列反而更方便。关于索引有一个高频坑写入时如果不处理索引再读回来时索引会被当成一列数据。后面讲to_csv时会重点展开。3. 读txt不是另一个函数而是换分隔规则3.1 txt文件最容易被误判的是分隔符很多人以为读 txt 要用专门的函数其实read_csv的核心能力就是处理分隔文本txt 只是扩展名不同。真正要关心的不是扩展名而是文件内部用什么分隔符。常见的 txt 格式# 制表符分隔 idTABnameTABscore 1TAB张三TAB89.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, sepr\s) # 连续多个空格也能处理 # 竖线 pd.read_csv(data.txt, sep|)3.2 常见txt格式与read_csv参数组合我在处理日志类数据时最常用的组合是df pd.read_csv( system_log.txt, sepr\s, headerNone, names[timestamp, level, module, message], encodingutf-8, skiprows1, on_bad_lineswarn )这个组合解决的是日志文件没有规范表头、字段之间用空格或制表符混排、偶有坏行的问题。如果 txt 文件特别大比如几 GB 的日志可以配合nrows先抽样读几行确认格式后再决定完整读取策略df_preview pd.read_csv(large_log.txt, sep\t, nrows20) print(df_preview.dtypes) print(df_preview.head())3.3 文本文件读进来后的第一件事类型确认txt 文件因为没有标准结构读进来之后“列类型全被猜错”的概率比 csv 更高。我最常遇到的场景是时间字段变成 object数字列里混入-或NA读进来变成 float 但业务上应该是 int。所以我处理 txt 时有一个固定步骤先用nrows10读一个预览。检查每列dtype。发现有类型不符时返回read_csv加dtype、parse_dates、na_values。确认类型无误后再全量读取。这个流程看起来多了一步实际上能省掉大量后续清洗时间。读取阶段多花 30 秒能少花后面 3 个小时处理脏类型。4. to_csv不是保存文件而是定义“下次怎么读”4.1 写出前先把“谁读、怎么读”想清楚很多人在read_csv上花了大量时间但to_csv只是随手一写。等到下一次读取时才发现索引被写成了一列、中文乱码、float 精度太多、空值变成空字符串。写文件时需要想清楚的问题是这个文件是给人看的还是给程序读的给程序读的规则要严格给人看的格式要舒服。4.2 to_csv关键参数按重要程度排序df.to_csv( clean_data.csv, encodingutf-8-sig, # 避免 Excel 打开中文乱码 indexFalse, # 不把索引写入文件 columns[order_id, order_date, city, amount], float_format%.2f, # 控制浮点精度 na_repNULL, # 空值写为什么 sep, )参数作用典型场景index是否写入索引默认 True通常建议 Falseencoding写出编码Excel 打开用utf-8-sigcolumns只写出指定列避免输出多余列float_format浮点格式金额保留两位小数na_rep缺失值的表示默认空字符串sep分隔符默认逗号header是否写表头追加数据时可能写 False其中最容易出问题的是index。如果你不写indexFalse文件会多出一列无名的索引。下次读的时候Pandas 会把它当成普通列读进来然后你的 DataFrame 又多出一列叫Unnamed: 0的东西。要修复就得df df.drop(columns[Unnamed: 0])。与其每次写文件后清理不如从一开始写文件时就indexFalse。4.3 写txt文件的两种常见做法要写出 txt 格式本质是改变分隔符# 写出制表符分隔的 txt df.to_csv(data.txt, sep\t, indexFalse) # 写出竖线分隔的 txt df.to_csv(data_export.txt, sep|, indexFalse)没有单独的to_txt函数to_csv就是通用的分隔文本写出方法。4.4 每次写出后都做一次“回读验证”这是我最想强调的习惯。不管是清洗后的数据还是从数据库导出的数据写出文件后都要立刻用read_csv读回来做一次验证df_check pd.read_csv(clean_data.csv, encodingutf-8-sig) assert df_check.shape df.shape, fShape 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, nrows10) print(df_preview.head()) print(df_preview.dtypes)这一步能快速发现三件事分隔符对不对、编码对不对、表头是否存在。如果发现order_date列混杂2024-01-05和2024/1/6两种格式这就是日期解析需要特别注意的信号。5.3 第二步带参数读取先清洗再分析根据侦察结果完整读取df pd.read_csv( sales_data.csv, encodingutf-8-sig, parse_dates[order_date], dtype{order_id: str}, na_values[, NA, NULL, 待定], keep_default_naTrue, usecols[order_id, order_date, city, category, amount], )这里parse_dates会自动解析2024-01-05和2024/1/6两种格式统一变成datetime64。order_id用字符串保留避免转成数字后丢失前导零或变成科学计数法。5.4 第三步写出干净版本并回读# 简单清洗去除金额为空的记录排序 df_clean df.dropna(subset[amount]).sort_values(order_date) # 写出不写索引金额保留两位小数 df_clean.to_csv( sales_clean.csv, encodingutf-8-sig, indexFalse, float_format%.2f ) # 回读验证 df_check pd.read_csv(sales_clean.csv, encodingutf-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, chunksize10000)每批是一个 DataFrame可以做局部清洗再合并或写回。分块处理的核心价值不只是省内存而是让每一步都能看到中间结果避免一个大文件读进来后才发现整列类型错了。注意不要把文件一次性全部读进内存后再慢慢处理。先用nrows确认结构再用chunksize分批处理这是处理大文件的固定策略。6. 遇到报错先别搜答案按这条链路排查6.1 按“报错类型”定位问题层级Pandas 读写文件的报错其实很有规律。我总结了一个排查链路报FileNotFoundError先看路径相对路径基于当前工作目录不是脚本所在目录。报UnicodeDecodeError编码问题换encodingutf-8、gbk、utf-8-sig逐个试。报ParserError分隔符或引号规则问题检查sep是否正确。不报错但列数变成 1分隔符识别失败文件可能不是逗号分隔。不报错但dtype全变成 object类型推断失败说明列里有脏值配合na_values或dtype修正。多出Unnamed: 0列之前to_csv写了索引需要indexFalse。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_csv的encoding参数。问题二日期列是字符串。第一步确认这列里有没有多种日期格式。统一格式的直接用parse_dates即可不统一的先用pd.to_datetime加errorscoerce清洗df[order_date] pd.to_datetime(df[order_date], errorscoerce)问题三数值列里有特殊字符导致整列变 object。第一步用df[amount].astype(str).str.strip()查看特殊值。第二步决定是替换、删除还是保留。这类问题不要在读取后再逐个处理而是回到read_csv的na_values或dtype统一解决。6.3 什么时候不该用Pandas读文件read_csv很强大但不是所有文本文件都适合直接交给它。文件是 JSON 嵌套结构用pd.read_json或json模块先展开。文件是 Excel用pd.read_excel需要openpyxl或xlrd。文件是超大非结构化日志先考虑逐行读取或者用更合适的数据处理框架。文件是数据库导出而非常规表格先用数据库客户端确认导出格式再决定读取方式。把read_csv应用到不适合的场景不是工具的问题是选型问题。7. 把读写文件沉淀成一个固定套路7.1 “侦察-结构化-验证”三步法回顾这些内容最后沉淀成一套可以复用的流程第一步侦察。用nrows5或nrows10先读几行搞清楚分隔符、编码、表头、列类型、脏值。不猜用代码看。第二步结构化。根据侦察结果明确四层参数文件层编码、引擎、解析层分隔符、表头、跳过行、类型层dtype、日期、空值、结构层列选择、索引。写成一个可重复执行的读取代码块。第三步验证。读取完成后立刻做三件事看df.info()确认类型看df.head()确认数据内容看df.isna().sum()确认空值分布。写文件后立刻回读验证形状和类型。这个三步法在单个文件、批量文件、每日定时任务、甚至从数据库导出 CSV 的场景里都适用。7.2 适合什么场景不适合什么场景这套方法适合从业务系统导出的 CSV、txt 表格数据。需要把多个表合并、清洗、再导出给下游的场景。需要在自动化脚本里稳定读取固定格式文件的场景。初学者学习 Pandas 数据接入的入门阶段。不适合需要处理几十 GB 以上的超大数据集先考虑分块或分布式方案。数据是高度嵌套的 JSON、XML先做格式转换。对性能要求极高的实时流式处理Pandas 读写本身不是为流式设计的。7.3 一次读写两个时间点最后说一个长期经验。读写文件这件事真正的成本不在“读写”发生的那一刻而在两个时间点读之前对文件结构的判断和写之后对数据结果的验证。判断错了后面清洗全是补救。验证少了脏数据会在更下游的分析里悄悄污染结论。所以不要把read_csv和to_csv当成“打开”和“保存”。把它们当成数据工作流的两道闸门。每次过闸时多确认一次类型多看一眼空值多跑一行断言。长期下来你能少替下游同学背很多锅。Pandas 的读写函数看起来是入门知识但真正能稳定处理真实脏数据、能保证文件写出后还能原样读回的人往往已经踩过足够多的坑。希望这篇文章能让你在这些坑面前少走一次弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →