美文网首页设计方案Elasticsearch
模拟实战京东搜索效果(一)

模拟实战京东搜索效果(一)

作者: 郭彦超 | 来源:发表于2020-04-14 08:05 被阅读0次

    搜索是很多内容类app必不可少的功能,而搜索框提示则为用户关键词输入提供了一种引导,一个友好的搜索框提示不仅能提升用户体验,还能帮助用户节省触达商品的时间提升搜索效率。

    image.png image.png image.png

    搜索框功能主要有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
              }
            ]
          }
    
    

    线上效果

    易企秀商城

    相关文章

      网友评论

        本文标题:模拟实战京东搜索效果(一)

        本文链接:https://www.haomeiwen.com/subject/cprbmhtx.html