如果直接使用Elasticsearch的朋友在進行中文内容的搜尋時,肯定會遇到很尴尬的問題——中文詞語被分成了一個一個的漢字,當用Kibana作圖的時候,按照term來分組,結果一個漢字被分成了一組。
這是因為使用了Elasticsearch中預設的标準分詞器,這個分詞器在進行中文的時候會把中文單詞切分成一個一個的漢字,是以引入中文的分詞器就能解決這個問題。
本篇文章按照下面的内容進行描述:
- 分詞器的作用
- 安裝IK
- 簡單的測試
- 模拟測試
- 安裝elasticsearch-analysis-pinyin
分詞顧名思義,就是把一句話分成一個一個的詞。這個概念在搜尋中很重要,比如
This is a banana.
如果按照普通的空格來分詞,分成
this
,
is
a
banana
,的出來的
a
其實對我們并沒有什麼用處。是以需要注意下面的問題:
- 1 區分停頓詞(
a
or
這種都屬于停頓詞)and
- 2 大小寫轉換(
與Banana
)banana
- 3 時态的轉換....
具體的算法可以參考http://tartarus.org/~martin/PorterStemmer/,對照的詞語可以參考這裡http://snowball.tartarus.org/algorithms/porter/diffs.txt
相比中文,就複雜的度了。因為中文不能單純的依靠空格,标點這種進行分詞。就比如
中華人民共和國國民
,不能簡單的分成一個詞,也不能粗暴的分成
中華人民共和國
和
國民
,
人民
、
中華
這些也都算一個詞!
是以常見的分詞算法就是拿一個标準的詞典,關鍵詞都在這個詞典裡面。然後按照幾種規則去查找有沒有關鍵詞,比如:
- 正向最大比對(從左到右)
- 逆向最大比對(從右到左)
- 最少切分
- 雙向比對(從左掃描一次,從右掃描一次)
IK,elasticsearch-analysis-ik提供了兩種方式,
ik_smart
就是最少切分,
ik_max_word
則為細粒度的切分(可能是雙向,沒看過源碼)
了解了分詞器的背景後,就可以看一下如何在Elasticsearch重安裝分詞器了。
在github中下載下傳相應的代碼,比如我的最新版本2.4.0就沒有對應的ik版本,不用擔心,隻需要修改pom.xml就可以了:
<properties>
<!-- 這裡的版本号,修改成你對應的版本就行了。
不過最好不要跨度太大,相近的版本可能沒有問題,但是跨度太大的版本,這樣做就不保證好使了-->
<elasticsearch.version>2.4.0</elasticsearch.version>
<maven.compiler.target>1.7</maven.compiler.target>
<elasticsearch.assembly.descriptor>${project.basedir}/src/main/assemblies/plugin.xml</elasticsearch.assembly.descriptor>
<elasticsearch.plugin.name>analysis-ik</elasticsearch.plugin.name>
<elasticsearch.plugin.classname>org.elasticsearch.plugin.analysis.ik.AnalysisIkPlugin</elasticsearch.plugin.classname>
<elasticsearch.plugin.jvm>true</elasticsearch.plugin.jvm>
<tests.rest.load_packaged>false</tests.rest.load_packaged>
<skip.unit.tests>true</skip.unit.tests>
<gpg.keyname>4E899B30</gpg.keyname>
<gpg.useagent>true</gpg.useagent>
</properties>
下載下傳後,執行
mvn package
,進行打包:
├─config
├─src
└─target
├─archive-tmp
├─classes
├─generated-sources
├─maven-archiver
├─maven-status
├─releases
│ └─elasticsearch-analysis-ik-1.9.5.zip
└─surefire
編譯完成後,可以在target/releases目錄下找到對應的zip包。
解壓zip包,複制到elasticsearch-root-path/plugins/ik下即可。
[root@hadoop-master ik]# ll
total 1428
-rw-r--r-- 1 root root 263965 Sep 26 15:03 commons-codec-1.9.jar
-rw-r--r-- 1 root root 61829 Sep 26 15:03 commons-logging-1.2.jar
drwxr-xr-x 3 root root 4096 Sep 26 16:11 config
-rw-r--r-- 1 root root 56023 Sep 26 15:03 elasticsearch-analysis-ik-1.9.5.jar
-rw-r--r-- 1 root root 736658 Sep 26 15:03 httpclient-4.5.2.jar
-rw-r--r-- 1 root root 326724 Sep 26 15:03 httpcore-4.4.4.jar
-rw-r--r-- 1 root root 2666 Sep 26 15:03 plugin-descriptor.properties
[root@hadoop-master ik]# pwd
/usr/elk/elasticsearch-2.4.0/plugins/ik
拷貝後,重新開機elasticsearch就可以使用分詞器了。
最簡單的測試
這裡使用_analyze api對中文段落進行分詞,測試一下:
GET _analyze
{
"analyzer":"ik_max_word",
"text":"中華人民共和國國歌"
}
可以看到ik盡可能多的切分的單詞:
{
"tokens": [
{
"token": "中華人民共和國",
"start_offset": 0,
"end_offset": 7,
"type": "CN_WORD",
"position": 0
},
{
"token": "中華人民",
"start_offset": 0,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
},
{
"token": "中華",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 2
},
{
"token": "華人",
"start_offset": 1,
"end_offset": 3,
"type": "CN_WORD",
"position": 3
},
{
"token": "人民共和國",
"start_offset": 2,
"end_offset": 7,
"type": "CN_WORD",
"position": 4
},
{
"token": "人民",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 5
},
{
"token": "共和國",
"start_offset": 4,
"end_offset": 7,
"type": "CN_WORD",
"position": 6
},
{
"token": "共和",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 7
},
{
"token": "國",
"start_offset": 6,
"end_offset": 7,
"type": "CN_CHAR",
"position": 8
},
{
"token": "國歌",
"start_offset": 7,
"end_offset": 9,
"type": "CN_WORD",
"position": 9
}
]
}
如果使用ik_smart,則會盡可能少的傳回詞語:
{
"tokens": [
{
"token": "中華人民共和國",
"start_offset": 0,
"end_offset": 7,
"type": "CN_WORD",
"position": 0
},
{
"token": "國歌",
"start_offset": 7,
"end_offset": 9,
"type": "CN_WORD",
"position": 1
}
]
}
我這裡直接在elastic Sense中進行測試的(強烈推薦這個插件,非常好用,不過輸入中文的時候,有點BUG)
第一步,建立一個空的索引
PUT test
{
}
如果你用的是curl,可以執行
curl -XPUT localhost:9200/test
第二步,設定映射類型
POST test/test/_mapping
{
"test": {
"_all": {
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word",
"term_vector": "no",
"store": "false"
},
"properties": {
"content": {
"type": "string",
"store": "no",
"term_vector": "with_positions_offsets",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word",
"include_in_all": "true",
"boost": 8
}
}
}
}
上面的指令,是定義test索引下test類型的映射。其中定義了_all字段的分析方法,以及content屬性的分析方法。
這裡介紹下什麼是_all字段,其實_all字段是為了在不知道搜尋哪個字段時,使用的。es會把所有的字段(除非你手動設定成false),都放在_all中,然後通過分詞器去解析。當你使用query_string的時候,預設就在這個_all字段上去做查詢,而不需要挨個字段周遊,節省了時間。
properties
中定義了特定字段的分析方式。在上面的例子中,僅僅設定了content的分析方法。
- type,字段的類型為string,隻有string類型才涉及到分詞,像是數字之類的是不需要分詞的。
- store,定義字段的存儲方式,no代表不單獨存儲,查詢的時候會從_source中解析。當你頻繁的針對某個字段查詢時,可以考慮設定成true。
- term_vector,定義了詞的存儲方式,with_position_offsets,意思是存儲詞語的偏移位置,在結果高亮的時候有用。
- analyzer,定義了索引時的分詞方法
- search_analyzer,定義了搜尋時的分詞方法
- include_in_all,定義了是否包含在_all字段中
- boost,是跟計算分值相關的。
設定完成後,添加一個文檔
POST test/test/1
{
"test":"美國留給伊拉克的是個爛攤子嗎"
}
POST test/test/2
{
"content":"公安部:各地校車将享最高路權嗎"
}
POST test/test/3
{
"content":"中韓漁警沖突調查:韓警平均每天扣1艘中國漁船"
}
POST test/test/4
{
"content":"中國駐洛杉矶領事館遭亞裔男子槍擊 嫌犯已自首"
}
最後,執行查詢進行測試
GET test/_search
{
"query" : { "term" : { "content" : "中國" }},
"highlight" : {
"pre_tags" : ["<tag1>", "<tag2>"],
"post_tags" : ["</tag1>", "</tag2>"],
"fields" : {
"content" : {}
}
}
}
得到傳回結果:
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1.5,
"hits": [
{
"_index": "test",
"_type": "test",
"_id": "4",
"_score": 1.5,
"_source": {
"content": "中國駐洛杉矶領事館遭亞裔男子槍擊 嫌犯已自首"
},
"highlight": {
"content": [
"<tag1>中國</tag1>駐洛杉矶領事館遭亞裔男子槍擊 嫌犯已自首"
]
}
},
{
"_index": "test",
"_type": "test",
"_id": "3",
"_score": 0.53699243,
"_source": {
"content": "中韓漁警沖突調查:韓警平均每天扣1艘中國漁船"
},
"highlight": {
"content": [
"中韓漁警沖突調查:韓警平均每天扣1艘<tag1>中國</tag1>漁船"
]
}
}
]
}
}
安裝elasticsearch-analysis-pinyin分詞器
pinyin分詞器可以讓使用者輸入拼音,就能查找到相關的關鍵詞。比如在某個商城搜尋中,輸入
shuihu
,就能比對到
水壺
。這樣的體驗還是非常好的。
pinyin分詞器的安裝與IK是一樣的,這裡就省略掉了。下載下傳的位址參考github.
這個分詞器在1.8版本中,提供了兩種分詞規則:
-
,就是普通的把漢字轉換成拼音;pinyin
-
,提取漢字的拼音首字母pinyin_first_letter
首先建立索引,并建立分詞器:
PUT medcl
{
"index" : {
"analysis" : {
"analyzer" : {
"pinyin_analyzer" : {
"tokenizer" : "my_pinyin",
"filter" : "word_delimiter"
}
},
"tokenizer" : {
"my_pinyin" : {
"type" : "pinyin",
"first_letter" : "none",
"padding_char" : " "
}
}
}
}
}
然後使用analyze api,進行測試
GET medcl/_analyze
{
"text":"劉德華",
"analyzer":"pinyin_analyzer"
}
可以得到結果:
{
"tokens": [
{
"token": "liu",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "de",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 1
},
{
"token": "hua",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 2
}
]
}
如果分詞器設定為pinyin_first_letter,則分析的結果為:
{
"tokens": [
{
"token": "ldh",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
}
]
}
如果索引已經存在,需要先關閉索引
POST medcl/_close
{
}
然後設定分詞器配置
PUT medcl/_settings
{
"index" : {
"analysis" : {
"analyzer" : {
"pinyin_analyzer" : {
"tokenizer" : "my_pinyin",
"filter" : ["word_delimiter","nGram"]
}
},
"tokenizer" : {
"my_pinyin" : {
"type" : "pinyin",
"first_letter" : "prefix",
"padding_char" : " "
}
}
}
}
}
打開索引
POST medcl/_open
{
}
定義映射類型
POST medcl/folks/_mapping
{
"folks": {
"properties": {
"name": {
"type": "multi_field",
"fields": {
"name": {
"type": "string",
"store": "no",
"term_vector": "with_positions_offsets",
"analyzer": "pinyin_analyzer",
"boost": 10
},
"primitive": {
"type": "string",
"store": "yes",
"analyzer": "keyword"
}
}
}
}
}
}
送出樣例資料
POST medcl/folks/1
{
"name":"劉德華"
}
執行查詢
GET medcl/folks/_search
{
"query": {"match": {
"name": "l d hua"
}}
}
這裡搜
liu de hua
ldh
l de hua
都能比對到,還是很強大滴。
得到結果
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 7.408082,
"hits": [
{
"_index": "medcl",
"_type": "folks",
"_id": "1",
"_score": 7.408082,
"_source": {
"name": "劉德華"
}
}
]
}
}
參考
- 1 IK github 位址
- 2 _all字段的作用
- 3 英文分詞算法
- 4 elastcisearch Mapping 文檔
作者:xingoo
出處:http://www.cnblogs.com/xing901022
本文版權歸作者和部落格園共有。歡迎轉載,但必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接!