搜索是很多内容类app必不可少的功能,而搜索框提示则为用户关键词输入提供了一种引导,一个友好的搜索框提示不仅能提升用户体验,还能帮助用户节省触达商品的时间提升搜索效率。
搜索框功能主要有3部分组成:
- 智能补全
- 关联数量
- 拼写纠错
实现流程
ES官方文档建议通过phrase Suggester实行搜索框的自动补全,但这种查询对中文支持不太友好,经常会不做提示;下面我们通过n-gram来实现符合中国人民使用习惯的提示框。
- 什么是edge n-gram
假设有一个词hello,普通建索引时,就是把这个词hello放入倒排索引
用户输入h、he时会找不到索引(倒排索引中只有hello),因此匹配失败
而对于输入即搜索这种应用场景,可以使用一种特殊的n-gram,称为边界n-grams (edge n-grams)
所谓的edge n-gram,就是指它会固定从一边开始,进行窗口滑动,每次滑动长度为1,最终的结果取决于 n 的选择长度
以单词hello为例,它的edge n-gram的结果如下:
h
he
hel
hell
hello
因此可以发现到,在使用edge n-gram建索引时,一个单词会生成好几个索引,而这些索引一定是从头开始
这符合了输入即搜索的特性,即是用户打h、he能找到倒排中的索引h、he,而这些索引对应著的数据就是hello
依赖插件:
构建索引库:
PUT st
{
"mappings" : {
"dynamic" : "false",
"properties" : {
"suggest" : {
"type" : "text",
"analyzer" : "autocomplete" ,
"search_analyzer": "autocomplete_search"
},
"weight" : {
"type" : "integer"
},
"count" : {
"type" : "integer"
}
}
},
"settings" : {
"index" : {
"number_of_shards" : "1",
"analysis" : {
"filter" : {
"length_filter" : {
"type" : "length",
"min" : "2"
},
"my_pinyin" : {
"keep_joined_full_pinyin" : "true",
"lowercase" : "true",
"keep_original" : "true",
"remove_duplicated_term" : "true",
"keep_separate_first_letter" : "false",
"type" : "pinyin",
"limit_first_letter_length" : "16",
"keep_full_pinyin" : "true"
}
},
"analyzer" : {
"autocomplete_search" : {
"filter" : [
"lowercase"
],
"tokenizer" : "search_py"
},
"autocomplete" : {
"filter" : [
"lowercase",
"length_filter",
"my_pinyin"
],
"tokenizer" : "autocomplete"
}
},
"tokenizer" : {
"autocomplete" : {
"min_gram" : "2",
"type" : "edge_ngram",
"max_gram" : "16"
} ,
"search_py" : {
"keep_joined_full_pinyin" : "true",
"keep_none_chinese_in_first_letter " : "true",
"lowercase" : "true",
"none_chinese_pinyin_tokenize" : "false",
"keep_none_chinese_in_joined_full_pinyin" : "true",
"keep_original" : "true",
"keep_first_letter" : "true",
"keep_separate_first_letter" : "false",
"type" : "pinyin",
"limit_first_letter_length" : "16",
"keep_full_pinyin" : "false"
}
}
},
"number_of_replicas" : "1"
}
}
}
Mapping
-
_id
文档id, 这里取关键词md5后的字符串作为id,方便更新和删除 -
suggest
离线任务定时提取用户高频输入的搜索关键词与商品分类、标签信息索引到该字段;其中analyzer与search_analyzer分别使用自定义分词器。 -
weight
关键词权重;默认取词频,也可以手动指定 -
count
搜索词关联的商品数量
Setting
-
length_filter
用来控制分词后的term长度,这里限制为2,当term字符长度<2时会被忽略 - pinyin
keep_first_letter:这个参数会将词的第一个字母全部拼起来.例如:刘德华->ldh.默认为:true
keep_separate_first_letter:这个会将第一个字母一个个分开.例如:刘德华->l,d,h.默认为:flase.如果开启,可能导致查询结果太过于模糊,准确率太低.
limit_first_letter_length:设置最大keep_first_letter结果的长度,默认为:16
keep_full_pinyin:如果打开,它将保存词的全拼,并按字分开保存.例如:刘德华> [liu,de,hua],默认为:true
keep_joined_full_pinyin:如果打开将保存词的全拼.例如:刘德华> [liudehua],默认为:false
keep_none_chinese:将非中文字母或数字保留在结果中.默认为:true
keep_none_chinese_together:保证非中文在一起.默认为: true, 例如: DJ音乐家 -> DJ,yin,yue,jia, 如果设置为:false, 例如: DJ音乐家 -> D,J,yin,yue,jia, 注意: keep_none_chinese应该先开启.
keep_none_chinese_in_first_letter:将非中文字母保留在首字母中.例如: 刘德华AT2016->ldhat2016, 默认为:true
keep_none_chinese_in_joined_full_pinyin:将非中文字母保留为完整拼音. 例如: 刘德华2016->liudehua2016, 默认为: false
none_chinese_pinyin_tokenize:如果他们是拼音,切分非中文成单独的拼音项. 默认为:true,例如: liudehuaalibaba13zhuanghan -> liu,de,hua,a,li,ba,ba,13,zhuang,han, 注意: keep_none_chinese和keep_none_chinese_together需要先开启.
keep_original:是否保持原词.默认为:false
lowercase:小写非中文字母.默认为:true
trim_whitespace:去掉空格.默认为:true
remove_duplicated_term:保存索引时删除重复的词语.例如: de的>de, 默认为: false, 注意:开启可能会影响位置相关的查询.
ignore_pinyin_offset:在6.0之后,严格限制偏移量,不允许使用重叠的标记.使用此参数时,忽略偏移量将允许使用重叠的标记.请注意,所有与位置相关的查询或突出显示都将变为错误,您应使用多个字段并为不同的字段指定不同的设置查询目的.如果需要偏移量,请将其设置为false。默认值:true
-
autocomplete_search
查询时使用的分词器 -
autocomplete
索引时使用的分词器 -
edge_ngram
edge_ngram是ES自带的token解析器,从min_gram处开始依次分词,当达到max_gram或至文本结尾时停止分词。
插入测试数据
curl -XPUT "http://1.0.0.1:9200/st/_doc/1" -H 'Content-Type: application/json' -d'{ "suggest": "小米手机", "count":110,"weight":10}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/2" -H 'Content-Type: application/json' -d'{ "suggest": "小米手机新款", "count":110,"weight":8}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/3" -H 'Content-Type: application/json' -d'{ "suggest": "小米手机 5g", "count":110,"weight":10}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/4" -H 'Content-Type: application/json' -d'{ "suggest": "小米128g", "count":110,"weight":6}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/5" -H 'Content-Type: application/json' -d'{ "suggest": "小米袋装", "count":110,"weight":6}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/6" -H 'Content-Type: application/json' -d'{ "suggest": "华为5g新款", "count":110,"weight":10}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/7" -H 'Content-Type: application/json' -d'{ "suggest": "华为手机", "count":110, "weight":10}'
curl -XPUT "http://1.0.0.1:9200/st/_doc/8" -H 'Content-Type: application/json' -d'{ "suggest": "小蜜蜂", "count":110, "weight":8}'
权重计算
- 全量同步商城分类及标签数据,这类权重值给到Int最大值
- 通过spark离线任务增量迭代客户端搜索埋点日志,剔除过长搜索词后进行分组统计,并将sessionid去重后>3的入库
关联商品数量
- 在增量更新suggest中关键词时会去商品库进行一次聚合查询
- 为了避免对用户搜索业务的影响,任务会放在凌晨进行
- 为提升效率,应该使用Elasticsearch的Multi Search接口批量进行count,同时批量更新数据库里建议词的count值
- 由于商品是实时入库的而关联统计是定期离线执行的,所以在进行相关提示时会显示“约”
定期修正
- 离线任务会定时对近一月用户输入关键词进行分析统计,将weight值<10的删除,weight>10的入库,以修正增量数据的误差
- 删除时不要使用delete by query进行删除,这样会非常损耗es的性能,应该使用关键词md5后的id批量删除
- 由于数据量较大,更新时间比较长,为了不影响搜索性能,这里会先写入到一个临时索引库,通过bulk批量添加到临时索引中,然后通过别名切换来更新;
自动补全效果展示
- 输入“小米sj”获取【小米手机】相关提示词;支持特殊符号输入(如 H&M服饰),也可全拼音或全汉字输入
GET st/_search
{
"sort": [
{
"weight": {
"order": "desc"
}
}
],
"query": {
"multi_match": {
"query": "小米sj",
"fuzziness": 1,
"prefix_length" : 1,
"fields": ["suggest"]
}
}
}
需要注意的是,这里使用fuzziness来模糊匹配提升用户体验,fuzziness=1 允许用户输入一个错别字,并通过prefix_length设置为1来跳过开头首个字符的判断,因为一般用户输入出错大多发生在后面
- 返回结果
{
"_index" : "st",
"_type" : "_doc",
"_id" : "1",
"_score" : null,
"_source" : {
"suggest" : "小米手机",
"count" : 110,
"weight" : 10
},
"sort" : [
10
]
},
{
"_index" : "st",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"suggest" : "小米手机新款",
"count" : 110,
"weight" : 8
},
"sort" : [
8
]
}
智能纠错效果展示
- 如果Elasticsearch返回的是空结果,此时应该需要增加拼写纠错的处理(拼写纠错也可以在调用Elasticsearch搜索的时候带上,但是通常情况下用户并没有拼写错误,所以建议还是在后面单独调用suggester);如果返回的suggest不为空,则根据新的词调用建议词服务;比如用户输入了【小密】,调用Elasticsearch的suggester获取到的结果是【小米】,则再根据小米进行搜索建议词处理
GET st/_search
{
"_source": false,
"size": 0,
"suggest": {
"term-suggestion": {
"text": "小密",
"term": {
"field": "suggest",
"min_word_length":2,
"prefix_length":1,
“min_doc_freq”:2,
"size":2
}
}
}
}
需要注意:
其中min_word_length是用来控制候选词长度的,这里设置为2,意思是当term长度>=2才会被显示;
prefix_length=1表示忽略首字符是错别字,大多数输入错别字发生在后面;
min_doc_freq 当建议词出现文档频率低于该值时将被忽略,线上可适当调大该值以提升搜索效果
- 返回结果
{
"text" : "小密",
"offset" : 0,
"length" : 0,
"options" : [
{
"text" : "小米",
"score" : 0.5,
"freq" : 5
},
{
"text" : "小蜜",
"score" : 0.5,
"freq" : 1
}
]
}
线上效果
作者:易企秀工程师 Yarn ->个人主页