250万条客户数据姓名匹配性能优化:替换fuzz.token_set_ratio以缩短执行时间至1-2分钟的方案咨询
优化大规模姓名模糊匹配的方案
你的问题核心在于全量交叉连接带来的数据爆炸加上逐行调用模糊匹配的低效计算——250万条客户记录和待匹配姓名做交叉连接后,数据量会是两者的乘积,再逐行计算fuzz.token_set_ratio,耗时自然居高不下。下面是几个能把时间压缩到1-2分钟内的可行方案:
1. 用RapidFuzz替代FuzzyWuzzy,实现批量向量化匹配
FuzzyWuzzy是纯Python实现,速度较慢;而RapidFuzz是它的C++重写版本,速度能提升几十倍,还支持批量匹配,完全不需要做全量交叉连接。
示例代码:
from rapidfuzz import process, fuzz import pandas as pd import time # 待匹配姓名DataFrame(df1)和客户数据库(cust_2) # 先提取客户姓名列表(假设用FIRST_NAME列) cust_names = cust_2["FIRST_NAME"].tolist() st = time.time() # 对每个待匹配姓名,批量匹配前N个最相似的客户姓名 match_results = [] for idx, row in df1.iterrows(): target_name = row["SDN_NAME_SERACH"] # 取相似度最高的10个结果(可根据需求调整),用token_set_ratio算法 matches = process.extract( target_name, cust_names, scorer=fuzz.token_set_ratio, limit=10 ) # 整理结果:待匹配编号、待匹配姓名、匹配客户姓名、相似度 for name, score, cust_idx in matches: match_results.append({ "s_no": row["s_no"], "SDN_NAME_SERACH": target_name, "MATCHED_FIRST_NAME": name, "SIMILARITY_SCORE": score, "CUST_INDEX": cust_idx }) # 转成DataFrame final_df = pd.DataFrame(match_results) print(f"匹配耗时: {time.time() - st:.2f} 秒")
这样做的好处是:不需要生成交叉连接的巨量DataFrame,而是直接对每个待匹配姓名批量搜索相似项,计算量大幅减少,速度提升非常明显。
2. 预处理+候选过滤,减少需要匹配的范围
如果待匹配姓名数量较多,还可以先通过预提取特征+近似索引过滤出可能匹配的候选,再做精确的模糊匹配,进一步减少计算量。
具体步骤:
- 预处理姓名:统一转小写、去除空格/特殊字符、拆分姓名成关键词(比如把"John Doe"拆成["john", "doe"])。
- 构建倒排索引:把客户姓名的关键词映射到对应的客户ID/姓名,比如用字典存储
{关键词: [客户姓名列表]}。 - 候选过滤:对待匹配姓名的关键词,先从倒排索引中找出包含至少一个共同关键词的客户姓名,只对这些候选做模糊匹配,避免全量遍历。
示例代码:
import pandas as pd from rapidfuzz import process, fuzz import time from collections import defaultdict # 预处理函数:清理姓名并拆分成关键词 def preprocess_name(name): if pd.isna(name): return [] # 转小写、去特殊字符、拆分 cleaned = name.lower().replace(".", "").replace(",", "").strip() return cleaned.split() # 预处理客户姓名,构建倒排索引 cust_2["KEYWORDS"] = cust_2["FIRST_NAME"].apply(preprocess_name) inverted_index = defaultdict(list) for idx, row in cust_2.iterrows(): for keyword in row["KEYWORDS"]: inverted_index[keyword].append((idx, row["FIRST_NAME"])) # 去重候选(避免同一个客户被多次添加) def get_candidates(target_name): target_keywords = preprocess_name(target_name) candidates = set() for kw in target_keywords: if kw in inverted_index: for cust_idx, name in inverted_index[kw]: candidates.add((cust_idx, name)) return [name for _, name in candidates] if candidates else cust_2["FIRST_NAME"].tolist() st = time.time() match_results = [] for idx, row in df1.iterrows(): target_name = row["SDN_NAME_SERACH"] # 获取过滤后的候选集 candidates = get_candidates(target_name) # 对候选集做模糊匹配 matches = process.extract( target_name, candidates, scorer=fuzz.token_set_ratio, limit=10 ) # 整理结果 for name, score, _ in matches: match_results.append({ "s_no": row["s_no"], "SDN_NAME_SERACH": target_name, "MATCHED_FIRST_NAME": name, "SIMILARITY_SCORE": score }) final_df = pd.DataFrame(match_results) print(f"匹配耗时: {time.time() - st:.2f} 秒")
通过这种方式,能把需要匹配的候选集从250万缩减到几百甚至几十条,计算效率会再上一个台阶。
3. 并行计算进一步加速
模糊匹配是CPU密集型任务,可以用多进程/多线程来并行处理多个待匹配姓名,充分利用多核CPU的性能。
示例代码(用concurrent.futures实现多进程):
from concurrent.futures import ProcessPoolExecutor import pandas as pd from rapidfuzz import process, fuzz import time # 定义单条姓名的匹配函数 def match_single_name(row): target_name = row["SDN_NAME_SERACH"] matches = process.extract( target_name, cust_names, scorer=fuzz.token_set_ratio, limit=10 ) return [{ "s_no": row["s_no"], "SDN_NAME_SERACH": target_name, "MATCHED_FIRST_NAME": name, "SIMILARITY_SCORE": score, "CUST_INDEX": cust_idx } for name, score, cust_idx in matches] # 准备数据 cust_names = cust_2["FIRST_NAME"].tolist() rows = [row for _, row in df1.iterrows()] st = time.time() # 多进程并行处理,进程数设为CPU核心数 with ProcessPoolExecutor() as executor: results = executor.map(match_single_name, rows) # 合并结果 match_results = [] for res in results: match_results.extend(res) final_df = pd.DataFrame(match_results) print(f"匹配耗时: {time.time() - st:.2f} 秒")
注意:如果待匹配姓名数量较少(比如个位数),并行的收益可能不明显;但如果数量较多(比如几十上百条),并行能显著缩短时间。
总结
优先尝试RapidFuzz批量匹配,这是最容易实现且效果明显的优化;如果还不够快,再加上候选过滤缩小匹配范围;最后用并行计算榨干CPU性能。这三者结合下来,把耗时控制在1-2分钟内完全没问题。
内容的提问来源于stack exchange,提问作者user16351455
相关产品推荐
相关产品推荐

