初次使用 Elasticsearch 遇多种分词难题?那是你没掌握这些原理
作者介紹
魏彬,普翔科技 CTO,開源軟件愛好者,中國第一位 Elastic 認(rèn)證工程師,《Elastic日報》和 《ElasticTalk》社區(qū)項目發(fā)起人,被 elastic 中國公司授予 2019 年度合作伙伴架構(gòu)師特別貢獻(xiàn)獎。對 Elasticsearch、Kibana、Beats、Logstash、Grafana 等開源軟件有豐富的實踐經(jīng)驗,為零售、金融、保險、證券、科技等眾多行業(yè)的客戶提供過咨詢和培訓(xùn)服務(wù),幫助客戶在實際業(yè)務(wù)中找準(zhǔn)開源軟件的定位,實現(xiàn)從 0 到 1 的落地、從 1 到 N 的拓展,產(chǎn)生實際的業(yè)務(wù)價值。
初次接觸 Elasticsearch 的同學(xué)經(jīng)常會遇到分詞相關(guān)的難題,比如如下這些場景:
1、為什么命名有包含搜索關(guān)鍵詞的文檔,但結(jié)果里面就沒有相關(guān)文檔呢?
2、我存進(jìn)去的文檔到底被分成哪些詞(term)了?
3、我得自定義分詞規(guī)則,但感覺好麻煩呢,無從下手
如果你遇到過類似的問題,希望本文可以解決你的疑惑。
一、上手
讓我們從一個實例出發(fā),如下創(chuàng)建一個文檔:
PUT test/doc/1 {"msg":"Eating an apple a day keeps doctor away" }然后我們做一個查詢,我們試圖通過搜索 eat這個關(guān)鍵詞來搜索這個文檔
POST test/_search {"query":{"match":{"msg":"eat"}} }ES的返回結(jié)果為0。這不太對啊,我們用最基本的字符串查找也應(yīng)該能匹配到上面新建的文檔才對啊!
各位不要急,我們先來看看什么是分詞。
二、 分詞
搜索引擎的核心是倒排索引(這里不展開講),而倒排索引的基礎(chǔ)就是分詞。所謂分詞可以簡單理解為將一個完整的句子切割為一個個單詞的過程。在 es 中單詞對應(yīng)英文為 term。我們簡單看個例子:
ES 的倒排索引即是根據(jù)分詞后的單詞創(chuàng)建,即 我、愛、北京、天安門這4個單詞。這也意味著你在搜索的時候也只能搜索這4個單詞才能命中該文檔。
實際上 ES 的分詞不僅僅發(fā)生在文檔創(chuàng)建的時候,也發(fā)生在搜索的時候,如下圖所示:
讀時分詞發(fā)生在用戶查詢時,ES 會即時地對用戶輸入的關(guān)鍵詞進(jìn)行分詞,分詞結(jié)果只存在內(nèi)存中,當(dāng)查詢結(jié)束時,分詞結(jié)果也會隨即消失。而寫時分詞發(fā)生在文檔寫入時,ES 會對文檔進(jìn)行分詞后,將結(jié)果存入倒排索引,該部分最終會以文件的形式存儲于磁盤上,不會因查詢結(jié)束或者 ES 重啟而丟失。
ES 中處理分詞的部分被稱作分詞器,英文是Analyzer,它決定了分詞的規(guī)則。ES 自帶了很多默認(rèn)的分詞器,比如Standard、 Keyword、Whitespace等等,默認(rèn)是 Standard。當(dāng)我們在讀時或者寫時分詞時可以指定要使用的分詞器。
三、寫時分詞結(jié)果
回到上手階段,我們來看下寫入的文檔最終分詞結(jié)果是什么。通過如下 api 可以查看:
POST test/_analyze {"field": "msg","text": "Eating an apple a day keeps doctor away" }其中 test為索引名,_analyze 為查看分詞結(jié)果的 endpoint,請求體中 field 為要查看的字段名,text為具體值。該 api 的作用就是請告訴我在 test 索引使用 msg 字段存儲一段文本時,es 會如何分詞。
返回結(jié)果如下:
{"tokens": [{"token": "eating","start_offset": 0,"end_offset": 6,"type": "<ALPHANUM>","position": 0},{"token": "an","start_offset": 7,"end_offset": 9,"type": "<ALPHANUM>","position": 1},{"token": "apple","start_offset": 10,"end_offset": 15,"type": "<ALPHANUM>","position": 2},{"token": "a","start_offset": 16,"end_offset": 17,"type": "<ALPHANUM>","position": 3},{"token": "day","start_offset": 18,"end_offset": 21,"type": "<ALPHANUM>","position": 4},{"token": "keeps","start_offset": 22,"end_offset": 27,"type": "<ALPHANUM>","position": 5},{"token": "doctor","start_offset": 28,"end_offset": 34,"type": "<ALPHANUM>","position": 6},{"token": "away","start_offset": 35,"end_offset": 39,"type": "<ALPHANUM>","position": 7}] }返回結(jié)果中的每一個 token即為分詞后的每一個單詞,我們可以看到這里是沒有 eat 這個單詞的,這也解釋了在上手中我們搜索 eat 沒有結(jié)果的情況。如果你去搜索 eating ,會有結(jié)果返回。
寫時分詞器需要在 mapping 中指定,而且一經(jīng)指定就不能再修改,若要修改必須新建索引。如下所示我們新建一個名為ms_english 的字段,指定其分詞器為 english:
PUT test/_mapping/doc {"properties": {"msg_english":{"type":"text","analyzer": "english"}} }四、讀時分詞結(jié)果
由于讀時分詞器默認(rèn)與寫時分詞器默認(rèn)保持一致,拿 上手 中的例子,你搜索 msg 字段,那么讀時分詞器為 Standard ,搜索 msg_english 時分詞器則為 english。這種默認(rèn)設(shè)定也是非常容易理解的,讀寫采用一致的分詞器,才能盡最大可能保證分詞的結(jié)果是可以匹配的。
然后 ES 允許讀時分詞器單獨設(shè)置,如下所示:
POST test/_search{"query":{"match":{"msg":{"query": "eating","analyzer": "english"}}}}如上 analyzer 字段即可以自定義讀時分詞器,一般來講不需要特別指定讀時分詞器。
如果不單獨設(shè)置分詞器,那么讀時分詞器的驗證方法與寫時一致;如果是自定義分詞器,那么可以使用如下的 api 來自行驗證結(jié)果。
POST _analyze{"text":"eating","analyzer":"english"}返回結(jié)果如下:
{"tokens": [{"token": "eat","start_offset": 0,"end_offset": 6,"type": "<ALPHANUM>","position": 0}] }由上可知 english分詞器會將 eating處理為 eat,大家可以再測試下默認(rèn)的 standard分詞器,它沒有做任何處理。
五、解釋問題
現(xiàn)在我們再來看下 上手 中所遇問題的解決思路。
1、查看文檔寫時分詞結(jié)果
2、查看查詢關(guān)鍵詞的讀時分詞結(jié)果
3、匹對兩者是否有命中
我們簡單分析如下:
由上圖可以定位問題的原因了。
六、解決需求
由于 eating只是 eat的一個變形,我們依然希望輸入 eat時可以匹配包含 eating的文檔,那么該如何解決呢?
答案很簡單,既然原因是在分詞結(jié)果不匹配,那么我們就換一個分詞器唄~ 我們可以先試下 ES 自帶的 english分詞器,如下:
# 增加字段 msg_english,與 msg 做對比 PUT test/_mapping/doc {"properties": {"msg_english":{"type":"text","analyzer": "english"}} } # 寫入相同文檔 PUT test/doc/1 {"msg":"Eating an apple a day keeps doctor away","msg_english":"Eating an apple a day keeps doctor away" } # 搜索 msg_english 字段 POST test/_search {"query": {"match": {"msg_english": "eat"}} }執(zhí)行上面的內(nèi)容,我們會發(fā)現(xiàn)結(jié)果有內(nèi)容了,原因也很簡單,如下圖所示:
由上圖可見 english分詞器會將 eating分詞為 eat,此時我們搜索 eat或者 eating肯定都可以匹配對應(yīng)的文檔了。至此,需求解決。
七、深入分析
最后我們來看下為什么english分詞器可以解決我們遇到的問題。一個分詞器由三部分組成:char filter、tokenizer 和 token filter。各部分的作用我們這里就不展開了,我們來看下 standard和english分詞器的區(qū)別。
從上圖可以看出,english分詞器在 Token Filter 中和 Standard不同,而發(fā)揮主要作用的就是 stemmer,感興趣的同學(xué)可以自行去看起它的作用。
八、自定義分詞
如果我們不使用 english分詞器,自定義一個分詞器來實現(xiàn)上述需求也是完全可行的,這里不詳細(xì)講解了,只給大家講一個快速驗證自定義分詞器效果的方法,如下:
POST _analyze {"char_filter": [], "tokenizer": "standard","filter": ["stop","lowercase","stemmer"],"text": "Eating an apple a day keeps doctor away" }通過上面的 api 你可以快速驗證自己要定制的分詞器,當(dāng)達(dá)到自己需求后,再將這一部分配置加入索引的配置。
至此,我們再看開篇的三個問題,相信你已經(jīng)心里有答案了,趕緊上手去自行測試下吧!
聲明:本文由原文《掌握 analyze API,一舉搞定 Elasticsearch 分詞難題》作者“魏彬”授權(quán)轉(zhuǎn)載,對未經(jīng)許可擅自使用者,保留追究其法律責(zé)任的權(quán)利。
【阿里云Elastic Stack】100%兼容開源ES,獨有9大能力,提供免費X-pack服務(wù)(單節(jié)點價值$6000)
相關(guān)活動
更多折扣活動,請訪問阿里云 Elasticsearch 官網(wǎng)
阿里云 Elasticsearch 商業(yè)通用版,1核2G ,SSD 20G首月免費
阿里云 Logstash 2核4G首月免費
原文鏈接:https://developer.aliyun.com/article/768351?
版權(quán)聲明:本文中所有內(nèi)容均屬于阿里云開發(fā)者社區(qū)所有,任何媒體、網(wǎng)站或個人未經(jīng)阿里云開發(fā)者社區(qū)協(xié)議授權(quán)不得轉(zhuǎn)載、鏈接、轉(zhuǎn)貼或以其他方式復(fù)制發(fā)布/發(fā)表。申請授權(quán)請郵件developerteam@list.alibaba-inc.com,已獲得阿里云開發(fā)者社區(qū)協(xié)議授權(quán)的媒體、網(wǎng)站,在轉(zhuǎn)載使用時必須注明"稿件來源:阿里云開發(fā)者社區(qū),原文作者姓名",違者本社區(qū)將依法追究責(zé)任。 如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,歡迎發(fā)送郵件至:developer2020@service.aliyun.com 進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。總結(jié)
以上是生活随笔為你收集整理的初次使用 Elasticsearch 遇多种分词难题?那是你没掌握这些原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿波罗登月51周年,程序员用DAIN技术
- 下一篇: 星辰大海:阿里数据体验技术揭秘!