前言
上一篇部落格寫到ElasticSearch有中文分詞檢索的能力,但如果僅僅就這個就完全沒辦法展現ElasticSearch的強大了,ElasticSearch還能支援短語搜尋,近似搜尋,搜尋推薦,搜尋糾正等搜尋引擎進階特性。可以極大地友善使用者,極大地提高使用者體驗。站内垂直搜尋幾乎在所有網際網路産品中都有運用,往往首頁最顯眼處都有一個搜尋框,如天貓,京東,拼多多,當當網,美團,餓了麼,優酷,愛奇藝,哔哩哔哩,QQ音樂,網易雲音樂,攜程,途牛等等等等,不出意外的話應該都是用ElasticSearch或者solr來實作的。
ES不錯的教學視訊:
- https://www.bilibili.com/video/av50555094/?p=66
- https://www.bilibili.com/video/av50555916
es會把每一個分詞器分好的詞建立反向索引,存儲每一個包含該詞語的id等資訊,個人猜測分詞的清單還會建立Hash表或者B樹,因為詞語的資料量一大,達到千萬級億級,性能無法保證,如果這樣檢索單個詞語的時候時間複雜度則為O(1)或者O(log(M,N)),當然純屬個人臆測啦,并沒有找到相關資料。

ElasticSearch
短語搜尋&近似搜尋
es會對搜尋關鍵字進行分詞,然後到反向索引中去比對,但如果使用者希望根據輸入的短語比對,比如就是要搜尋‘是一個靓仔’一定連一起的,跟資料庫的LIKE關鍵字效果一樣,而不是分詞之後隻要有【是,一個,靓仔】其中一個就OK,這個時候需要用到短語搜尋了;短語搜尋也支援任意兩個詞設定一個最大的距離,隻要滿足詞語詞之間不要超過這個距離都可以搜尋出來,比如包含【是不是 一個鮮肉 靓仔】需要搜尋出來,那麼距離就要設定為2了,這種可以稱之為近似搜尋。
原理:因為文檔分詞後建立反向索引時會記錄該詞在文檔中的position,比對到之後再比較一下position的內插補點即可,如果必須連在一起,那麼內插補點不能超過1。es也可以使用prefix,wildcard,regexp來檢索,但是這些是不能利用es索引的。
-- 建立映射
PUT /parse_search
{
"mappings": {
"properties": {
"con_parse": {
"type": "text"
},
"con_notana": {
"type": "text",
"fields": {
"raw": {
"type": "text",
"index": false
}
}
}
}
}
}
-- 插入資料
PUT /parse_search/_doc/1
{
"con_parse":"淩章是一個靓仔",
"con_notana":"淩章是一個靓仔"
}
PUT /parse_search/_doc/2
{
"con_parse":"ling is a good looking boy",
"con_notana":"ling is a good looking boy"
}
可以搜到,不符合要求
GET /parse_search/_search
{
"query": {
"match": {
"con_parse": "a good"
}
}
}
可以搜到,不符合要求
GET /parse_search/_search
{
"query": {
"match": {
"con_parse": "is good"
}
}
}
搜不到,不符合要求
GET /parse_search/_search
{
"query": {
"term": {
"con_parse": {
"value": "a good"
}
}
}
}
搜不到,不符合要求
GET /parse_search/_search
{
"query": {
"term": {
"con_parse": {
"value": "a good"
}
}
}
}
搜不到,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": "is good"
}
}
}
可以搜到,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": "a good"
}
}
}
可以搜到,間隔一個詞,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": {
"query": "is good",
"slop": 2
}
}
}
}
搜不到,不符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": {
"query": "is goo",
"slop": 2
}
}
}
}
可以搜到,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase_prefix": {
"con_parse": {
"query": "is goo",
"slop": 2
}
}
}
}
搜尋推薦&搜尋糾正
有些時候,使用者去做搜尋是,未必記得全名,這個時候我們需要給使用者一些推薦詞清單,喚起使用者的記憶,友好的幫助使用者搜尋;又有些時候,使用者會有意或無意把詞語拼錯,比如靓仔拼成靓崽,good拼成god,像筆者這種相當愛國、過四級還差兩分、又要經常搜一些代碼資料的人自然是無可避免(拿他真沒辦法),是以搜尋糾正對于es來說也是必不可少。
原理:es的實作使用的N-gram算法模型,我查了一下,他是自然語言處理NLP的一種模型,說起來有一點AI的意味了。使用fuzzy也可以完成檢索,但是不能利用es索引。
--- 搜尋推薦操作
PUT /ngram_search
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 30,
"token_chars": [
"letter",
"digit"
]
}
}
}
},"mappings": {
"properties": {
"con_ngram":{
"type": "text",
"analyzer": "my_analyzer"
}
}
}
}
-- 測試
POST ngram_search/_analyze
{
"analyzer": "my_analyzer",
"text": "ling is a good looking boy"
}
PUT /parse_search/_doc/1
{
"con_parse":"淩章是一個靓仔"
}
PUT /parse_search/_doc/2
{
"con_parse":"ling is a good looking boy"
}
--- 搜尋糾正操作
PUT /autoc_search
{
"settings": {
"analysis": {
"filter": {
"autocomplete_filter" : {
"type" : "edge_ngram",
"min_gram" : 1,
"max_gram" : 20
}
},
"analyzer": {
"autocomplete" : {
"type" : "custom",
"tokenizer" : "standard",
"filter" : [
"lowercase",
"autocomplete_filter"
]
}
}
}
}
, "mappings": {
"properties": {
"con_ngram":{
"type": "text",
"analyzer": "autocomplete"
}
}
}
}
-- 檢視分詞
POST autoc_search/_analyze
{
"analyzer": "autocomplete",
"text": "ling is a good looking boy"
}
PUT /autoc_search/_doc/1
{
"con_ngram":"淩章是一個靓仔"
}
PUT /autoc_search/_doc/2
{
"con_ngram":"ling is a good looking boy"
}
-- 可以查到,搜尋糾正
GET /autoc_search/_search
{
"query": {
"match_phrase": {
"con_ngram":{
"query": "is a god"
}
}
}
}
-- 不可以查到,隔了一個
GET /autoc_search/_search
{
"query": {
"match_phrase": {
"con_ngram":{
"query": "is god"
}
}
}
}
--可以查到,搜尋糾正
GET /autoc_search/_search
{
"query": {
"match_phrase": {
"con_ngram":{
"query": "is god",
"slop": 1
}
}
}
}
-- 搜尋不到
GET /autoc_search/_search
{
"query": {
"match_phrase_prefix": {
"con_ngram":{
"query": "is god"
}
}
}
}
-- 可以搜到
GET /autoc_search/_search
{
"query": {
"match_phrase_prefix": {
"con_ngram":{
"query": "is god",
"slop": 1
}
}
}
}
PostgreSQL再提一筆
PostgreSQL也支援N-gram,是以再提一筆,需要添加pg_trgm擴充,使用gist_trgm後對左右比對都可以利用索引了,也可對正規表達式利用索引,可以實作近似搜尋,但是搜尋推薦和搜尋糾正貌似還是不行。
-- 建立擴充
create extension pg_trgm;
create table pg_ngram(
id serial,
con text,
con_btree text,
con_gin text,
con_gist text
);
-- 索引
create index k_btree on pg_ngram using btree(con_btree);
create index k_gin on pg_ngram using gin(con_gin gin_trgm_ops);
create index k_gist on pg_ngram using gist(con_gist gist_trgm_ops);
-- 查詢
explain select * from pg_ngram where con like '%god looking%';
explain select * from pg_ngram where con_btree like '%god looking%';
explain select * from pg_ngram where con_gin like '%good looking%';
explain select * from pg_ngram where con_gist like '%good looking%';
explain select * from pg_ngram where con_gist ~ 'good looking';
explain select * from pg_ngram where con_gist ~ 'good looking[^。]';
--檢視分解
select show_trgm(con_gin) from pg_ngram;
select show_trgm(con_gist) from pg_ngram;
select similarity(con_gin,'god looking') from pg_ngram;
select similarity(con_gist,'god looking') from pg_ngram;