影刀RPA 数据去重的七种方法:从简单到复杂的选型
作者:林焱
什么情况用这个
你从三个平台采集了商品数据,合并后发现有重复。Excel里的订单记录导入了两次,客户名单里有同名同姓不同人、也有同一人多条记录。
数据去重听起来简单——不就 drop_duplicates() 嘛?实际远不止。不同场景需要不同的去重策略:有时候全字段相同才是重复,有时候只要ID相同就算重复;有时候要保留第一条,有时候要合并两条记录的信息。这篇文章从简单到复杂,把七种去重方法的使用场景和选择逻辑讲清楚。
怎么做
方法一:集合去重(最简单)
适合:单列数据、全字段重复的场景。
# 字符串列表去重
names = ["张三", "李四", "张三", "王五", "李四"]
unique_names = list(set(names))
print(unique_names) # ['张三', '李四', '王五'](顺序不保证)
# 保持原顺序的去重
seen = set()
unique_ordered = []
for name in names:
if name not in seen:
seen.add(name)
unique_ordered.append(name)
print(unique_ordered) # ['张三', '李四', '王五']
方法二:字典键去重(按关键字段)
适合:多个字段中只有某个字段决定是否重复(如ID)。
records = [
{"id": "001", "name": "商品A", "price": 99},
{"id": "002", "name": "商品B", "price": 199},
{"id": "001", "name": "商品A(新)", "price": 109}, # 同ID,信息更新了
]
# 按ID去重,保留第一次出现的
seen = {}
for record in records:
key = record["id"]
[video(video-owL94VaT-1784132827886)(type-csdn)(url-https://live.csdn.net/v/embed/525010)(image-https://v-blog.csdnimg.cn/asset/f4faa587144cb7070f19e8b36813806b/cover/Cover0.jpg)(title-店群矩阵自动化突破运营极限!)]
if key not in seen:
seen[key] = record
unique_records = list(seen.values())
# 如果需要保留最后出现的(更新的数据)
for record in records:
seen[record["id"]] = record # 直接覆盖
方法三:多字段组合键去重
适合:需要多个字段组合才能判断重复。
# 按"姓名+手机号"组合去重
contacts = [
{"name": "张三", "phone": "13800138000", "email": "a@x.com"},
{"name": "张三", "phone": "13900139000", "email": "b@x.com"}, # 同名人不同手机
{"name": "张三", "phone": "13800138000", "email": "c@x.com"}, # 重复(同名同手机)
]
def dedup_by_keys(records, keys):
"""按多个字段组合去重"""
seen = set()
result = []
for record in records:

# 生成组合键
composite_key = tuple(record.get(k, "") for k in keys)
if composite_key not in seen:
seen.add(composite_key)
result.append(record)
return result
unique = dedup_by_keys(contacts, ["name", "phone"])
# 结果:张三+138... 和 张三+139... 两条
方法四:模糊匹配去重
适合:文本不完全一样但基本是同一内容的场景。
import difflib
def fuzzy_dedup(strings, threshold=0.85):
"""模糊去重——相似度超过阈值的视为重复"""
unique = []
for s in strings:
is_dup = False
for existing in unique:
similarity = difflib.SequenceMatcher(None, s, existing).ratio()
if similarity >= threshold:
is_dup = True
break
if not is_dup:
unique.append(s)
return unique
# 示例:公司名稍有不同的记录
names = [
"深圳市腾讯计算机系统有限公司",
"深圳腾讯计算机系统有限公司", # 少了"市"
"深圳市腾讯科技公司", # 不同
]
unique_names = fuzzy_dedup(names, threshold=0.8)
print(unique_names) # 前两条会去重
注意:模糊匹配去重计算量大,数据量>1000条时建议先做精确去重缩小范围。
方法五:合并去重(取并集)
适合:两条重复记录互补的信息要合并。
def merge_duplicates(records, key_field):
"""合并重复记录——取所有字段的非空并集"""
merged = {}
for record in records:
key = record[key_field]
if key not in merged:
merged[key] = record.copy()
else:
# 合并非空字段
existing = merged[key]
for field, value in record.items():
if value and not existing.get(field):
existing[field] = value # 用新值填充空字段
return list(merged.values())
# 示例
records = [
{"id": "001", "name": "张三", "phone": "13800138000", "email": ""},
{"id": "002", "name": "李四", "phone": "", "email": "lisi@qq.com"},
{"id": "001", "name": "张三", "phone": "", "email": "zhangsan@qq.com"},
]
merged = merge_duplicates(records, "id")
# 结果:张三的信息现在有完整的phone和email
方法六:时间优先去重
适合:同一ID有多条历史记录,保留最新的。
from datetime import datetime
def keep_latest(records, key_field, time_field):
"""按关键字段去重,保留时间最新的"""
latest = {}
for record in records:
key = record[key_field]
record_time = record.get(time_field)
# 解析时间
if isinstance(record_time, str):
try:
record_time = datetime.strptime(record_time, "%Y-%m-%d %H:%M:%S")
except:
record_time = datetime.min
if key not in latest:
latest[key] = record
else:
existing_time = latest[key].get(time_field)
if isinstance(existing_time, str):
try:
existing_time = datetime.strptime(existing_time, "%Y-%m-%d %H:%M:%S")
except:
existing_time = datetime.min
if record_time > existing_time:
latest[key] = record
return list(latest.values())
方法七:权重优先去重
适合:多源数据去重,不同来源的可信度不同。
def keep_highest_quality(records, key_field, source_weights):
"""
按来源权重去重——保留可信度最高的
source_weights: {"官方数据库": 10, "用户提交": 3, "公开采集": 1}
"""
best = {}
for record in records:
key = record[key_field]
source = record.get("source", "未知")
weight = source_weights.get(source, 0)
if key not in best:
best[key] = (weight, record)
else:
existing_weight, _ = best[key]
if weight > existing_weight:
best[key] = (weight, record)
return [record for _, record in best.values()]
有什么坑
坑一:set去重后顺序乱了
现象:用 set() 去重后列表顺序和原来不一样了(Python 3.7+的dict保持插入顺序但set不保证)。
解决:保持顺序用 dict.fromkeys():
unique_ordered = list(dict.fromkeys(items))
坑二:忽略了None和空字符串的区别
现象:None 和 "" 被判定为不同的key,导致没去掉重复。
解决:生成组合键时统一规范化空值:
[video(video-tH9VCwRG-1784132834586)(type-csdn)(url-live.csdn.net/v/embed/524…)]
def normalize_key(value):
"""规范化键值——把各种空值统一"""
if value is None or str(value).strip() == "":
return "_EMPTY_"
return str(value).strip()
坑三:模糊去重在大数据集上超慢
现象:5000条数据做两两模糊比较,O(n²)复杂度,流程卡死。
解决:先精确去重,再按首字母或拼音分组缩小候选范围。或者用距离算法先聚类。
坑四:去重决策没有记录
现象:去重后不知道删了什么,出了问题没法回溯。
解决:记录去重日志:
def dedup_with_log(records, key_func):
"""带去重日志"""
seen = {}
duplicates = []
for record in records:
key = key_func(record)
if key in seen:
duplicates.append({"kept": seen[key], "removed": record})
else:
seen[key] = record
print(f"去重: {len(records)}→{len(seen)}条, 移除{len(duplicates)}条重复")
return list(seen.values()), duplicates
坑五:业务上的"重复"和技术上的"重复"不一致
现象:程序认为商品ID不同就不是重复,但业务上同一个商品在不同平台有不同ID。
解决:这种情况需要建立"商品映射表"——先做技术去重(按字段),再做业务匹配(按规则),两者分开处理。
总结:去重方法的选择顺序——先用集合/dict按关键字段精确去重(最快),再根据业务需求选择保留策略(第一条、最新、合并、权重),最后才考虑模糊匹配(最慢)。大部分场景用方法一、二、三就够了。每做一次去重,把去重原因和数量记到日志里。