1. 多字段特性
相关阅读:https://www.elastic.co/guide/en/elasticsearch/reference/7.4/mapping-params.html
默认情况 ElasticSearch 会对 text 类型字段默认增加 keyword 类型子字段,keyword 类型是 Exact Values,即精确值,仅支持精确匹配查询。Exact Values 不会被 ElasticSearch 做分词处理,而是直接索引。而 text 类型是全文本,即非结构化的文本数据。
如下增加索引 users 的 mapping 定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| PUT users { "mappings": { "properties": { "name": { "type": "text", "fields": { "keyword": { "type": "keyword" // 增加名称为 keyword 的 keyword 类型子字段 } } }, "address": { "type": "text", "fields": { "other_address": { // 子字段 other_address "type": "text", "analyzer": "stop", // 倒排索引中采用 stop analyzer 分词 "search_analyzer": "whitespace" // 检索时对检索文本采用 whitespace analyzer 分词 } } } } } }
|
添加如下文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| PUT users/_doc/1 { "name": "Hello ShotoZheng", "address": "fish Eating, I'M Loser" }
// 无法被检索到 POST users/_search { "query": { "match": { "name.keyword": "ShotoZheng" } } }
// 可以被检索到 POST users/_search { "query": { "match": { "name.keyword": "Hello ShotoZheng" } } }
|
2. search_analyzer
前面的示例中我们对子字段 other_address 设置 stop analyzer,并设置 search_analyzer 为 whitespace。首先我们要理解如下概念:
- analyzer:插入文档时,将text类型的字段做分词然后插入倒排索引。
- search_analyzer:查询时,先对要查询的text类型的输入做分词,再去倒排索引中搜索。
如上示例中,我们采用的是 stop analyzer,那么我们可以对 address.other_address 字段所使用的 stop analyzer 分析其在倒排索引中存储的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| GET /users/_analyze { "field": "address.other_address", // 使用 address.english_address 的 analyzer 进行分析 "text": "fish Eating, I'M Winer" }
// 分析结果 { "tokens" : [ { "token" : "fish", "start_offset" : 0, "end_offset" : 4, "type" : "<ALPHANUM>", "position" : 0 }, { "token" : "eating", "start_offset" : 5, "end_offset" : 11, "type" : "<ALPHANUM>", "position" : 1 }, { "token" : "i'm", "start_offset" : 13, "end_offset" : 16, "type" : "<ALPHANUM>", "position" : 2 }, { "token" : "winer", "start_offset" : 17, "end_offset" : 22, "type" : "<ALPHANUM>", "position" : 3 } ] }
|
如下我们去检索 address.other_address 包含 I'M winer
的文档,由于我们设置 search_analyzer 成 whitespace,我们 I'M winer
会被分词成 I'M
和 winer
,然后依次去检索倒排索引,很明显能够匹配到 winer
,所以如下示例能够检索到匹配到的文档:
1 2 3 4 5 6 7 8
| POST users/_search { "query": { "match": { "address.other_address": "I'M winer" } } }
|
我们采用如下示例则检索不到匹配的文档,因为倒排索引中不存在 Winer
:
1 2 3 4 5 6 7 8
| POST users/_search { "query": { "match": { "address.other_address": "I'M Winer" } } }
|
需要说明的是,默认情况下如果我们不设置 search_analyzer,其默认与 analyzer 所设置的一致。
3. 自定义分词
相关阅读:https://www.elastic.co/guide/en/elasticsearch/reference/7.4/analysis-analyzers.html
当 ElasticSearch 自带的分词器无法满足时,可以自定义分词器。通过自组合不同的组件实现。
- Character Filters
- Tokenizer
- Token Filter
3.1 Tokenizer
Tokenizer 将原始的文本按照一定的规则切分为词(term or tolen),ElasticSearch 内置了 whitespace 、standard、keyword、path hierarchy、uax_url_emain 和 pattern 等 Tokenizer 。当然也可以使用 Java 开发插件实现自己的 Tokenizer。
如下演示 whitespace tokenizer 实现按空格对文本进行分词:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| POST _analyze { "tokenizer": "whitespace", "text": "123-456, I-test! test-990 650-555-1234" }
// 分词结果 { "tokens" : [ { "token" : "123-456,", "start_offset" : 0, "end_offset" : 8, "type" : "word", "position" : 0 }, { "token" : "I-test!", "start_offset" : 9, "end_offset" : 16, "type" : "word", "position" : 1 }, { "token" : "test-990", "start_offset" : 17, "end_offset" : 25, "type" : "word", "position" : 2 }, { "token" : "650-555-1234", "start_offset" : 26, "end_offset" : 38, "type" : "word", "position" : 3 } ] }
|
3.2 Character Filters
在 Tokenizer 之前对文本进行处理,例如增加删除及替换字符。可以配置多个 Character Filters。会影响 Tokenizer 的 position 和 offset 信息。ElasticSearch 自带了如下三种 Character Filters:
- HTML strip:去除 html 标签
- Mapping:字符串替换
- Pattern replace:正则匹配替换
如下演示几个示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| // 使用char filter进行替换 POST _analyze { "tokenizer": "standard", "char_filter": [ { "type" : "mapping", "mappings" : [ "- => _"] } ], "text": "123-456, I-test! test-990 650-555-1234" }
// char filter 替换表情符号 POST _analyze { "tokenizer": "standard", "char_filter": [ { "type" : "mapping", "mappings" : [ ":) => happy", ":( => sad"] } ], "text": ["I am felling :)", "Feeling :( today"] }
//正则表达式 GET _analyze { "tokenizer": "standard", "char_filter": [ { "type" : "pattern_replace", "pattern" : "http://(.*)", "replacement" : "$1" } ], "text" : "http://www.elastic.co" } // 正则执行结果 { "tokens" : [ { "token" : "www.elastic.co", // 剔除了 http:// "start_offset" : 0, "end_offset" : 21, "type" : "<ALPHANUM>", "position" : 0 } ] }
|
3.3 Token Filters
Token Filters 将 Tokenizer 输出的单词(term)进行增加、修改和删除。ElasticSearch 自带有 lowecase、stop 和 synonym 等 Token Filters。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| // 对 text 按 whitespace 分词后,再进行转小写和去除停用词的处理,例如去除 in、are、this等 GET _analyze { "tokenizer": "whitespace", "filter": ["lowercase","stop"], "text": ["The gilrs in China are playing this game!"] } // 分析结果 { "tokens" : [ { "token" : "gilrs", "start_offset" : 4, "end_offset" : 9, "type" : "word", "position" : 1 }, { "token" : "china", "start_offset" : 13, "end_offset" : 18, "type" : "word", "position" : 3 }, { "token" : "playing", "start_offset" : 23, "end_offset" : 30, "type" : "word", "position" : 5 }, { "token" : "game!", "start_offset" : 36, "end_offset" : 41, "type" : "word", "position" : 7 } ] }
|
3.4 Custom Analyzer
可以在 settings 中设置自定义自己的分析器,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| // 定义自定义分析器 my_custom_analyzer PUT my_index { "settings": { "analysis": { "analyzer": { "my_custom_analyzer": { "type": "custom", "tokenizer": "standard", // standard 分词处理 "char_filter": [ "html_strip" // 剔除 html 文本 ], "filter": [ "lowercase" // 分词转小写 ] } } } } }
// 使用自定义分析器 POST my_index/_analyze { "analyzer": "my_custom_analyzer", "text": "Is this <b>déjà vu</b>?" }
// 分析结果 { "tokens" : [ { "token" : "is", "start_offset" : 0, "end_offset" : 2, "type" : "<ALPHANUM>", "position" : 0 }, { "token" : "this", "start_offset" : 3, "end_offset" : 7, "type" : "<ALPHANUM>", "position" : 1 }, { "token" : "déjà", "start_offset" : 11, "end_offset" : 15, "type" : "<ALPHANUM>", "position" : 2 }, { "token" : "vu", "start_offset" : 16, "end_offset" : 22, "type" : "<ALPHANUM>", "position" : 3 } ] }
|