如何计算两个DataFrame元素的字符串相似度并优化匹配效率
商品名称模糊匹配优化问题
问题背景
- 现有两个Excel文件:商品分类目录表(包含商品名与对应分类)、海量商品数据条目表
- 数据条目中的商品名称与目录表中的名称存在不一致,需要通过字符串相似度算法实现模糊匹配,找到对应分类
- 最初使用
fuzzywuzzy的Levenshtein距离实现,运行速度极慢;尝试多进程优化后,速度未提升且出现异常
现有代码
from fuzzywuzzy import fuzz import pandas as pd import os import multiprocessing data = pd.read_excel(r"...\data_items.xlsx") catalog = pd.read_excel(r"...\catalog.xlsx") def find_best_match(string, catalog_tokens): # 计算输入字符串的token集合 string_tokens = set(string.split()) # 计算输入字符串与目录中每个字符串的相似度得分 scores = catalog_tokens.apply(lambda x: fuzz.token_set_ratio(string_tokens, x)) # 找到得分最高的目录项索引 index = scores.idxmax() # 返回最佳匹配的商品名和分类 return catalog.loc[index, "Item name"], catalog.loc[index, "Category name"] if __name__ == "__main__": multiprocessing.freeze_support() # 预计算目录中每个商品名的token集合 catalog_tokens = catalog["Item name"].apply(lambda x: set(x.split())) # 使用多进程并行计算 pool = multiprocessing.Pool() results = pool.starmap(find_best_match, [(string, catalog_tokens) for string in data["item"]]) pool.close() pool.join() # 拆分结果 best_matches, categories = zip(*results) print(f"Best_matches: {best_matches} | Categories: {categories}") # 添加结果列到数据中 data["Item_name"] = best_matches data["Category"] = categories data.to_excel(r"...\New_data_items.xlsx", index=False)
问题分析与优化方案
1. 多进程失效的核心原因
catalog_tokens是Pandas Series对象,在多进程中传递时会被完整拷贝到每个子进程,带来巨大的内存开销和序列化时间,抵消了并行计算的优势fuzzywuzzy是纯Python实现,本身计算效率低下,即使并行也难有明显提升
2. 优先提升单进程速度
替换fuzzywuzzy为rapidfuzz(fuzzywuzzy的C++重写版,速度提升10-100倍):
pip install rapidfuzz
修改代码中的导入和匹配逻辑,用process.extractOne简化实现:
from rapidfuzz import fuzz, process def find_best_match(string, catalog_items): best_match, score, _ = process.extractOne(string, catalog_items["Item name"], scorer=fuzz.token_set_ratio) category = catalog_items[catalog_items["Item name"] == best_match]["Category name"].iloc[0] return best_match, category
3. 正确的多进程优化方式
将目录数据转换为可序列化的列表/字典,避免传递大对象:
if __name__ == "__main__": multiprocessing.freeze_support() # 预整理目录数据为字典,方便进程传递 catalog_dict = dict(zip(catalog["Item name"], catalog["Category name"])) catalog_names = list(catalog_dict.keys()) def match_wrapper(string): best_match, _, _ = process.extractOne(string, catalog_names, scorer=fuzz.token_set_ratio) return best_match, catalog_dict[best_match] # 使用imap_unordered减少内存占用(注意:若需保留原数据顺序,需传递索引后再映射) pool = multiprocessing.Pool() results = list(pool.imap_unordered(match_wrapper, data["item"])) pool.close() pool.join() # 合并结果到原数据 data["Item_name"], data["Category"] = zip(*results) data.to_excel(r"...\New_data_items.xlsx", index=False)
4. 进一步优化思路
- 字符串预处理:统一转换为小写、去除特殊字符、过滤停用词,减少匹配干扰项
import re def preprocess(s): s = s.lower() s = re.sub(r'[^\w\s]', '', s) stop_words = {"官方", "正品", "新款", "爆款"} return ' '.join([word for word in s.split() if word not in stop_words]) # 预处理所有商品名 data["item_clean"] = data["item"].apply(preprocess) catalog["Item name_clean"] = catalog["Item name"].apply(preprocess) - 候选集过滤:先通过倒排索引找出包含共同token的目录项,再在候选集中计算相似度,减少计算量
- 嵌入向量匹配:对于超大规模数据,可使用FastText/Word2Vec将商品名转换为向量,通过余弦相似度快速匹配
内容的提问来源于stack exchange,提问作者Fernando Castilla
相关产品推荐
相关产品推荐

