CSV 去重之前,先说清楚“同一条记录”
一张客户或订单表里出现重复行,最容易想到的操作是“去重”。但完全相同的行、编号相同而其他字段不同的行,以及编号缺失的行,并不是一种情况。把它们一起删掉,很可能得到一张看似干净、实际上丢了信息的表。
下面用 Python 的标准库说明一种保守做法:按业务编号分组,保留第一次出现的完整记录,把所有重复和缺号记录另存为可核对的报告。
一、先定义唯一键
假设 CSV 中有 order_id 、 amount 、 customer 三列。这里选择 order_id 作为唯一键,不代表金额相同就是同一订单,也不代表同一个客户只会有一笔订单。唯一键必须来自业务含义。
编号中的前后空格是否忽略,也要明确。示例会去掉前后空格,但保留编号里的零和字母大小写。不要先把编号转成整数: 0012 和 12 是否相同,不能交给类型转换替你决定。
二、把去重与报告同时输出
import csv
from pathlib import Path
source = Path("orders.csv")
clean = Path("orders_unique.csv")
report = Path("orders_conflicts.csv")
with source.open("r", encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
fields = reader.fieldnames
if not fields or "order_id" not in fields:
raise ValueError("缺少 order_id 列")
if len(fields) != len(set(fields)):
raise ValueError("表头存在重复列名")
seen = {}
accepted = []
rejected = []
for line, row in enumerate(reader, start=2):
key = (row.get("order_id") or "").strip()
if not key:
rejected.append({**row, "line": line,
"reason": "编号为空"})
elif key in seen:
rejected.append({**row, "line": line,
"reason": f"编号重复,首次位于第 {seen[key]} 行"})
else:
seen[key] = line
accepted.append(row)
for target, columns, rows in [
(clean, fields, accepted),
(report, fields + ["line", "reason"], rejected),
]:
with target.open("w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(rows)
示例输出到两个新文件,不覆盖原始文件。报告里有原始字段、行号和原因,后续核对时不必只靠记忆寻找那条数据。
三、为什么不直接保留最后一行
最后一行可能是更新后的数据,也可能只是一次错误粘贴。如果 CSV 没有可靠的更新时间字段,按行顺序推断“最新”是不稳妥的。示例采用保留第一次的策略,只是为了让规则简单可复现;正式场景可以根据更新时间、来源可信度或人工确认改成另一套规则。
金额不同的重复编号值得单独关注。它往往需要核对,而不是自动合并。可以在报告中再添加“与首条记录有哪些字段不同”,让后续处理更快。
四、验收不只看文件能否打开
检查输入行数是否等于保留行数与报告行数之和;确认编号缺失的记录没有混进正常结果;挑一条重复编号,核对报告指向的首次行号是否正确。如果源文件还存在列数不齐、异常编码或超大文件,应该先增加相应校验,再把脚本用于整批数据。
这个示例把记录暂存在内存,适合能放入内存的小文件。处理更大的文件可以改为流式写入,但用于记住唯一键的集合仍会占用内存,需要根据数据规模重新设计。
去重不是让行数越少越好,而是让每一次删减都有可解释的依据。原文件、唯一键规则和冲突报告一起保留,才方便以后复查。


浙公网安备 33010602011771号