分词器是es中的一个组件,通俗意义上理解,就是将一段文本按照一定的逻辑,分析成多个词语,同时对这些词语进行常规化的一种工具;ES会将text格式的字段按照分词器进行分词,并编排成倒排索引,正是因为如此,es的查询才如此之快;
1 normalization 规范化
2 character filter 字符过滤器
分词之前的预处理,过滤无用字符
2.1 HTML Strip
PUT /define_index
{
"settings": {
"analysis": {
"char_filter": {
"define_char_filter": {
"type": "html_strip"
}
},
"analyzer": {
"define_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"define_char_filter"
]
}
}
}
}
}
自定义字符过滤器define_analyzer,作用是过滤数据中的html标签
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["<p>I; m so <a>happy</a></p>"]
}
可使用"escaped_tags":[“a”]设置保留不被过滤的标签
2.2 Mapping
PUT /define_index
{
"settings": {
"analysis": {
"char_filter": {
"define_char_filter": {
"type": "mapping",
"mappings": ["滚 => *", "垃圾 => 可爱"]
}
},
"analyzer": {
"define_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"define_char_filter"
]
}
}
}
}
}
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["<p>你好啊 你这个大垃圾, 滚犊子"]
}
通过自定义的过滤器,可以将聊天、留言或者弹幕之类的发言根据需求进行屏蔽或替换。
2.3 Pattern Replace
PUT /define_index
{
"settings": {
"analysis": {
"char_filter": {
"define_char_filter": {
"type": "pattern_replace",
"pattern": "(\\d{3})\\d{4}(\\d{4})",
"replacement": "$1****$2"
}
},
"analyzer": {
"define_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"define_char_filter"
]
}
}
}
}
}
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["电话号码:13792223536"]
}
通过正则表达式进行数据的替换。
3 token filter 令牌过滤器
停用词、时态转换、大小写转换、同义词转换、语气词处理等。
PUT /define_index
{
"settings": {
"analysis": {
"filter": {
"define_synonym": {
"type": "synonym",
"synonyms": [
"东邪 => 黄药师",
"西毒 => 欧阳锋",
"南帝 => 段智兴",
"北丐 => 洪七公"]
}
},
"analyzer": {
"define_analyzer": {
"tokenizer": "standard",
"filter": ["define_synonym"]
}
}
}
}
}
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["东邪", "西毒"]
}
可以看到"东邪"检索到"黄药师"的分词,"西毒"检索到"欧阳锋"的分词。
除了自定义的以外,也可以使用ES自带的,比如大小写的转换:
甚至可以通过自定义脚本动态的控制过滤逻辑,比如将长度小于5的字符串转为全大写:
GET /define_index/_analyze
{
"tokenizer": "standard",
"filter": {
"type": "condition",
"filter": "uppercase",
"script": {
"source": "token.getTerm().length() < 5"
}
},
"text": ["FeeNIXee IanlXD DOc dkkkk kkk ddd ds"]
}
也可以将一些语句中没有什么意义的语气词等作为停用词不参与检索:
PUT /define_index
{
"settings": {
"analysis": {
"analyzer": {
"define_analyzer": {
"type": "standard",
"stopwords": "_english_"
}
}
}
}
}
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["FeeNIXee IanlXD DOc and is or ds"]
}
也可以将一些语句中不管有没有意义所有的词都不作为停用词参与检索:
PUT /define_index
{
"settings": {
"analysis": {
"analyzer": {
"define_analyzer": {
"type": "standard",
"stopwords": "_none_"
}
}
}
}
}
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["FeeNIXee IanlXD DOc and is or ds"]
}
当然也可以手动自定义去设置那些词作为停用词使用:
PUT /define_index
{
"settings": {
"analysis": {
"analyzer": {
"define_analyzer": {
"type": "standard",
"stopwords": ["is", "or", "ds"]
}
}
}
}
}
GET /define_index/_analyze
{
"analyzer": "define_analyzer",
"text": ["FeeNIXee IanlXD DOc and is or ds"]
}
4 tokenizer 分词器
但是对于中文来说,standard也是非常简单粗暴的按照每一个汉字来进行切割,这就很难受了:
虽然ES官方提供了很多个分词器,其实对于中文的支持都不好用:
standard:默认分词器,中文会逐字拆分;
pattern:以正则匹配分隔符,把文本拆分成若干词项;
simple pattern:以正则匹配词项,速度比pattern会快一些;
whitespace:以空白符分隔,把文本拆分成若干词项;
既然官方提供的不好用,那就得自己手动来自定义分词器:
char_filter:内置或自定义字符过滤器;
token_filter:内置或自定义token过滤器;
tokenizer:内置或自定义分词器;
值得一提的是my_tokenizer的设置,“pattern”: "[,.!;?]"指的是文本会按照[]中有的符号去切割
4.1 ES安装IK分词器
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.10.0/elasticsearch-analysis-ik-7.10.0.zip
解压IK分词器安装包,重启ES服务
GET /define_index/_analyze
{
"analyzer": "ik_max_word",
"text": ["我爱中文,你是中国人吗"]
}
IK文件描述
IKAnalyzer.cfg.xml:IK分词器配置文件
main.dic:IK分词器主词库,也是IK最大的词库,收录了20多万常用的汉语词汇
stopword.dic:英文停用词库,不会建立在倒排索引中
quantifier.dic:特殊词库,计量单位
suffix.dic:特殊词库,行政、地理单位
surname.dic:特殊词库,百家姓
preposition.dic:特殊词库,语气词
根据IK分词器官方提供的文档,提供了两个analyzer和两个tokenizer,都是ik_smart和ik_max_word。
在官方词库中没有 蒙丢丢 如下所示,被拆分了
我们可以修改配置文件来解决问题
调整文件:
新建文档
5 热更新
5.1 基于远程词库的热更新
每次在修改完IK分词器的配置文件之后,都需要对ES服务进行重新启动。
现在每天网络热门词汇层出不穷,按照词汇更新的速度频率经常重启ES服务显示是不可能的。在新版本中,IK分词器官方可以支持配置远程扩展字典和扩展停止词字典:
并且官方也提供了非常详细的使用步骤说明:
package com.inspur.elasticsearch.controller;
import lombok.val;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* IK热更新控制器
*
* @author zhaoshuai-lc
* @date 2023/02/07
*/
@RestController
@RequestMapping(value = "/api")
public class HotUpdateController {
/**
* type - 0
*/
private static final Integer HOT_WORD = 0;
/**
* 刷新词库
*
* @param type 词库类型
* @param response 响应
*/
@RequestMapping(value = "/refreshThesaurus")
public void refreshThesaurus(HttpServletResponse response, Integer type) {
try {
val path = HOT_WORD.equals(type) ? "src/main/resources/hotUpdateWord/hotWord.dic" :
"src/main/resources/hotUpdateWord/stopWord.dic";
val file = new File(path);
val fileInputStream = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
int offset = 0;
while (-1 != fileInputStream.read(buffer, offset, buffer.length - offset)) {
}
response.setHeader("Last-Modified", String.valueOf(buffer.length));
response.setHeader("ETag", String.valueOf(buffer.length));
response.setContentType("text/plain;charset=utf-8");
val outputStream = response.getOutputStream();
outputStream.write(buffer);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
nginx的相关知识补充,不然不会起作用
5.2 基于MySQL数据库的热更新
https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0
https://blog.csdn.net/qq_24223159/article/details/124691360