ES使用中文分词器IK插件介绍
🍎❗️注意一个前提:查询的字段必须是text类型的
注意一个前提:查询的字段必须是text类型的!❗️keyword类型的字段不支持match分词!!!
🍎❗️用到的Python脚本
import random
from datetime import datetime, timedelta
from elasticsearch import Elasticsearch
# 初始化 Elasticsearch 客户端
es = Elasticsearch("http://127.0.0.1:9200")
# 检查连接
es_ping_ret = es.ping()
print('es.ping():>>>>>>> ', es_ping_ret) # True / False
if not es_ping_ret:
raise ValueError("无法连接到 Elasticsearch")
# 定义索引名称
index_name = "student_v2"
### Notice 1确保索引存在,如果不存在则创建索引
# if not es.indices.exists(index=index_name):
# es.indices.create(index=index_name)
# print(f"索引 '{index_name}' 已创建")
### Notice 2、检查索引是否存在,不存在则创建并配置映射
if not es.indices.exists(index=index_name):
mapping = {
"mappings": {
"properties": {
"name": {
"type": "text",
# Notice ik分词配置~
"analyzer": "ik_max_word", # 索引时分词器(细粒度拆分)
"search_analyzer": "ik_smart" # 查询时分词器(粗粒度拆分)
},
# 其他字段配置 默认就好了
# "age": {"type": "integer"},
# "gender": {"type": "keyword"},
# "created_time": {"type": "date"}
}
}
}
es.indices.create(index=index_name, body=mapping)
print(f"索引 '{index_name}' 已创建(带中文分词器配置)")
# 生成随机数据
# names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Hank", "Ivy", "Jack"]
names = [
# 4字组合名
"林墨星辰", "夏晚知意", "江疏影然", "苏念安禾", "韩冷轩风",
"许若晴川", "邓语书瑶", "彭亦辰安", "蔡知南絮", "潘星辞月",
# 5字文艺名
"沈知夏眠风", "宋清川渡月", "唐晚舟听雨", "袁星落归林", "蒋云深见鹿",
"何疏影横斜", "魏青衫如故", "吕晚风枕星", "薛秋池映月", "叶随云归舟",
# 6字意境名
"罗山间雾起时", "高月下独酌酒", "谢庭前花未眠", "丁陌上草初荣", "姜檐下听春雨",
"崔松间鹤鸣远", "程渡口待人归", "白篱边菊正黄", "陆窗前雪初落", "金江上帆影斜",
# 7~9字特色名
"姚晚来风急送归舟", "钟疏林深处有人家", "卢青山郭外斜炊烟", "余小桥流水人家晚", "方古道西风白马行",
"石寒山寺外夜半钟", "戴杏花疏影里吹笛", "范长亭外古道边柳", "汪夕阳西下断肠人", "毛牧童遥指杏花村"
]
genders = ["male", "female"]
data = []
### Notice 时间格式化(注意它返回是一个tuple类型的数据)
# 默认是这种格式的: ('2024-09-21T11:38:54.364793',) <class 'tuple'>
# created_time = (datetime.now() - timedelta(days=random.randint(1, 365))).isoformat(),
# updated_time = datetime.now().isoformat(),
# 也可以指定时间的格式: ('2025-03-04 11:40:51.849562',) <class 'tuple'>
created_time = (datetime.now() - timedelta(days=random.randint(1, 365))).isoformat(sep=' '),
updated_time = datetime.now().isoformat(sep=' '),
print('created_time: ', created_time, type(created_time))
print('updated_time: ', updated_time, type(updated_time))
# for i in range(3):
for i in range(len(names)):
record = {
# "name": random.choice(names) + str(i),
"name": names[i],
"age": random.randint(18, 25),
"gender": random.choice(genders),
"score": str(random.randint(60, 100)),
"created_time": (datetime.now() - timedelta(days=random.randint(1, 365))).isoformat(),
"updated_time": datetime.now().isoformat(),
}
data.append(record)
# 插入数据到 Elasticsearch
for i, doc in enumerate(data):
# Notice 指定 _id 创建文档!!!
res = es.index(index=index_name, id=i + 1, document=doc)
# Notice 不指定 _id 创建文档!!!ES会自动生成!
# res = es.index(index=index_name, document=doc)
if res.get("result") == "created":
print(f"文档 {i + 1} 创建成功")
print("所有数据已插入完成!")
🍎 演示一下实际的例子再引出来主题
✅ 默认分词器的情况:student索引
1、使用我的本地docker搭建的ES实例,有一个student索引,先这样查的结果如下:
{
"query": {
"bool": {
"must": [
{"match": {"name": "古道西风"}}
]
}
}
}
### 结果把 古 道 西 风 这4个字相关的name都查出来了!
实际上 默认的ES的分词对中文支持不是很友好,我们可以看看分词结果:
POST - student/_analyze
{
"field": "name",
"text": "古道西风"
}
### 结果如下:
{
"tokens": [
{
"token": "古",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "道",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "西",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "风",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
}
]
}
✅ 使用中文IK插件的情况:student_v2索引
相同的查询语句:
{
"query": {
"bool": {
"must": [
{"match": {"name": "古道西风"}}
]
}
}
}
### 查出来的结果 相对来说比较符合要求
再看看分词:
POST - student_v2/_analyze
{
"field": "name",
"text": "古道西风"
}
{
"tokens": [
{
"token": "古道",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "西风",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
}
]
}
看看student_v2的mapping结构中name的定义
### ❗️需要安装 ik 插件
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
❗️❗️接下来就给大家详细介绍一下实际中如何下载、安装插件;安装完插件如何使用;
❗️❗️❗️还有一个非常重要的话题是:如果实际中我们student索引上线有一段时间了,这时候我们需要给ES安装IK与pinyin插件,安装完以后如何操作能让student索引中的name字段在查询的时候用上ik插件(大家都知道这涉及到改ES的mapping结构了,而且ES的mapping结构不能直接修改,这里需要用reindex操作~后面会详细给大家讲解具体的过程!)
🍎 下载、安装
下载
❗️除了中文,有时候还需要安装拼音插件,这里就一起讲了吧!
IK插件地址:https://github.com/infinilabs/analysis-ik
pinyin插件地址:https://github.com/infinilabs/analysis-pinyin
进入github主页后,通过引导页可以找到所有版本的安装包:
IK插件所有版本安装包:https://release.infinilabs.com/analysis-ik/stable/
pinyin插件所有版本安装包:https://release.infinilabs.com/analysis-pinyin/stable/
安装:这里用我本地docker搭建的ES演示下
安装完以后记得重启~
🍎 也可以查看ES集群安装了哪些插件
### 命令
_cat/plugins
# 结果样例
[
{
"name": "2662d67ab089",
"component": "analysis-ik",
"version": "7.16.2"
},
{
"name": "2662d67ab089",
"component": "analysis-pinyin",
"version": "7.16.2"
}
]
# 如果是集群多节点的话 结果如下
[
{
"name": "elasticsearch-master-2",
"component": "analysis-ik",
"version": "7.14.2"
},
{
"name": "elasticsearch-master-2",
"component": "analysis-pinyin",
"version": "7.14.2"
},
{
"name": "elasticsearch-master-1",
"component": "analysis-ik",
"version": "7.14.2"
},
{
"name": "elasticsearch-master-1",
"component": "analysis-pinyin",
"version": "7.14.2"
},
{
"name": "elasticsearch-master-0",
"component": "analysis-ik",
"version": "7.14.2"
},
{
"name": "elasticsearch-master-0",
"component": "analysis-pinyin",
"version": "7.14.2"
}
]
🍎❗️ 实际中对于老索引、老数据如何操作
❗️❗️需要注意下,并不是安装完分词器就万事大吉了!比如student这个索引的name字段,即使安装了IK分词器默认情况下查询是不用的。需要我们对name的mapping做一下修改,改成下面这样的:
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
——>但是,大家都知道,ES不支持动态更新mapping,我们需要使用reindex操作来“曲线救国”!
✅ 处理思路及流程(可以用我之前用go写的es-reindex那个工具)
❓❓❓下面只是简单的流程,详细操作需要再补充~
比如,实际中student索引中的name字段是text类型的,之前没有用分词器,现在给ES安装了分词器,需要给这个字段在查询的时候指定分词器就得修改mapping结构了!
1、先用工具生成一份当前索引mapping的 original_mapping.json 文件,然后将name字段改成带分词器的结果:
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
2、备份下原student索引:原student索引 reindex——> student_bak(临时索引)
3、删除原student索引
4、根据新的 mapping.json 文件 生成新的student索引(这个新的student的name字段是带分词配置的)
5、将 student_bak 中的老数据 reindex 到新的 student 索引中(记得验证下数据)
6、删除临时的 student_bak 索引