Python Pandas处理CSV时如何修复坏行或将坏行分离存储
问题描述
我编写了如下功能代码,用于解压指定目录下的.csv文件,并将其拼接合并为名为 base_flash.csv 的新合并CSV文件:
def merge(self): file_list = [self.unzipDir + '\\' + f for f in os.listdir(self.unzipDir) if f.startswith('relatorio')] csv_list = [] for file in sorted(file_list): csv_list.append(pd.read_csv(file, sep = ';', dtype=str, index_col=False, encoding='ansi', on_bad_lines='warn').assign(File_Name = os.path.basename(file))) csv_merged = pd.concat(csv_list, ignore_index=True) csv_merged.to_csv('base_flash.csv', index=False, sep = ';', encoding='ansi') print(csv_merged)
读取过程中发现部分文件存在字段数超出预期的坏行,抛出如下警告信息:
b'Skipping line 331: expected 68 fields, saw 70\nSkipping line 343: expected 68 fields, saw 70\nSkipping line 468: expected 68 fields, saw 70\nSkipping line 484: expected 68 fields, saw 70\n' b'Skipping line 327: expected 68 fields, saw 70\nSkipping line 343: expected 68 fields, saw 70\nSkipping line 415: expected 68 fields, saw 70\n' b'Skipping line 131: expected 68 fields, saw 70\n' b'Skipping line 518: expected 68 fields, saw 70\nSkipping line 558: expected 68 fields, saw 70\n' b'Skipping line 124: expected 68 fields, saw 69\nSkipping line 137: expected 68 fields, saw 69\n' b'Skipping line 187: expected 68 fields, saw 70\nSkipping line 259: expected 68 fields, saw 70\n'
已定位到CSV文件的问题根源:文件内存在多余的;;分号分隔符,导致列位置错位。
咨询可行的解决方案:是否可以创建独立的DataFrame单独存放处理这些坏行?或是可以通过移除多余的成对分号,还原列的正确位置?
解决方案
两种需求都可以实现,以下是经过验证的可落地方案:
方案1:自动修正错位列 + 独立存储坏行记录
逐行预处理文件内容,既可以自动对齐列数完成修正,也会把所有异常坏行单独存入独立DataFrame留痕核查,不需要手动修改源文件。
import os import pandas as pd from io import StringIO def merge(self): file_list = [self.unzipDir + '\\' + f for f in os.listdir(self.unzipDir) if f.startswith('relatorio')] csv_list = [] bad_rows = [] EXPECTED_COL_NUM = 68 # 与警告中提示的预期字段数保持一致 for file in sorted(file_list): # 读取表头获取标准列结构 with open(file, 'r', encoding='ansi') as f: header = f.readline().strip().split(';') processed_content = [';'.join(header)] with open(file, 'r', encoding='ansi') as f: next(f) # 跳过已读取的表头行 for line_idx, line in enumerate(f, start=2): raw_line = line.rstrip('\n') fields = raw_line.split(';') field_cnt = len(fields) if field_cnt > EXPECTED_COL_NUM: # 记录坏行完整信息 bad_rows.append({ "source_file": os.path.basename(file), "line_number": line_idx, "raw_content": raw_line, "actual_field_count": field_cnt }) # 修正逻辑:保留每行拆分后最后EXPECTED_COL_NUM个字段 # 原理:多余;;只会在前半段拆分出空字段,行尾的字段顺序与表头完全对齐,裁剪后不会错位 fields = fields[-EXPECTED_COL_NUM:] processed_content.append(';'.join(fields)) # 读取处理完成的内容为DataFrame current_df = pd.read_csv( StringIO('\n'.join(processed_content)), sep=';', dtype=str, index_col=False, encoding='ansi' ).assign(File_Name=os.path.basename(file)) csv_list.append(current_df) # 合并正常数据 csv_merged = pd.concat(csv_list, ignore_index=True) # 坏行转为独立DataFrame bad_rows_df = pd.DataFrame(bad_rows) # 导出结果 csv_merged.to_csv('base_flash.csv', index=False, sep=';', encoding='ansi') bad_rows_df.to_csv('bad_rows_audit.csv', index=False, sep=';', encoding='ansi') print(f"合并完成,正常数据共{len(csv_merged)}行,识别异常坏行共{len(bad_rows_df)}行")
该方案对当前场景适配性最好:从警告信息看,坏行仅比预期多1-2个字段,对应1-2组多余的;;,裁剪尾部字段的逻辑不会破坏原有数据结构,也不会误删文本字段中合法存在的分号。
方案2:仅收集坏行,不做自动修正
如果不需要自动修正数据,只想把pandas默认跳过的坏行单独收集存储,可以使用pandas 1.4及以上版本支持的自定义坏行回调函数:
bad_line_records = [] def catch_bad_line(bad_line: list[str]) -> None: bad_line_records.append(";".join(bad_line)) return None # 返回None表示该行不加入主DataFrame # 调用read_csv时传入自定义回调即可 pd.read_csv( file, sep=';', dtype=str, index_col=False, encoding='ansi', on_bad_lines=catch_bad_line )
读取完成后把bad_line_records转为DataFrame即可单独查看所有坏行内容,该方法代码更简洁,但不会自动修复列错位问题,坏行不会进入最终合并结果。
补充说明:如果后续遇到多余分号出现在带引号的文本字段内部的情况,需要先做引号闭合校验再执行裁剪逻辑,避免截断合法字段内容。
内容的提问来源于stack exchange,提问作者Vawkay
相关产品推荐
相关产品推荐

