基於Elasticsearch實現搜尋建議

AskHarries發表於2018-07-27

搜尋建議是搜尋的一個重要組成部分,一個搜尋建議的實現通常需要考慮建議詞的來源、匹配、排序、聚合、關聯的文件數和拼寫糾錯等,本文介紹一個基於Elasticsearch實現的搜尋建議。

問題描述

電商網站的搜尋是最基礎最重要的功能之一,搜尋框上面的良好體驗能為電商帶來更高的收益,我們先來看看淘寶、京東、亞馬遜網站的搜尋建議。

在淘寶的搜尋框輸入【衛衣】時,下方的搜尋建議包括建議詞以及相關的標籤:
淘寶的搜尋建議

在京東的搜尋框輸入【衛衣】時,下方搜尋建議右方顯示建議詞關聯的商品數量:
京東的搜尋建議

在亞馬遜的搜尋框輸入【衛衣】時,搜尋建議上部分能支援在特定的分類下進行搜尋:
亞馬遜的搜尋建議

通過上述對比可以看出,不同的電商對於搜尋建議的側重點略有不同,但核心的問題包括:

  • 匹配:能夠通過使用者的輸入進行字首匹配;
  • 排序:根據建議詞的優先順序進行排序;
  • 聚合:能夠根據建議詞關聯的商品進行聚合,比如聚合分類、聚合標籤等;
  • 糾錯:能夠對使用者的輸入進行拼寫糾錯;

搜尋建議實現

在我們的搜尋建議實現裡,主要考慮了建議詞的來源、匹配、排序、關聯的商品數量和拼寫糾錯。

SuggestionDiscovery

  • SuggestionDiscovery的職責是發現建議詞;
  • 建議詞的來源可以是商品的分類名稱、品牌名稱、商品標籤、商品名稱的高頻詞、熱搜詞,也可以是一些組合詞,比如“分類 + 性別”和“分類 + 標籤”,還可以是一些自定義新增的詞;
  • 建議詞維護的時候需要考慮去重,比如“衛衣男”和“衛衣 男”應該是相同的,“Nike”和“nike”也應該是相同的;
  • 由於建議詞的來源通常比較穩定,所以執行的週期可以比較長一點,比如每週一次;

SuggestionCounter

  • SuggestionCounter的職責是獲取建議詞關聯的商品數量,如果需要可以進行一些聚合操作,比如聚合分類和標籤;
  • SuggestionCounter的實現的時候由於要真正地呼叫搜尋介面,應該儘量避免對使用者搜尋的影響,比如在凌晨執行並且使用單執行緒呼叫;
  • 為了提升效率,應該使用Elasticsearch的Multi Search介面批量進行count,同時批量更新資料庫裡建議詞的count值;
  • 由於SuggestionCounter是比較耗資源的,可以考慮延長執行的週期,但是這可能會帶來count值與實際搜尋時誤差較大的問題,這個需要根據實際情況考慮;

SuggestionIndexRebuiler

  • SuggestionIndexRebuiler的職責是負責重建索引;
  • 考慮到使用者的搜尋習慣,可以使用Multi-fields來給建議詞增加多個分析器。比如對於【衛衣 套頭】的建議詞使用Multi-fields增加不分詞欄位、拼音分詞欄位、拼音首字母分詞欄位、IK分詞欄位,這樣輸入【weiyi】和【套頭】都可以匹配到該建議詞;
  • 重建索引時通過是通過bulk批量新增到臨時索引中,然後通過別名來更新;
  • 重建索引的資料依賴於SuggestionCounter,因此其執行的週期應該與SuggestionCounter保持一致;

SuggestionService

  • SuggestionService是真正處於使用者搜尋建議的服務類;
  • 通常的實現是先到快取中查詢是否能匹配到快取記錄,如果能匹配到則直接返回;否則的話呼叫Elasticsearch的Prefix Query進行搜尋,由於我們在重建索引的時候定義了Multi-fields,在搜尋的時候應該用boolQuery來處理;如果此時Elasticsearch返回不為空的結果資料,那麼加入快取並返回即可;
POST /suggestion/_search
{
  "from" : 0,
  "size" : 10,
  "query" : {
    "bool" : {
      "must" : {
        "bool" : {
          "should" : [ {
            "prefix" : {
              "keyword" : "衛衣"
            }
          }, {
            "prefix" : {
              "keyword.keyword_ik" : "衛衣"
            }
          }, {
            "prefix" : {
              "keyword.keyword_pinyin" : "衛衣"
            }
          }, {
            "prefix" : {
              "keyword.keyword_first_py" : "衛衣"
            }
          } ]
        }
      },
      "filter" : {
        "range" : {
          "count" : {
            "from" : 5,
            "to" : null,
            "include_lower" : true,
            "include_upper" : true
          }
        }
      }
    }
  },
  "sort" : [ {
    "weight" : {
      "order" : "desc"
    }
  }, {
    "count" : {
      "order" : "desc"
    }
  } ]
}
複製程式碼
  • 如果Elasticsearch返回的是空結果,此時應該需要增加拼寫糾錯的處理(拼寫糾錯也可以在呼叫Elasticsearch搜尋的時候帶上,但是通常情況下使用者並沒有拼寫錯誤,所以建議還是在後面單獨呼叫suggester);如果返回的suggest不為空,則根據新的詞呼叫建議詞服務;比如使用者輸入了【adidss】,呼叫Elasticsearch的suggester獲取到的結果是【adidas】,則再根據adidas進行搜尋建議詞處理。
POST /suggestion/_search
{
  "size" : 0,
  "suggest" : {
    "keyword_suggestion" : {
      "text" : "adidss",
      "term" : {
        "field" : "keyword",
        "size" : 1
      }
    }
  }
}
複製程式碼
  • 關於排序:在我們的實現裡面是通過weight和count進行排序的,weight目前只考慮了建議詞的型別(比如分類 > 品牌 > 標籤);

實現效果和後續改進

  • 通過上面的實現,我們已經能實現一個比較強大的搜尋建議詞了,實際的效果如下所示:

最終效果

  • 後續可以考慮的改進:參考亞馬遜增加分類的聚合展示、增加使用者個性化的處理支援更好的建議詞排序、基於使用者的搜尋歷史支援更好的建議詞推薦;

參考資料

基於Elasticsearch實現搜尋建議


相關文章