基于 IKAnalyzer 实现 Elasticsearch 中文分词插件

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介:

虽然Elasticsearch有原生的中文插件elasticsearch-analysis-smartcn(实际上是lucence的org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer),但它似乎没能满足我的要求。比如我希望对文档中的“林夕”不分词(就是不要把它当成“林”,“夕”两个字索引),smartcn没法做到。

然后我找到了IK,以及elasticsearch-analysis-ik。elasticsearch-analysis-ik已经有些时候没人维护了。而且它使用的httpclient来获取分词词典。总之各种纠结。

最后,我决定还是自己写一个吧。

原来IKAnalyzer的目录结构

├── IKAnalyzer.cfg.xml
    ├── ext.dic
    ├── org
    │   └── wltea
    │       └── analyzer
    │           ├── cfg
    │           │   ├── Configuration.java
    │           │   └── DefaultConfig.java
    │           ├── core
    │           │   ├── AnalyzeContext.java
    │           │   ├── CJKSegmenter.java
    │           │   ├── CN_QuantifierSegmenter.java
    │           │   ├── CharacterUtil.java
    │           │   ├── IKArbitrator.java
    │           │   ├── IKSegmenter.java
    │           │   ├── ISegmenter.java
    │           │   ├── LetterSegmenter.java
    │           │   ├── Lexeme.java
    │           │   ├── LexemePath.java
    │           │   └── QuickSortSet.java
    │           ├── dic
    │           │   ├── DictSegment.java
    │           │   ├── Dictionary.java
    │           │   ├── Hit.java
    │           │   ├── main2012.dic
    │           │   └── quantifier.dic
    │           ├── lucene
    │           │   ├── IKAnalyzer.java
    │           │   └── IKTokenizer.java
    │           ├── query
    │           │   ├── IKQueryExpressionParser.java
    │           │   └── SWMCQueryBuilder.java
    │           ├── sample
    │           │   └── IKAnalyzerDemo.java
    │           └── solr
    │               └── IKTokenizerFactory.java
    └── stopword.dic

加入构建脚本

我发现没有使用任何的构建工具。我不是说不使用构建工具就是不好,而是我已经习惯了使用构建工具,不用就没有安全感。所以,我第一步是给它加构建脚本。

同时,我把原来的IKAnalyzerDemo.java改成两个测试类。最后运行测试,确保我的修改没有破坏原有逻辑

└── src
        ├── main
        │   ├── java
        │   │   └── ......
        │   └── resources
        │       ├── IKAnalyzer.cfg.xml
        │       ├── main2012.dic
        │       ├── quantifier.dic
        │       └── stopword.dic
        └── test
            ├── java
            │   └── org
            │       └── wltea
            │           └── analyzer
            │               ├── IKAnalzyerTest.java
            │               └── LuceneIndexAndSearchTest.java
            └── resources
                ├── IKAnalyzer.cfg.xml
                ├── main2012.dic
                ├── quantifier.dic
                └── stopword.dic
build.gradle

                apply plugin: 'java'

        //apply plugin: 'checkstyle'
        apply plugin: 'idea'

        sourceCompatibility = 1.7
        version = '1.0'

        repositories {
            mavenCentral()
        }

        dependencies {
            compile(
                    'org.apache.lucene:lucene-core:4.10.4',
                    'org.apache.lucene:lucene-queryparser:4.10.4',
                    'org.apache.lucene:lucene-analyzers-common:4.10.4'
            )

            testCompile group: 'junit', name: 'junit', version: '4.11'
        }

将项目拆成core和lucence两个子项目

我发现IK实际上由两部分组成:真正的分词逻辑和扩展Lucence分析器的逻辑。可以想象得到

我们需要支持不同版本的Lucence
我们可以把IK的分词逻辑应用到其它的搜索引擎上
基于这两点,我决定把原有的项目分成两个子项目。并加上测试:

├── build.gradle
        ├── ik-analyzer-core
        │   ├── build.gradle
        │   └── src
        │       ├── main
        │       │   ├── java
        │       │   │   └── .....
        │       │   └── resources
        │       └── test
        ├── ik-analyzer-lucence
        │   ├── build.gradle
        │   └── src
        │       ├── main
        │       │   └── java
        │       │       └── org
        │       │           └── wltea
        │       │               └── analyzer
        │       │                   ├── lucene
        │       │                   │   ├── IKAnalyzer.java
        │       │                   │   └── IKTokenizer.java
        │       │                   └── query
        │       │                       ├── IKQueryExpressionParser.java
        │       │                       └── SWMCQueryBuilder.java
        │       └── test
        │           ├── java
        │           │   └── .....
        └── settings.gradle

创建Elasticsearch插件

一开始,我还想让Elasticsearch插件只依赖core子项目就好了。谁知道要实现Elasticsearch的插件还需要依赖Lucence。所以Elasticsearch插件需要依赖lucence子项目。

实现的过程发现Elasticsearch的版本之间有些不同,你可以对比下: AnalysisIkPlugin.java和IKAnalyzerPlugin.java

目前,Elasticsearch文档中,关于它的插件的概念和原理说的都非常少!

├── build.gradle
    ├── ik-analyzer-core
    │   ├── ......
    ├── ik-analyzer-elasticseaarch-plugin
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           ├── java
    │           │   └── org
    │           │       └── elasticsearch
    │           │           └── plugin
    │           │               └── ikanalyzer
    │           │                   ├── IKAnalyzerComponent.java
    │           │                   ├── IKAnalyzerModule.java
    │           │                   └── IKAnalyzerPlugin.java
    │           └── resources
    │               └── es-plugin.properties
    ├── ik-analyzer-lucence
    │   ├── .....
    └── settings.gradle
## es-plugin.properties

plugin=org.elasticsearch.plugin.ikanalyzer.IKAnalyzerPlugin

重构Core子项目

目前IK还有一个问题没有解决:灵活扩展现有的词典。比如我希望将“林夕”加入词典,从而使其不分被索引成“林”,“夕”。这样的应用场景非常多的。以至于elasticsearch-analysis-ik自己实现从远程读取词典的功能:Dictionary.java:338

但是我觉得这样还是够好。比如,我期望从本地的sqlite中读取词典呢?所以,我将IK原有的关于配置的读取的逻辑抽取出来:

/**
         * 加载主词典及扩展词典
         */
        private void loadMainDict(){
                //建立一个主词典实例
                _MainDict = new DictSegment((char)0);
                //读取主词典文件
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(cfg.getMainDictionary());
        if(is == null){
                throw new RuntimeException("Main Dictionary not found!!!");
        }

                try {
                        BufferedReader br = new BufferedReader(new InputStreamReader(is , "UTF-8"), 512);
                        String theWord = null;
                        do {
                                theWord = br.readLine();
                                if (theWord != null && !"".equals(theWord.trim())) {
                                        _MainDict.fillSegment(theWord.trim().toLowerCase().toCharArray());
                                }
                        } while (theWord != null);

                } catch (IOException ioe) {
                        System.err.println("Main Dictionary loading exception.");
                        ioe.printStackTrace();

                }finally{
                        try {
                                if(is != null){
                    is.close();
                    is = null;
                                }
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
                }
                //加载扩展词典
                this.loadExtDict();
        }

其中cfg.getMainDictionary(),cfg是一个接口Configuration的实例,但是Dictionary假设getMainDictionary返回的一个文件的路径。所以,我认为这个接口的设计是没有意义的。

我们为什么不让cfg.getMainDictionary()直接返回Dictionary要求的词典内容呢,像这样:

/**
     * 加载主词典及扩展词典
     */
    private void loadMainDict() {
        //建立一个主词典实例
        _MainDict = new DictSegment((char) 0);
        for (char[] segment : cfg.loadMainDictionary()) {
            _MainDict.fillSegment(segment);

        }
    }

这样,我们就可以实现像FileConfiguration,HttpConfiguraion,SqliteConfiguration,RedisConfiguration等任何你期望的扩展词典方式了。

但是,目前,我还没有实现任何的一种 :P

小结

实际上的重构和本文写的相差无几。还算比较顺利,要感谢原作者。这里,我还有一个问题没想通,就是如何打包才能让大家都方便用,比如方便在Elasticsearch中安装。希望大家能多给建议。

文章转载自 开源中国社区 [http://www.oschina.net]

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
29天前
|
自然语言处理 API 索引
Elasticsearch Analyzer原理分析并实现中文分词
Elasticsearch Analyzer原理分析并实现中文分词
76 0
|
29天前
ElasticSearch-Head浏览器插件离线安装
ElasticSearch-Head浏览器插件离线安装
125 0
|
8月前
|
人工智能 数据可视化 Java
ElasticSearch安装、插件介绍及Kibana的安装与使用详解
ElasticSearch安装、插件介绍及Kibana的安装与使用详解
ElasticSearch安装、插件介绍及Kibana的安装与使用详解
|
29天前
|
安全 大数据 Java
elasticsearch|大数据|低版本的elasticsearch集群的官方安全插件x-pack的详解
elasticsearch|大数据|低版本的elasticsearch集群的官方安全插件x-pack的详解
67 0
|
29天前
|
自然语言处理 算法 索引
Elasticsearch 8.X 分词插件版本更新不及时解决方案
Elasticsearch 8.X 分词插件版本更新不及时解决方案
26 0
|
29天前
|
自然语言处理 Java Shell
Elasticsearch【环境搭建 01】【elasticsearch-6.4.3 单机版】(含 安装包+分词插件 云盘资源)
【4月更文挑战第12天】Elasticsearch【环境搭建 01】【elasticsearch-6.4.3 单机版】(含 安装包+分词插件 云盘资源)
30 2
|
10月前
|
自然语言处理 Java Maven
Elasticsearch系列——安装中文分词插件elasticsearch-analysis-ik
Elasticsearch系列——安装中文分词插件elasticsearch-analysis-ik
|
29天前
|
自然语言处理 Java 关系型数据库
Elasticsearch【环境搭建 01】elasticsearch-6.4.3 单机版不能以root用户运行es 及 max_map_count 问题解决(含 安装包+分词插件 云盘资源)
Elasticsearch【环境搭建 01】elasticsearch-6.4.3 单机版不能以root用户运行es 及 max_map_count 问题解决(含 安装包+分词插件 云盘资源)
33 0
|
10月前
|
SQL AliSQL API
阿里云ElasticSearch安装开源插件实践
阿里云ElasticSearch安装开源插件实践,包含较为常用的NLPchina/elasticsearch-sql与medcl/elasticsearch-analysis-stconvert两个插件的安装、使用测试及同类插件的差异对比
|
10月前
|
存储 自然语言处理 搜索推荐
Elasticsearch插件管理(ik分词器、附件文本抽取插件)
Elasticsearch插件管理(ik分词器、附件文本抽取插件)
208 0