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 索引

posted on 2025-09-07 12:28  江湖乄夜雨  阅读(89)  评论(0)    收藏  举报