Python Fuzzywuzzy处理百万级Excel数据匹配过慢的优化求助
百万级Excel数据模糊匹配优化方案
问题背景
用户拥有一个50MB以上的.xlsx文件,包含Sheet A和Sheet B两个工作表,Sheet A存有数百万条记录,需要将Sheet A的「Entity Name」列与Sheet B的7个「name」列做模糊匹配。原Python脚本运行超20小时仍无结果,需优化运行速度并输出指定格式的匹配结果。
原代码核心性能瓶颈
- 遍历效率极差:用嵌套列表推导式对每个A列元素遍历所有B列去重元素,时间复杂度为O(M*N)(M是A列行数,N是B列去重后数据量),百万级数据下完全无法承受。
- 未充分利用数据源:原代码仅提取Sheet B第一个「name」列的去重值,忽略了另外6个「name」列的数据,导致匹配数据源不完整。
- 模糊匹配库效率低:fuzzywuzzy的纯Python实现对大规模数据计算速度慢,未使用底层优化版本。
针对性优化方案
1. 完整提取Sheet B的所有name列并去重
先把Sheet B中7个name列全部提取合并,生成无重复的匹配数据源,确保匹配范围完整的同时减少重复计算:
# 筛选Sheet B中所有名为name的列 all_name_cols = [col for col in dataframe2.columns if col == 'name'] # 合并所有name列、剔除空值并去重 B_cleaned = dataframe2[all_name_cols].stack().dropna().unique().tolist()
2. 替换Fuzzywuzzy为RapidFuzz
RapidFuzz是Fuzzywuzzy的C++重写版本,计算速度提升10-100倍,API完全兼容,直接替换即可:
# 先安装依赖 pip install rapidfuzz pandas openpyxl
代码中替换导入语句:
from rapidfuzz import fuzz, process
3. 用批量匹配替代嵌套遍历
RapidFuzz的process.extractOne专门用于单元素的最优匹配,比手动嵌套遍历高效得多;数据量极大时还可使用多进程加速:
基础单进程优化
# 批量获取每个A列元素的最优匹配结果 tuples_list = [process.extractOne(i, B_cleaned, scorer=fuzz.token_set_ratio) for i in A_cleaned] # 拆分结果:匹配值、分数、匹配索引(索引可忽略) fuzzy_match, similarity_score, _ = map(list, zip(*tuples_list))
进阶多进程优化(适配百万级数据)
利用concurrent.futures开启多进程,充分利用CPU多核能力:
from concurrent.futures import ProcessPoolExecutor def match_single_entity(entity): """单个实体的匹配函数,用于多进程调用""" result = process.extractOne(entity, B_cleaned, scorer=fuzz.token_set_ratio) return (result[0], result[1]) # 自动根据CPU核心数设置进程数 with ProcessPoolExecutor() as executor: results = list(executor.map(match_single_entity, A_cleaned)) fuzzy_match, similarity_score = map(list, zip(*results))
4. 优化Excel读写速度
- 读取大Excel时,指定
openpyxl引擎(xlsx格式默认); - 写入结果优先用CSV格式(读写速度远快于Excel),如需Excel格式可再转存:
# 先写入CSV(速度更快) df.to_csv("C:/Users/Downloads/fuz-match-output.csv", index=False) # 按需转存为Excel格式 pd.read_csv("C:/Users/Downloads/fuz-match-output.csv").to_excel("C:/Users/Downloads/fuz-match-output.xlsx", sheet_name="匹配结果", index=False)
5. 统一字符串预处理(可选)
对所有匹配字符串做标准化处理,既能提升匹配精度,也能减少无效计算:
import re def clean_string(s): # 转小写、去除特殊字符、合并连续空格 s = s.lower().strip() s = re.sub(r'[^\w\s]', '', s) s = re.sub(r'\s+', ' ', s) return s # 预处理A、B列的字符串 A_cleaned = [clean_string(df1) for df1 in dataframe1["Entity Name"].dropna()] B_cleaned = [clean_string(name) for name in dataframe2[all_name_cols].stack().dropna().unique()]
完整优化代码
import pandas as pd from rapidfuzz import fuzz, process from concurrent.futures import ProcessPoolExecutor import re def clean_string(s): """统一字符串预处理函数""" s = s.lower().strip() s = re.sub(r'[^\w\s]', '', s) s = re.sub(r'\s+', ' ', s) return s def match_single_entity(entity): """单个实体的匹配逻辑,用于多进程调用""" result = process.extractOne(entity, B_cleaned, scorer=fuzz.token_set_ratio) return (result[0], result[1]) if __name__ == "__main__": filename = 'C:/Users/Downloads/SUniverse.xlsx' # 读取Excel工作表 dataframe1 = pd.read_excel(filename, sheet_name='A', engine='openpyxl') dataframe2 = pd.read_excel(filename, sheet_name='B', engine='openpyxl') # 处理Sheet A:提取并清理Entity Name列 raw_A = dataframe1["Entity Name"].dropna() A_cleaned = [clean_string(name) for name in raw_A] # 处理Sheet B:合并所有name列、去重并清理 all_name_cols = [col for col in dataframe2.columns if col == 'name'] raw_B = dataframe2[all_name_cols].stack().dropna().unique() B_cleaned = [clean_string(name) for name in raw_B] # 多进程批量执行匹配 with ProcessPoolExecutor() as executor: results = list(executor.map(match_single_entity, A_cleaned)) # 整理成要求的结果格式 fuzzy_match, similarity_score = map(list, zip(*results)) df = pd.DataFrame({ "Entity Name": raw_A.tolist(), "Name": fuzzy_match, "fuzzy score": similarity_score }) # 输出结果文件 df.to_csv("C:/Users/Downloads/fuz-match-output.csv", index=False) df.to_excel("C:/Users/Downloads/fuz-match-output.xlsx", sheet_name="匹配结果", index=False, engine='openpyxl') print('匹配完成!')
内容的提问来源于stack exchange,提问作者technophile_3
相关产品推荐
相关产品推荐

