搜索提示
1. 问题背景
搜索关键字智能提示是一个搜索应用的标配,主要作用是避免用户输入错误的搜索词,并将用户引导到相应的关键词上,以提升用户搜索体验。下面是京东的搜索提示功能:
2. 需求分析
能够根据现有的商品自动生成可用的提示词
公司客户现状决定了不可能有人人工维护提示词,需要由系统自动生成可用的提示词支持前缀匹配原则
在搜索框中输入“海底”,搜索框下面会以海底为前缀,展示“海底捞”、“海底捞火锅”、“海底世界”等等搜索词;输入“万达”,会提示“万达影城”、“万达广场”、“万达百货”等搜索词。同时支持汉字、拼音输入
由于中文的特点,如果搜索自动提示可以支持拼音的话会给用户带来更大的方便,免得切换输入法。比如,输入“haidi”提示的关键字和输入“海底”提示的一样,输入“wanda”与输入“万达”提示的关键字一样。支持多音字输入提示
比如输入“chongqing”或者“zhongqing”都能提示出“重庆火锅”、“重庆烤鱼”、“重庆小天鹅”。支持拼音缩写输入
对于较长关键字,为了提高输入效率,有必要提供拼音缩写输入。比如输入“hd”应该能提示出“haidi”相似的关键字,输入“wd”也一样能提示出“万达”关键字。支持英文、拼音大小写输入
提示不区分大小写,输入“WD“和“wd”一样的效果,输入“HAIDI”、“Haidi”、“HAiDi” 和“haidi”相同的效果。支持单词或者汉字之间的空格
由于输入法的关系,有时单词或者汉字之间会多输入空格,应该对这种情况进行容错。输入“w d”和“wd”应该是一样的效果;输入“wan da”和“wanda”也是一样的效果。考虑关键字的商品数量因素进行排序
一般来讲,关键字的商品数量越多,表示用户购买该关键字下的商品的几率越大,因此提示词排序应该考虑商品数量。基于用户的历史搜索行为,考虑关键字热度因素进行排序
为了提供suggest关键字的准确度,最终查询结果,根据用户查询关键字的频率进行排序,如输入[重庆,chongqing,cq,zhongqing,zq] —> [“重庆火锅”(f1),“重庆烤鱼”(f2),“重庆小天鹅”(f3),…],查询频率f1 > f2 > f3。支持运营手工插入提示词、调整提示词顺序**
系统应该支持由运营人员手工插入提示词,并调整提示词顺序,这样可以引导客户搜索希望的关键词。
3. 解决方案
3.1. 生成拼音
汉字转拼音
用户输入的关键字可能是汉字、数字,英文,拼音,特殊字符等等,由于需要实现拼音提示,我们需要把汉字转换成拼音,我们考虑使用pypinyin库实现转换。拼音缩写提取
虑到需要支持拼音缩写,汉字转换拼音的过程中,顺便提取出拼音缩写,如“chongqing”,"zhongqing"--->"cq",”zq”。多音字全排列
要支持多音字提示,对查询串转换成拼音后,需要实现一个全排列组合。支持单词和汉字空格
对单词或者汉字拼音组合时,增加一种中间带空格的组合不区分大小写
增加全大写和小写的组合。
完整的代码如下,可以看到python代码还是很简洁的,只有22行代码
def get_pingyin_combination(self, input_str, separator=SEPARATOR):
"""
获取词汇的拼音综合,多音字会有多个读音组合
水果 的返回结果为:["shui guo","shuiguo","s g","shg","sh g","sg"]
"""
if not input_str:
return []
pinyin_list = self.combine_element(pinyin(input_str, style=pypinyin.NORMAL, heteronym=True), 0)
first_letter_list = pinyin(input_str, style=pypinyin.FIRST_LETTER, heteronym=True)
initials_letter_list = pinyin(input_str, style=pypinyin.INITIALS, heteronym=True)
merge_letter_list = map(lambda a_list, b_list: filter(lambda item: item, set(a_list + b_list)),
first_letter_list, initials_letter_list)
pinyin_list += self.combine_element(merge_letter_list, 0) + self.combine_low_up_chars(input_str)
pinyin_list += map((lambda element: element.replace(separator, '')), pinyin_list)
return list(set(pinyin_list))
def get_integrated_pingyin_strs(self, input_str):
"""
获取词汇的完整拼音,包含多音词
"""
if not input_str:
return []
return self.combine_element(pinyin(input_str, style=pypinyin.NORMAL, heteronym=True), 0, '')
def combine_element(self, input_list, start, separator=SEPARATOR):
"""
组合各个字的拼音
"""
if start == len(input_list) - 1:
return input_list[start]
return [separator.join(element) for element in
product(input_list[start], self.combine_element(input_list, start + 1, separator))]
def combine_low_up_chars(self, word):
"""
组合大小写字符
"""
if re.search(self.english_filter_regr, word):
return [word.lower(), word.upper()]
return []
3.2. 用户搜索关键词收集
用户在使用搜索引擎查找商品时,会输入大量的关键字,每一次输入就是对关键字的一次投票,那么关键字被输入的次数越多,它对应的查询就比较热门,所以需要把查询的关键字记录下来,并且统计出每个关键字的频率,方便提示结果按照频率排序。用户搜索时,搜索引擎异步把用户每次搜索使用的搜索关键词记录下来,每个关键词的长度为1-40字节。
3.3. 提示词自动生成
搜索关键词提示是为了帮助用户更好的搜索到商品,那么反向推导,我们可以从商品数据中自动提取出提示词。
我们从商品的关键字段(在配置文件中配置)提取提示词,目前是商品标题、品牌、类目、类型字段。不同的字段处理方法不同:
- 品牌、类目、类型字段是固定明确的,不需要分词
比如iphone 6s手机的品牌是苹果,类型是手机,这些直接作为提示词即可 - 标题字段需要分词
商品的标题为"Apple iPhone 6s (A1700) 64G 玫瑰金色 移动联通电信4G手机",真正可以作为提示词的有:“apple”、“iphone”、“6s”、“A1700”、“玫瑰金”、“4G手机”、“手机”、“iphone 6s”。目前任何一种中文分词方法分词结果都不能100%准确,经过比较,我们选择了ansj分词法作为我们的标题分词器。
通过扫描用户所有的商品,提取出关键词,经过计算处理后存放到搜索平台ES Suggest索引中。目前有两种方法触发提示词自动提取任务,一种是后台定时任务触发,一种是通过RESTFul接口由应用直接触发。
3.4. 提示词手工插入
将提示词结构中添加来源字段,目前分为自动提取和手工插入,手工插入的提示词默认权重较高,排序时会在前面。
提供提示词增删改查Restful接口,并且初始化提示词自动提取任务前只删除自动提取的提示词。
3.5. 索引与前缀查询(Trie树)
字符串前缀查询一般用Trie树实现,假设输入的提示词长度为len(q),则Trie树查询的复杂度为O(len(q)),Trie树的查询效率是要比Hash树高。我们可以给每个用户建一颗Trie树。介绍Trie树比较好的文章: 从Trie树(字典树)谈到后缀树。
为了速度可以考虑将提示词全部缓存到内存中,这样查询的速度是最快的。一般1w个商品自动提取的提示词也为1w,每个提示词的数据结构大小为100B,那么单个用户的所有的提示词大小为100000*100B=1MB,1000个有1w商品用户占用的内存大小为1GB,内存消耗还是可以承受的。
考虑使用Elasticsearch的Context Suggester模块实现,ES Context Suggest模块将所有提示词都加载到内存中,然后使用Trie树进行查找,查询时间10ms以内。
3.6. 提示词排序
排序的三个原则:
- 提示词包含的商品数量越多,提示词的顺序越靠前
- 被用户搜索的次数越多,提示词的顺序越靠前
- 手工添加的提示词(可能是推广)顺序靠前
我们设计通过weight(权重)字段进行排序,weight越高顺序越靠前,权重的计算公式:
m表示提示词包含的文档数,s表示提示词被搜索次数,t表示提示词类型权重
每次更新提示词时都会重新计算权重,通过上面的公式计算得到的weight即满足排序的三个原则。
3.7. 从用户输入的关键词提取搜索建议
一个强大的系统应该具有自学习能力,用户输入的关键词也是一种宝贵的资源。比如很多用户搜索"iPhone 6s"、"iPhone 6s 玫瑰金",那么这些热词也可以作为提示词。目前我们的系统具有这样的能力,但是热门词不一定是合法词,系统不具备识别非法词的能力,所以系统的自学习功能不会上线。前不久微软的智能机器人就学习了很多脏话。