搜索引擎keyword智能提示的一种实现
问题背景
搜索关键字智能提示是一个搜索应用的标配。主要作用是避免用户输入错误的搜索词,并将用户引导到相应的关键词上,以提升用户搜索体验。
美团CRM系统中存在数以百万计的商家,为了让用户高速查找到目标商家,我们基于solrcloud实现了商家搜索模块。用户在查找商家时主要输入商户名、商户地址进行搜索,为了提升用户的搜索体验和输入效率。本文实现了一种基于solr前缀匹配查询关键字智能提示(Suggestion)实现。
需求分析
- 支持前缀匹配原则 
在搜索框中输入“海底”,搜索框以下会以海底为前缀,展示“海底捞”、“海底捞火锅”、“海底世界”等等搜索词;输入“万达”,会提示“万达影城”、“万达广场”、“万达百货”等搜索词。 - 同一时候支持汉字、拼音输入 
因为中文的特点,假设搜索自己主动提示能够支持拼音的话会给用户带来更大的方便。免得切换输入法。比方。输入“haidi”提示的关键字和输入“海底”提示的一样,输入“wanda”与输入“万达”提示的关键字一样。 - 支持多音字输入提示 比方输入“chongqing”或者“zhongqing”都能提示出“重庆火锅”、“重庆烤鱼”、“重庆小天鹅”。
 - 支持拼音缩写输入 
对于较长关键字,为了提高输入效率,有必要提供拼音缩写输入。比方输入“hd”应该能提示出“haidi”类似的关键字,输入“wd”也一样能提示出“万达”关键字。 - 基于用户的历史搜索行为,依照关键字热度进行排序 
为了提供suggest关键字的精确度,终于查询结果。依据用户查询关键字的频率进行排序,如输入[重庆,chongqing,cq,zhongqing,zq]
—> [“重庆火锅”(f1),“重庆烤鱼”(f2),“重庆小天鹅”(f3)。…]。查询频率f1 > f2 > f3。 
解决方式
- 关键字收集 
当用户输入一个前缀时,碰到提示的候选词非常多的时候。怎样取舍,哪些展示在前面。哪些展示在后面?这就是一个搜索热度的问题。用户在使用搜索引擎查找商家时。会输入大量的关键字,每一次输入就是对关键字的一次投票。那么关键字被输入的次数越多,它相应的查询就比較热门。所以须要把查询的关键字记录下来,而且统计出每一个关键字的频率,方便提示结果依照频率排序。搜索引擎会通过日志文件把用户每次检索使用的全部检索串都记录下来,每一个查询串的长度为1-255字节。 - 汉字转拼音 
用户输入的关键字可能是汉字、数字,英文,拼音,特殊字符等等,因为须要实现拼音提示。我们须要把汉字转换成拼音,java中考虑使用pinyin4j组件实现转换。 - 拼音缩写提取 
考虑到须要支持拼音缩写。汉字转换拼音的过程中,顺便提取出拼音缩写,如“chongqing”,”zhongqing”—>”cq”,”zq”。 - 多音字全排列 要支持多音字提示,对查询串转换成拼音后。须要实现一个全排列组合,字符串多音字全排列算法例如以下:
 
public static List getPermutationSentence(List> termArrays,int start) {
  if (CollectionUtils.isEmpty(termArrays))
      return Collections.emptyList();
  int size = termArrays.size();
  if (start < 0 || start >= size) {
      return Collections.emptyList();
  }
  if (start == size-1) {
      return termArrays.get(start);
  }
  List<String> strings = termArrays.get(start);
  List<String> permutationSentences = getPermutationSentence(termArrays, start + 1);
  if (CollectionUtils.isEmpty(strings)) {
      return permutationSentences;
  }
  if (CollectionUtils.isEmpty(permutationSentences)) {
      return strings;
  }
  List<String> result = new ArrayList<String>();
  for (String pre : strings) {
      for (String suffix : permutationSentences) {
          result.add(pre+suffix);
      }
  }
  return result;
}
- 索引与前缀查询
 
方案一 Trie树 + TopK算法 
Trie树即字典树,又称单词查找树或键树,是一种树形结构。是一种哈希树的变种。
典型应用是用于统计和排序大量的字符串(但不仅限于字符串)。所以常常被搜索引擎系统用于文本词频统计。它的长处是:最大限度地降低无谓的字符串比較,查询效率比哈希表高。Trie是一颗存储多个字符串的树。
相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串。和普通树不同的地方是。同样的字符串前缀共享同一条分支。
比如。给出一组单词inn, int, at, age, adv, ant, 我们能够得到以下的Trie:
从上图可知,当用户输入前缀i的时候,搜索框可能会展示以i为前缀的“in”。“inn”。”int”等关键词,再当用户输入前缀a的时候,搜索框里面可能会提示以a为前缀的“ate”等关键词。如此。实现搜索引擎智能提示suggestion的第一个步骤便清晰了,即用trie树存储大量字符串,当前缀固定时。存储相对来说比較热的后缀。
TopK算法用于解决统计热词的问题。解决TopK问题主要有两种策略:hashMap统计+排序、堆排序 
hashmap统计: 先对这批海量数据预处理。详细方法是:维护一个Key为Query字串。Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query。假设该字串不在Table中。那么增加该字串,而且将Value值设为1。假设该字串在Table中,那么将该字串的计数加一就可以,终于在O(N)的时间复杂度内用Hash表完毕了统计。 
堆排序:借助堆这个数据结构。找出Top K,时间复杂度为N‘logK。
即借助堆结构,我们能够在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对照。
所以,我们终于的时间复杂度是:O(N) + N’ * O(logK),(N为1000万,N’为300万)。
该方案存在的问题是:
- 建索引和查询的时候都要把汉字转换成拼音。查询完毕后还得把拼音转换成汉字显示,且须要考虑数字和特殊字符。
 - 须要维护拼音、缩写两棵Trie树。
 
方案二 Solr自带Suggest智能提示
Solr作为一个应用广泛的搜索引擎系统,它内置了智能提示功能。叫做Suggest模块。
该模块可选择基于提示词文本做智能提示。还支持通过针对索引的某个字段建立索引词库做智能提示。 (详见solr的wiki页面http://wiki.apache.org/solr/Suggester)
该方案存在的问题是:
返回的结果是基于索引中字段的词频进行排序,不是用户搜索关键字的频率,因此不能将一些热门关键字排在前面。 
拼音提示。多音字。缩写还是要另外加索引字段。
方案三 Solrcloud建立单独的collection,利用solr前缀查询实现
如前所述,以上两个方案在实施起来都存在一些问题,Trie树+TopK算法,在处理汉字suggest时不是非常优雅,且须要维护两棵Trie树,实施起来比較复杂。Solr自带的suggest智能提示组件存在问题是使用freq排序算法。返回的结果全然基于索引中字符的出现次数。没有兼顾用户搜索词语的频率,因此无法将一些热门词排在更靠前的位置。于是。我们继续寻找一种解决问题更加优雅的方案。
至此。我们考虑专门为关键字建立一个索引collection,利用solr前缀查询实现。solr中的copyField能非常好解决我们同一时候索引多个字段(汉字、pinyin, abbre)的需求,且field的multiValued属性设置为true时能解决同一个关键字的多音字组合问题。配置例如以下:
schema.xml:
<field name="kw" type="string" indexed="true" stored="true" />
<field name="pinyin" type="string" indexed="true" stored="false" multiValued="true"/>
<field name="abbre" type="string" indexed="true" stored="false" multiValued="true"/>
<field name="kwfreq" type="int" indexed="true" stored="true" />
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="suggest" type="suggest_text" indexed="true" stored="false" multiValued="true" />
------------------multiValued表示字段是多值的-------------------------------------
<uniqueKey>kw</uniqueKey>
<defaultSearchField>suggest</defaultSearchField>
说明:
kw为原始关键字
pinyin和abbre的multiValued=true,在使用solrj建此索引时,定义成集合类型就可以:如关键字“重庆”的pinyin字段为{chongqing,zhongqing}, abbre字段为{cq, zq}
kwfreq为用户搜索关键的频率。用于查询的时候排序
-------------------------------------------------------
<copyField source="kw" dest="suggest" />
<copyField source="pinyin" dest="suggest" />
<copyField source="abbre" dest="suggest" />
------------------suggest_text----------------------------------
<fieldType name="suggest_text" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true">
    <analyzer type="index">
            <tokenizer class="solr.KeywordTokenizerFactory" />
            <filter class="solr.SynonymFilterFactory"
                    synonyms="synonyms.txt"
                    ignoreCase="true"
                    expand="true" />
            <filter class="solr.StopFilterFactory"
                    ignoreCase="true"
                    words="stopwords.txt"
                    enablePositionIncrements="true" />
            <filter class="solr.LowerCaseFilterFactory" />
            <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt" />
    </analyzer>
    <analyzer type="query">
            <tokenizer class="solr.KeywordTokenizerFactory" />
            <filter class="solr.StopFilterFactory"
                    ignoreCase="true"
                    words="stopwords.txt"
                    enablePositionIncrements="true" />
            <filter class="solr.LowerCaseFilterFactory" />
            <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt" />
    </analyzer>
</fieldType>
KeywordTokenizerFactory:这个分词器不进行不论什么分词!整个字符流变为单个词元。String域类型也有类似的效果。可是它不能配置文本分析的其他处理组件,比方大写和小写转换。
不论什么用于排序和大部分Faceting功能的索引域,这个索引域仅仅有能一个原始域值中的一个词元。
前缀查询构造:
private SolrQuery getSuggestQuery(String prefix, Integer limit) {
    SolrQuery solrQuery = new SolrQuery();
    StringBuilder sb = new StringBuilder();
    sb.append(“suggest:").append(prefix).append("*");
    solrQuery.setQuery(sb.toString());
    solrQuery.addField("kw");
    solrQuery.addField("kwfreq");
    solrQuery.addSort("kwfreq", SolrQuery.ORDER.desc);
    solrQuery.setStart(0);
    solrQuery.setRows(limit);
    return solrQuery;
}
效果例如以下图所看到的: 
搜索引擎keyword智能提示的一种实现的更多相关文章
- 各大搜索引擎智能提示API(JSONP跨域实现自动补全搜索建议)
		
---------------------------------------搜索引擎JSONP接口--------------------------------------------- 提示:U ...
 - 解决Eclipse中编辑xml文件的智能提示问题,最简单的是第二种方法。
		
Eclipse for Android xml 文件代码自动提示功能,介绍Eclipse 编辑器中实现xml 文件代码自动智能提示功能,解决eclipse 代码提示失效.eclipse 不能自动提示. ...
 - 程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索
		
第三十六~三十七章.搜索智能提示suggestion,附近地点搜索 作者:July.致谢:caopengcs.胡果果.时间:二零一三年九月七日. 题记 写博的近三年,整理了太多太多的笔试面试题,如微软 ...
 - 用nodejs 开发的智能提示
		
用nodejs 开发的智能提示 时间:2014-07-01 03:50:18 类别:搜索引擎 访问: 2576 次 感谢:http://lutaf.com/223.htm 智能提示对于搜索非常重要,相 ...
 - 【jQuery】jquery-ui autocomplete智能提示
		
jQuery UI,简而言之,它是一个基于jQuery的前端UI框架.我们可以使用jQuery + jQuery UI非常简单方便地制作出界面美观.功能强大.跨浏览器兼容的前端html界面. Auto ...
 - 搜索关键词智能提示suggestion
		
转载自:stormbjm的专栏 题目详情:百度搜索框中,输入“北京”,搜索框下面会以北京为前缀,展示“北京爱情故事”.“北京公交”.“北京医院”等等搜索词,输入“结构之”,会提示“结构之法”,“结构之 ...
 - 福利到~分享一个基于jquery的智能提示控件intellSeach.js
		
一.需求 我们经常会遇到[站内搜索]的需求,为了提高用户体验,我们希望能做到像百度那样的即时智能提示.例如:某公司人事管理系统,想搜索李XX,只要输入“李”,系统自然会提示一些姓李的员工,这样方便用户 ...
 - Visual Studio Code 使用 Typings 实现智能提示功能
		
前言 我们知道在IDE中代码的智能提示几乎都是标配,虽然一些文本编辑器也有一些简单的提示,但这是通过代码片段提供的.功能上远不能和IDE相比.不过最近兴起的文本编辑器的新锐 Visual Studio ...
 - Eclipse配置详解(包括智能提示设置、智能提示插件修改,修改空格自动上屏、JDK配置、各种快捷键列表……)
		
Eclipse编辑器基本设置 1.添加行号 在边缘处右键 2.改字体 字体的一般配置 3.去掉拼写错误检查 4.Java代码风格 代码格式化 Ctrl + Shift + F 之后点击右边的New按钮 ...
 
随机推荐
- 使用snapshot继续训练网络
			
注意:snapshots和weights不能同时使用 用预训练模型进行finetune是以下命令: ./build/tools/caffe train --solver=examples/XXX/le ...
 - Caused by: java.lang.ClassNotFoundException: Cannot find class: User
			
源代码: <select id="selectAll" resultType="User"> select user_id uid,user_nam ...
 - centos7 install vim8
			
centos7 install vim8 Git and dependency Git: https://github.com/vim/vim # yum install -y perl-devel ...
 - git 超时 时长 设置?
			
[Pipeline] { (Checkout) [Pipeline] checkout > git.exe rev-parse --is-inside-work-tree # timeout=1 ...
 - Map容器之热血格斗场
			
3343:热血格斗场 总时间限制: 1000ms 内存限制: 65536kB 描述 为了迎接08年的奥运会,让大家更加了解各种格斗运动,facer新开了一家热血格斗场.格斗场实行会员制,但是新来的 ...
 - CSS--基础结构层叠
			
权值:通配符*的权值为0,标签和伪元素的权值为1,类选择符,属性选择器或伪类的权值为10,ID选择符的权值为100,内联样式最高为1000.还有一个权值比较特殊--继承也有权值但很低,有的文献提出它只 ...
 - LeetCode(26) Remove Duplicates from Sorted Array
			
题目 Given a sorted array, remove the duplicates in place such that each element appear only once and ...
 - sed之h;H和:a;N;ba使用精解(对段落进行操作)
			
1) 文本: Handle 0x0058, DMI type 20, 19 bytes Memory Device Mapped Address Starting Address: 0 ...
 - react native   标签出错.
			
这种错误为标签错误,没办法,你只能往标签上找了,但不一定是<Text></Text>,我是在<TextInput></TextInput>上出错的,多了 ...
 - selenium之文件上传
			
文件上传是所有UI自动化测试都要面对的一个头疼问题,今天博主在这里给大家分享下自己处理文件上传的经验,希望能够帮助到广大被文件上传坑住的seleniumer. 首先,我们要区分出上传按钮的种类,大体上 ...