詳解美團(tuán)實(shí)現(xiàn)搜索關(guān)鍵詞自動匹配功能的方法

問題背景
搜索關(guān)鍵字智能提示是一個搜索應(yīng)用的標(biāo)配,主要作用是避免用戶輸入錯誤的搜索詞,并將用戶引導(dǎo)到相應(yīng)的關(guān)鍵詞上,以提升用戶搜索體驗(yàn)。
美團(tuán)CRM系統(tǒng)中存在數(shù)以百萬計的商家,為了讓用戶快速查找到目標(biāo)商家,我們基于solrcloud實(shí)現(xiàn)了商家搜索模塊。用戶在查找商家時主要輸入商戶名、商戶地址進(jìn)行搜索,為了提升用戶的搜索體驗(yàn)和輸入效率,本文實(shí)現(xiàn)了一種基于solr前綴匹配查詢關(guān)鍵字智能提示(Suggestion)實(shí)現(xiàn)。
需求分析
1.支持前綴匹配原則
在搜索框中輸入“海底”,搜索框下面會以海底為前綴,展示“海底撈”、“海底撈火鍋”、“海底世界”等等搜索詞;輸入“萬達(dá)”,會提示“萬達(dá)影城”、“萬達(dá)廣場”、“萬達(dá)百貨”等搜索詞。
2.同時支持漢字、拼音輸入
由于中文的特點(diǎn),如果搜索自動提示可以支持拼音的話會給用戶帶來更大的方便,免得切換輸入法。比如,輸入“haidi”提示的關(guān)鍵字和輸入“海底”提示的一樣,輸入“wanda”與輸入“萬達(dá)”提示的關(guān)鍵字一樣。
3.支持多音字輸入提示
比如輸入“chongqing”或者“zhongqing”都能提示出“重慶火鍋”、“重慶烤魚”、“重慶小天鵝”。
4.支持拼音縮寫輸入
對于較長關(guān)鍵字,為了提高輸入效率,有必要提供拼音縮寫輸入。比如輸入“hd”應(yīng)該能提示出“haidi”相似的關(guān)鍵字,輸入“wd”也一樣能提示出“萬達(dá)”關(guān)鍵字。
基于用戶的歷史搜索行為,按照關(guān)鍵字熱度進(jìn)行排序
為了提供suggest關(guān)鍵字的準(zhǔn)確度,最終查詢結(jié)果,根據(jù)用戶查詢關(guān)鍵字的頻率進(jìn)行排序,如輸入[重慶,chongqing,cq,zhongqing,zq] —> [“重慶火鍋”(f1),“重慶烤魚”(f2),“重慶小天鵝”(f3),…],查詢頻率f1 > f2 > f3。
解決方案
1.關(guān)鍵字收集
當(dāng)用戶輸入一個前綴時,碰到提示的候選詞很多的時候,如何取舍,哪些展示在前面,哪些展示在后面?這就是一個搜索熱度的問題。用戶在使用搜索引擎查找商家時,會輸入大量的關(guān)鍵字,每一次輸入就是對關(guān)鍵字的一次投票,那么關(guān)鍵字被輸入的次數(shù)越多,它對應(yīng)的查詢就比較熱門,所以需要把查詢的關(guān)鍵字記錄下來,并且統(tǒng)計出每個關(guān)鍵字的頻率,方便提示結(jié)果按照頻率排序。搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節(jié)。
2.漢字轉(zhuǎn)拼音
用戶輸入的關(guān)鍵字可能是漢字、數(shù)字,英文,拼音,特殊字符等等,由于需要實(shí)現(xiàn)拼音提示,我們需要把漢字轉(zhuǎn)換成拼音,java中考慮使用pinyin4j組件實(shí)現(xiàn)轉(zhuǎn)換。
3.拼音縮寫提取
考慮到需要支持拼音縮寫,漢字轉(zhuǎn)換拼音的過程中,順便提取出拼音縮寫,如“chongqing”,"zhongqing"--->"cq",”zq”。
4.多音字全排列
要支持多音字提示,對查詢串轉(zhuǎn)換成拼音后,需要實(shí)現(xiàn)一個全排列組合,字符串多音字全排列算法如下:
- 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樹即字典樹,又稱單詞查找樹或鍵樹,是一種樹形結(jié)構(gòu),是一種哈希樹的變種。典型應(yīng)用是用于統(tǒng)計和排序大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計。它的優(yōu)點(diǎn)是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。Trie是一顆存儲多個字符串的樹。相鄰節(jié)點(diǎn)間的邊代表一個字符,這樣樹的每條分支代表一則子串,而樹的葉節(jié)點(diǎn)則代表完整的字符串。和普通樹不同的地方是,相同的字符串前綴共享同一條分支。例如,給出一組單詞inn, int, at, age, adv, ant, 我們可以得到下面的Trie:
從上圖可知,當(dāng)用戶輸入前綴i的時候,搜索框可能會展示以i為前綴的“in”,“inn”,”int"等關(guān)鍵詞,再當(dāng)用戶輸入前綴a的時候,搜索框里面可能會提示以a為前綴的“ate”等關(guān)鍵詞。如此,實(shí)現(xiàn)搜索引擎智能提示suggestion的第一個步驟便清晰了,即用trie樹存儲大量字符串,當(dāng)前綴固定時,存儲相對來說比較熱的后綴。
TopK算法用于解決統(tǒng)計熱詞的問題。解決TopK問題主要有兩種策略:hashMap統(tǒng)計+排序、堆排序
hashmap統(tǒng)計: 先對這批海量數(shù)據(jù)預(yù)處理。具體方法是:維護(hù)一個Key為Query字串,Value為該Query出現(xiàn)次數(shù)的HashTable,即hash_map(Query,Value),每次讀取一個Query,如果該字串不在Table中,那么加入該字串,并且將Value值設(shè)為1;如果該字串在Table中,那么將該字串的計數(shù)加一即可,最終在O(N)的時間復(fù)雜度內(nèi)用Hash表完成了統(tǒng)計。
堆排序:借助堆這個數(shù)據(jù)結(jié)構(gòu),找出Top K,時間復(fù)雜度為N‘logK。即借助堆結(jié)構(gòu),我們可以在log量級的時間內(nèi)查找和調(diào)整/移動。因此,維護(hù)一個K(該題目中是10)大小的小根堆,然后遍歷300萬的Query,分別和根元素進(jìn)行對比。所以,我們最終的時間復(fù)雜度是:O(N) + N' * O(logK),(N為1000萬,N’為300萬)。
該方案存在的問題是:
建索引和查詢的時候都要把漢字轉(zhuǎn)換成拼音,查詢完成后還得把拼音轉(zhuǎn)換成漢字顯示,且需要考慮數(shù)字和特殊字符。
需要維護(hù)拼音、縮寫兩棵Trie樹。
方案二 Solr自帶Suggest智能提示
Solr作為一個應(yīng)用廣泛的搜索引擎系統(tǒng),它內(nèi)置了智能提示功能,叫做Suggest模塊。該模塊可選擇基于提示詞文本做智能提示,還支持通過針對索引的某個字段建立索引詞庫做智能提示。 (詳見solr的wiki頁面http://wiki.apache.org/solr/Suggester)
該方案存在的問題是:
返回的結(jié)果是基于索引中字段的詞頻進(jìn)行排序,不是用戶搜索關(guān)鍵字的頻率,因此不能將一些熱門關(guān)鍵字排在前面。
拼音提示,多音字,縮寫還是要另外加索引字段。
方案三 Solrcloud建立單獨(dú)的collection,利用solr前綴查詢實(shí)現(xiàn)
如前所述,以上兩個方案在實(shí)施起來都存在一些問題,Trie樹+TopK算法,在處理漢字suggest時不是很優(yōu)雅,且需要維護(hù)兩棵Trie樹,實(shí)施起來比較復(fù)雜;Solr自帶的suggest智能提示組件存在問題是使用freq排序算法,返回的結(jié)果完全基于索引中字符的出現(xiàn)次數(shù),沒有兼顧用戶搜索詞語的頻率,因此無法將一些熱門詞排在更靠前的位置。于是,我們繼續(xù)尋找一種解決這個問題更加優(yōu)雅的方案。
至此,我們考慮專門為關(guān)鍵字建立一個索引collection,利用solr前綴查詢實(shí)現(xiàn)。solr中的copyField能很好解決我們同時索引多個字段(漢字、pinyin, abbre)的需求,且field的multiValued屬性設(shè)置為true時能解決同一個關(guān)鍵字的多音字組合問題。配置如下:
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為原始關(guān)鍵字
pinyin和abbre的multiValued=true,在使用solrj建此索引時,定義成集合類型即可:如關(guān)鍵字“重慶”的pinyin字段為{chongqing,zhongqing}, abbre字段為{cq, zq}
kwfreq為用戶搜索關(guān)鍵的頻率,用于查詢的時候排序
-------------------------------------------------------
- <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:這個分詞器不進(jìn)行任何分詞!整個字符流變?yōu)閱蝹€詞元。String域類型也有類似的效果,但是它不能配置文本分析的其它處理組件,比如大小寫轉(zhuǎn)換。任何用于排序和大部分Faceting功能的索引域,這個索引域只有能一個原始域值中的一個詞元。
前綴查詢構(gòu)造:
- 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;
- }
效果如下圖所示:
相關(guān)文章
如何挖掘常規(guī)的長尾關(guān)鍵詞?輕松獲取海量長尾詞的八種挖掘方法
對于一般的網(wǎng)站來說,流量大部分均來自長尾關(guān)鍵詞,看流量統(tǒng)計的時候,搜索關(guān)鍵詞前幾頁是指數(shù)相對高一點(diǎn)的詞語,但是后面數(shù)頁基本都是長尾詞,那么如何挖掘常規(guī)的長尾關(guān)鍵2016-05-19如何做好100關(guān)鍵詞?得到ASO優(yōu)化100關(guān)鍵詞字符實(shí)例解析
如何做好100關(guān)鍵詞?下面小編就為大家介紹得到ASO優(yōu)化100關(guān)鍵詞字符方法,有需要的朋友可以參考一下哦2016-05-18- 很多做過網(wǎng)站的企業(yè)或者對于個人站長來說,選擇有效的關(guān)鍵詞對于一個網(wǎng)站的重要性不言而喻,但是如何選擇有效的關(guān)鍵詞就不是一件容易的事情,尤其是是將自己的關(guān)鍵詞做到搜索2016-05-18
怎樣挖掘長尾關(guān)鍵詞?挖掘關(guān)鍵詞工具熊貓關(guān)鍵詞使用教程
做SEO都很注重關(guān)鍵詞,尤其是重點(diǎn)關(guān)鍵詞,可對于一般的網(wǎng)站來說,流量大部分均來自長尾關(guān)鍵詞,看流量統(tǒng)計的時候,搜索關(guān)鍵詞前幾頁是指數(shù)相對高一點(diǎn)的詞語,但是后面數(shù)頁2016-05-17如何挖掘關(guān)鍵詞 網(wǎng)站關(guān)鍵詞SEO優(yōu)化實(shí)戰(zhàn)技巧
在網(wǎng)站推廣SEO工作流程中,挖掘關(guān)鍵詞是非常重要的一環(huán)。而關(guān)鍵詞對于網(wǎng)站搜索排名就像水對于魚一樣重要。沒有關(guān)鍵詞,很難實(shí)現(xiàn)流量的提升、訂單收益。那么,關(guān)鍵詞可以通過2016-05-13為什么SEO方案制定時應(yīng)避免盲目的從眾式調(diào)整
搜索引擎針對收錄和排名的算法經(jīng)常作出改變,很多人往往選擇時刻"與時俱進(jìn)",然而效果卻往往并非如期,這里就來舉一些實(shí)例說明為什么SEO方案制定時應(yīng)避免盲目的從眾式調(diào)整.2016-05-13淺析SEO優(yōu)化中關(guān)鍵詞的密度數(shù)多少比較合適
由于每一個人對于SEO優(yōu)化見解的不一樣,對于關(guān)鍵詞的密度數(shù)值也是不一樣的,小編認(rèn)為針對不同頁面情況,關(guān)鍵詞密度值也有所不相同,總之關(guān)鍵詞密度可控制在1%-9%之間,下面2016-05-12怎么樣的關(guān)鍵詞進(jìn)行推廣?百度推廣競價賬戶關(guān)鍵詞選擇策略
在我們進(jìn)行百度推廣時,選擇符合自己企業(yè)的關(guān)鍵詞進(jìn)行推廣是非常重要且緊急的事情,那么,在我們進(jìn)行百度賬戶優(yōu)化的時候,到底有哪里推廣優(yōu)化技巧值得我們學(xué)習(xí)和專研呢?下2016-05-12如何讓你的網(wǎng)站關(guān)鍵詞排名超越同行業(yè)網(wǎng)站?
如何讓你的網(wǎng)站關(guān)鍵詞排名超越同行業(yè)網(wǎng)站?關(guān)注關(guān)鍵詞排名是每個站長每天都在做的事,只有上了首頁網(wǎng)站才有流量,那么如何實(shí)現(xiàn)呢?下面一起跟小編來看看吧2016-05-12- 平時人們使用搜索引擎進(jìn)行搜索時基本上都是依靠輸入關(guān)鍵詞進(jìn)行,因而如何定位網(wǎng)站內(nèi)容的關(guān)鍵詞自然也是SEO的重點(diǎn),下面我們就來具體看一下關(guān)鍵詞的概念,解析SEO中的關(guān)鍵詞要2016-05-12