1 文档规范化(normalization)
是为了提高召回率。
停用词 、时态转换、大小写、同义词、语气词。
以下的doc1\doc2,经过normalization之后,在搜索的时候是可以匹配到这两个doc。
我们可以看到,normalization就是把一些词 变成 通用的词。
#normalization
GET _analyze
{
"text": "Mr. Ma is an excellent teacher",
"analyzer": "standard"
}
#结果:Mr-》mr。Ma-》ma。等等。
2 字符过滤器(character filter)
分词之前的预处理,过滤无用字符
- HTML Strip Character Filter:html_strip。过滤掉html标签的。
- 参数:escaped_tags 需要保留的html标签
- Mapping Character Filter:type是mapping。可以将一些词替换成xxx。
- Pattern Replace Character Filter:type是pattern_replace。正则替换。
#character filter
##HTML Strip Character Filter
DELETE my_index
#创建分词器my_analyzer 和 过滤器my_char_filter
#把html标签过滤掉,保留a标签
PUT my_index
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter":{
"type":"html_strip",
"escaped_tags":["a"]
}
},
"analyzer": {
"my_analyzer":{
"tokenizer":"keyword",
"char_filter":["my_char_filter"]
}
}
}
}
}
GET my_index/_analyze
{
"analyzer": "my_analyzer",
"text": "<p>I'm so <a>happy</a>!</p>"
}
#结果:I'm so <a>happy</a>!
##Mapping Character Filter
DELETE my_index
#把一些词替换成*
PUT my_index
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter":{
"type":"mapping",
"mappings":[
"滚 => *",
"垃 => *",
"圾 => *"
]
}
},
"analyzer": {
"my_analyzer":{
"tokenizer":"keyword",
"char_filter":["my_char_filter"]
}
}
}
}
}
GET my_index/_analyze
{
"analyzer": "my_analyzer",
"text": "你就是个垃圾!滚"
}
#结果:你就是个**!*
##Pattern Replace Character Filter
#17611001200
DELETE my_index
#正则替换
PUT my_index
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter":{
"type":"pattern_replace",
"pattern":"(\\d{3})\\d{4}(\\d{4})",
"replacement":"$1****$2"
}
},
"analyzer": {
"my_analyzer":{
"tokenizer":"keyword",
"char_filter":["my_char_filter"]
}
}
}
}
}
GET my_index/_analyze
{
"analyzer": "my_analyzer",
"text": "您的手机号是17611001200"
}
#结果:您的手机号是176****1200
3 令牌过滤器(token filter)
停用词、时态转换、大小写转换、同义词转换、语气词处理等。比如:has=>have him=>he apples=>apple the/oh/a=>干掉。
https://www.elastic.co/guide/en/elasticsearch/reference/7.10/analysis-synonym-tokenfilter.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.10/analysis-keyword-marker-tokenfilter.html
(这里需要用到ik分词器)
在elasticsearch-7.10.0\config目录下新建analysis目录,新建synonym.txt,内容为
蒙丢丢 ==> 蒙
大G ==> 大
霸道 ==> 霸
daG ==> 大哥
#token filter
DELETE test_index
#在elasticsearch-7.10.0\config目录下新建analysis目录,新建synonym.txt
#创建了个同义词转换,使用的是文件
PUT /test_index
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym_graph",
"synonyms_path": "analysis/synonym.txt"
}
},
"analyzer": {
"my_analyzer": {
"tokenizer": "ik_max_word",
"filter": [ "my_synonym" ]
}
}
}
}
}
GET test_index/_analyze
{
"analyzer": "my_analyzer",
"text": ["蒙丢丢,大G,霸道,daG"]
}
#结果:蒙\大\霸\大哥
GET test_index/_analyze
{
"analyzer": "ik_max_word",
"text": ["奔驰G级"]
}
#结果:拆分成了 奔驰、g、级 词项
DELETE test_index
#创建了个同义词转换,没有使用文件
#赵,钱,孙,李 的同义词都是 吴
PUT /test_index
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": ["赵,钱,孙,李=>吴","周=>王"]
}
},
"analyzer": {
"my_analyzer": {
"tokenizer": "standard",
"filter": [ "my_synonym" ]
}
}
}
}
}
GET test_index/_analyze
{
"analyzer": "my_analyzer",
"text": ["赵,钱,孙,李","周"]
}
#结果:吴、王
#大小写转换
GET test_index/_analyze
{
"tokenizer": "standard",
"filter": ["lowercase"],
"text": ["AASD ASDA SDASD ASDASD"]
}
#结果:都变成了小写
GET test_index/_analyze
{
"tokenizer": "standard",
"filter": ["uppercase"],
"text": ["asdasd asd asg dsfg gfhjsdf asfdg g"]
}
#结果:都变成了大写
#复杂点的,使用脚本的:长度小于5的变成大写
GET test_index/_analyze
{
"tokenizer": "standard",
"filter": {
"type": "condition",
"filter":"uppercase",
"script": {
"source": "token.getTerm().length() < 5"
}
},
"text": ["asdasd asd asg dsfg gfhjsdf asfdg g"]
}
#停用词
DELETE test_index
#把me\you停用了
PUT /test_index
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "standard",
"stopwords":["me","you"]
}
}
}
}
}
GET test_index/_analyze
{
"analyzer": "my_analyzer",
"text": ["Teacher me and you in the china"]
}
#结果里边没有me\you
4 分词器(tokenizer)
主要是将document切词,切割点。
https://www.elastic.co/guide/en/elasticsearch/reference/7.10/analysis-standard-tokenizer.html
#分词器 tokenizer
#standard以空格进行切词的,对英文支持比较好
GET test_index/_analyze
{
"analyzer": "standard",
"text": ["Teacher me and you in the china"]
}
#结果:各个单词
GET test_index/_analyze
{
"tokenizer": "ik_max_word",
"text": ["我爱北京天安门","天安门上太阳升"]
}
#结果:把一些特定的词拆分,比较符合中文的习惯
5 常见分词器:
- standard analyzer:默认分词器,中文支持的不理想,会逐字拆分。
- pattern tokenizer:以正则匹配分隔符,把文本拆分成若干词项。
- simple pattern tokenizer:以正则匹配词项,速度比pattern tokenizer快。
- whitespace analyzer:以空白符分隔 Tim_cookie
6 自定义分词器:custom analyzer
- char_filter:内置或自定义字符过滤器 。
- token filter:内置或自定义token filter 。
- tokenizer:内置或自定义分词器。
#自定义分词器
DELETE custom_analysis
PUT custom_analysis
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "mapping",
"mappings": [
"& => and",
"| => or"
]
},
"html_strip_char_filter":{
"type":"html_strip",
"escaped_tags":["a"]
}
},
"filter": {
"my_stopword": {
"type": "stop",
"stopwords": [
"is",
"in",
"the",
"a",
"at",
"for"
]
}
},
"tokenizer": {
"my_tokenizer": {
"type": "pattern",
"pattern": "[ ,.!?]"
}
},
"analyzer": {
"my_analyzer":{
"type":"custom",
"char_filter":["my_char_filter","html_strip_char_filter"],
"filter":["my_stopword","lowercase"],
"tokenizer":"my_tokenizer"
}
}
}
}
}
GET custom_analysis/_analyze
{
"analyzer": "my_analyzer",
"text": ["What is ,<a>as.df</a> ss<div> in ? &</div> | is ! in the a at for "]
}
7 中文分词器:ik分词
自定义词库和热更新可以参考它的github主页介绍。
-
安装和部署
- ik下载地址:https://github.com/medcl/elasticsearch-analysis-ik
- Github加速器:https://github.com/fhefh2015/Fast-GitHub
- 创建插件文件夹 cd your-es-root/plugins/ && mkdir ik
- 将插件解压缩到文件夹 your-es-root/plugins/ik
- 重新启动es
-
IK文件描述
- IKAnalyzer.cfg.xml:IK分词配置文件
- 主词库:main.dic
- 英文停用词:stopword.dic,不会建立在倒排索引中
- 特殊词库:
- quantifier.dic:特殊词库:计量单位等
- suffix.dic:特殊词库:行政单位
- surname.dic:特殊词库:百家姓
- preposition:特殊词库:语气词
- 自定义词库:网络词汇、流行词、自造词等
自定义词库过程:
1、在elasticsearch-7.10.0\plugins\ik\config下创建custom目录,创建文件wxd_extra.dic、wxd_extra2.dic
2、文件内容
#wxd_extra.dic
蒙丢丢
#wxd_extra2.dic
渣男
渣女
3、修改配置文件IKAnalyzer.cfg.xml
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">custom\wxd_extra.dic;custom\wxd_extra2.dic</entry>
3、重启es,再次查询
GET custom_analysis/_analyze
{
"analyzer": "ik_max_word",
"text": ["蒙丢丢","渣男","渣女"]
}
#添加自定义词库之前结果:蒙、丢、丢、渣、男、渣、女
#添加自定义词库之后结果:蒙丢丢、渣男、渣女
-
ik提供的两种analyzer:
- ik_max_word 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
- ik_smart 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国、国歌”,适合 Phrase 查询。
8 ik热更新
- 远程词库文件
- 优点:上手简单
- 缺点:
- 词库的管理不方便,要操作直接操作磁盘文件,检索页很麻烦
- 文件的读写没有专门的优化性能不好
- 多一层接口调用和网络传输
- ik访问数据库
- MySQL驱动版本兼容性
- https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html
- https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-versions.html
- 驱动下载地址
- https://mvnrepository.com/artifact/mysql/mysql-connector-java
- MySQL驱动版本兼容性
8 ik热更新
目前该插件支持热更新 IK 分词,通过上文在 IK 配置文件中提到的如下配置
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">location</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<entry key="remote_ext_stopwords">location</entry>
其中 location
是指一个 url,比如 http://yoursite.com/getCustomDict
,该请求只需满足以下两点即可完成分词热更新。
- 该 http 请求需要返回两个头部(header),一个是
Last-Modified
,一个是ETag
,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。 - 该 http 请求返回的内容格式是一行一个分词,换行符用
\n
即可。
满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。
可以将需自动更新的热词放在一个 UTF-8 编码的 .txt 文件里,放在 nginx 或其他简易 http server 下,当 .txt 文件修改时,http server 会在客户端请求该文件时自动返回相应的 Last-Modified 和 ETag。可以另外做一个工具来从业务系统提取相关词汇,并更新这个 .txt 文件。
基于远程词库的热更新
- 优点:上手简单
- 缺点:
- 词库的管理不方便,要操作直接操作磁盘文件,检索页很麻烦
- 文件的读写没有专门的优化性能不好
- 多一层接口调用和网络传输
需要:一个普通的springboot项目(controller)+ 词库文件
@RestController
@RequestMapping(value = "/api")
public class HomeController {
@RequestMapping(value = "hotWord")
public void hotword(HttpServletResponse response, Integer wordlib) throws IOException {
File file = new File(wordlib == 1 ? "./files/wxd_extend.dic" : "./files/wxd_stopword.dic");
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
response.setContentType("text/plain;charset=utf-8");
response.setHeader("Last-Modified", String.valueOf(buffer.length));
response.setHeader("ETag", String.valueOf(buffer.length));
int offset = 0;
while (fis.read(buffer, offset, buffer.length - offset) != -1) {
}
OutputStream out = response.getOutputStream();
out.write(buffer);
out.flush();
fis.close();
}
}
在springboot项目的目录下新建files目录,里边分别有两个文件wxd_extend.dic、wxd_stopword.dic,此时都是空的
修改配置文件IKAnalyzer.cfg.xml
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://localhost:9081/api/hotWord?wordlib=1</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<entry key="remote_ext_stopwords">http://localhost:9081/api/hotWord?wordlib=2</entry>
在修改了wxd_extend.dic、wxd_stopword.dic之后,es会自动去请求远程字典的地址。
#文件内容都为空的时候
GET custom_analysis/_analyze
{
"analyzer": "ik_max_word",
"text": ["张明","太阳"]
}
#结果:张、明、太阳
#wxd_extend.dic中添加张明,es会加载文件内容,之后,再测测试:张、明 变成了 张明
#wxd_stopword.dic中添加太阳,es会加载文件内容,之后,再测测试:太阳 不再显示了
=非常重要的注意===文件一定要是UTF-8 编码的。
基于mysql的热更新
- MySQL驱动版本兼容性
- https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html
- https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-versions.html
- 驱动下载地址
- https://mvnrepository.com/artifact/mysql/mysql-connector-java
需要:ik的源码+ mysql
1、下载ik的源码,https://github.com/medcl/elasticsearch-analysis-ik/releases
2、pom中添加mysql连接,在config文件里边创建一个jdbc配置
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
#jdbc-reload.properties
jdbc.url=jdbc:mysql://localhost:3306/es_ik_use?serverTimezone=UTC
jdbc.user=root
jdbc.password=123456
jdbc.reload.sql=select word from ik_extword
jdbc.reload.stopword.sql=select stopword as word from ik_stopword
jdbc.reload.interval=1000
根据org.wltea.analyzer.dic.Dictionary 模仿写出通过jdbc的加载热数据
根据org.wltea.analyzer.dic.Monitor模仿写出jdbc的monitor
3、打包
把target/releases下的zip包,复制到elasticsearch-7.10.0\plugins下,解压,重新命名为ik。
再次检查jdbc-reload.properties的内容是否正确。
之后,把mysql的jar包放到ik文件里边。
4、启动es,测试