4、查询预处理:查询解析、查询扩展、拼写纠错、同义词转换、停用词过滤
用户输入的那一串关键词,说白了就是一堆“原始请求”。搜索引擎不能直接拿它去倒排索引里硬怼。为什么?因为用户可能打错字、用词不规范、或者表达得太啰嗦。我做了这么多年搜索,见过最离谱的查询是“我想买那个红色的、能打电话的、不是苹果的那个手机”——嗯,这种查询如果不做预处理,召回结果基本没法看。
所以,查询预处理就是一道“翻译+清洗”的工序。它把用户输入的粗糙文本,转成搜索引擎能理解的结构化查询。今天我们就来拆解这五个核心环节。
4.1 查询解析:把字符串拆成“零件”
查询解析是第一关。它的任务很简单:把用户输入的字符串,拆成一个个有意义的单元。
举个例子,用户搜“北京 到 上海 机票”。解析器要识别出:
- 实体词:“北京”、“上海”是地点
- 意图词:“机票”是查询类型
- 连接词:“到”是关系词,可以忽略或保留
我在项目中遇到过一种情况:用户搜“Java 并发编程 书籍”。如果解析器只按空格切分,会得到三个词。但“并发编程”其实是一个复合概念。我个人习惯的做法是,先做一次短语识别,把高频共现的词组先合并,再去做后续处理。
核心要点:查询解析不是简单的分词。它需要结合意图识别和实体识别,才能把查询拆得“有灵魂”。
常见的解析策略有两种:
- 基于规则:写正则或模板,适合意图明确的场景(如“天气”、“航班”)
- 基于模型:用NER(命名实体识别)模型,适合长尾、复杂的查询
你想想看,如果解析这一步就错了,后面所有环节都是白费力气。所以,我建议在解析阶段就做好兜底策略——解析失败时,至少保证能按原始字符串做全文检索。
4.2 查询扩展:让搜索“多一条路”
用户搜“笔记本电脑”,但你的索引里可能只有“笔记本”、“便携电脑”、“laptop”。这时候,查询扩展就派上用场了。
查询扩展的本质是:在用户原始查询的基础上,加入相关的词或短语,提高召回率。
常用的扩展方式有:
- 基于同义词词典:比如“电脑”扩展出“计算机”、“PC”
- 基于用户行为:比如搜“苹果”的用户,经常也搜“iPhone”,那就可以把“iPhone”作为扩展词
- 基于向量模型:用Word2Vec或BERT把查询转成向量,找语义相近的词
我的经验:查询扩展是一把双刃剑。扩展太多,召回率上去了,但精确率会下降。我曾经在一个电商搜索项目里,把“手机”扩展出了“移动电话”、“手持设备”,结果用户搜“手机”时,出来一堆对讲机。嗯,后来我加了一个相关性阈值,只有相似度超过0.8的词才允许扩展。
扩展后的查询,通常会用OR逻辑组合。比如原始查询是“A B”,扩展后变成“(A OR A1 OR A2) (B OR B1)”。
4.3 拼写纠错:别让用户“打脸”
用户打错字,太常见了。搜“苹里手机”、“淘宝网”,搜索引擎要是直接去索引里找,肯定找不到。拼写纠错就是干这个的。
纠错的思路其实不复杂:
- 检测:判断当前词是否在词典里。不在,就可能是错词。
- 候选生成:基于编辑距离(Levenshtein Distance)、拼音相似度、键盘距离,生成候选词。
- 排序:根据词频、上下文、用户历史行为,选出最可能的正确词。
举个例子,用户搜“苹里”:
- 编辑距离1的候选词:“苹果”、“苹里”(本身)、“平里”
- 拼音相似度:“ping li” → “苹果”、“平里”
- 上下文:如果用户常搜“苹果手机”,那“苹果”的得分就最高
注意:不要对所有查询都做纠错。有些词是故意写错的,比如品牌名“KFC”写成“开封菜”。我曾经犯过这个错,把“开封菜”纠错成“开封菜馆”,结果用户投诉搜不到KFC。后来我加了一个白名单机制,对高频的“错误”写法直接放行。
拼写纠错的实现,我建议用BK树或SymSpell算法。SymSpell的优点是速度快,适合在线场景。我在一个日活千万的搜索系统里,用SymSpell把纠错延迟控制在5毫秒以内。
4.4 同义词转换:让搜索“懂行话”
同义词转换和查询扩展有点像,但侧重点不同。查询扩展是“加词”,同义词转换是“换词”。
比如用户搜“笔记本电脑”,你的索引里只有“笔记本”。同义词转换会把“笔记本电脑”换成“笔记本”,然后去匹配。
同义词的来源主要有:
- 人工维护:适合核心词,比如“电脑”=“计算机”
- 自动挖掘:基于搜索日志,发现用户搜A时也点B,那A和B可能是同义词
- 知识图谱:比如“苹果”和“iPhone”在特定上下文中是同义词
关键点:同义词转换要考虑上下文。比如“苹果”在“苹果手机”里是品牌,在“苹果水果”里是水果。不加区分的同义词转换,会闹笑话。
我个人的做法是,给同义词加上领域标签。比如“苹果”在“电子产品”领域同义于“iPhone”,在“食品”领域同义于“水果”。这样转换时就不会串场。
4.5 停用词过滤:去掉“废话”
停用词,就是那些高频但没啥实际意义的词。比如“的”、“了”、“是”、“在”、“和”。
为什么要过滤?两个原因:
- 减少索引噪音:停用词在文档里出现频率极高,但几乎不携带语义信息。保留它们,倒排索引的倒排链会变得又长又稀疏。
- 提升查询效率:用户搜“北京 的 天气”,如果不过滤“的”,搜索引擎会去匹配“的”这个词,白白浪费计算资源。
停用词表怎么建?我建议分两步:
- 通用停用词:像“的”、“了”、“吗”、“啊”这种,直接内置。
- 领域停用词:比如在电商搜索里,“包邮”、“正品”这类词,虽然有意义,但用户搜“包邮 手机”时,“包邮”其实是个修饰词,可以过滤掉,只保留“手机”作为核心查询。
避坑指南:我曾经在一个法律搜索项目里,把“的”加进了停用词表。结果用户搜“的哥”时,“的”被过滤了,只剩“哥”,搜出来一堆“大哥”、“小哥”。嗯,后来我学乖了——停用词过滤只对短查询生效,或者对高频词做二次校验。
停用词过滤的实现很简单,一般就是维护一个HashSet,查询解析后遍历一遍,把命中停用词表的词去掉。但要注意,有些查询本身就是停用词,比如用户搜“的”这个字——这时候就不能过滤了。所以,我建议加一个查询长度判断:如果查询只有1-2个词,且都是停用词,那就保留原始查询。
小结
查询预处理这五个环节,环环相扣。解析是基础,扩展和纠错是提升召回率的关键,同义词转换解决“说法不同”的问题,停用词过滤则是做减法。你想想看,一个查询经过这五道工序,从“原始字符串”变成了“结构化、可扩展、无噪音”的查询对象,搜索引擎的召回和排序才能有好的起点。
下一章,我们会聊倒排索引的构建与优化。到时候你会看到,预处理做得好,索引构建才能事半功倍。