【java进阶】es实操:新闻的搜索(三)

72 阅读2分钟

一. 生成mapping

​ 分析数据库中每一条新闻记录,然后插入样本数据,目的是借助于ES的本身的能力生成一个mapping,样本数如下所示:

POST news/_doc
{
  "id": 1,
  "title": "中国",
  "url": "http://news.cctv.com/2019/11/30/ARTILTZNGoTs91B2LHQFqDJV191130.shtml?from=singlemessage",
  "content": "应急管理体系和能力现代化。",
  "tags": [
    "中国",
    "体系"
  ]
}

获取ES自动生成的mapping信息,然后粘贴都某个位置。


GET news/_mapping

删除对应的索引


DELETE news

二. 创建自定的分词器

因为我们要实现中文、拼音的前缀提示功能,所以需要根据 pinyin 分词器来定制我们自己的分词器:


PUT news
{
  "settings": {
    "analysis": {
      "analyzer": {
        "tag_analyzer": {
          "char_filter": "html_strip",
          "tokenizer": "keyword",
          "filter": "tag_filter"
        }
      },
      "filter": {
        "tag_filter": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_none_chinese": false,
          "keep_original": true
        }
      }
    }
  }
}

三. 定义mapping

根据第一步自动生成的mapping,进行对应性的修改,然后添加即可


PUT news/_mapping
{
  "properties" : {
    "content" : {
      "type" : "text",
       "analyzer": "ik_max_word",
       "search_analyzer": "ik_smart"
    },
    "id" : {
      "type" : "long"
    },
    "tags" : {
      "type" : "completion",
      "analyzer": "tag_analyzer",
      "search_analyzer": "keyword"
    },
    "title" : {
      "type" : "text",
      "analyzer": "ik_max_word",
      "search_analyzer": "ik_smart"
    },
    "url" : {
      "type" : "text",
      "analyzer": "keyword"
    }
  }
}
  1. title、content是中文,所以必须要指定一个中文的分词器;在数据写入的时候,使用 ik_max_word 让分词尽量多;在搜索的时候,尽量将搜索内容不要分的太细(尽量贴合用户想找的内容),所以使用 ik_smart.
  2. url地址没有必要分词,所以使用 keyword 分词;
  3. tags 这个属性我们需要用他来实现前缀提示,所以数据类型是 completion;analyzer是我们自定义的那个分词器;search_analyzer表示搜索的时候使用何种分词,对于前缀提示来说用户搜索的内容是没必要分词的,所以设置成 keyword;

四. 从数据库导入数据

第一步,将mysql的驱动放到 $LOGSTASH_HOME/logstash-core/lib/jars 放到这个下面

第二步,使用如下的脚本导入数据

input {
  jdbc {
    jdbc_driver_class => "com.mysql.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://localhost:3306/es?useSSL=false&serverTimezone=UTC"
    jdbc_user => root
    jdbc_password => "123456"
    statement => "SELECT * FROM news where tags is not null"
  }
}
​
filter {
  mutate {
    split => { "tags" => ","}
  }
}
output {
  elasticsearch {
    document_id => "%{id}"
    document_type => "_doc"
    index => "news"
    hosts => ["http://127.0.0.1:9200"]
  }
  stdout{
    codec => rubydebug
  }
}

导入的命令如下所示:


logstash -f ../config/mysql-news.conf

五. 前缀查询的使用

在 kibana 书写前缀提示的查询,内容如下:


GET news/_search
{
  "_source": false,
  "suggest": {
    "tag_suggest": {
      "prefix": "ly",
      "completion": {
        "field": "tags",
        "size": 10,
        "skip_duplicates": true
      }
    }
  }
}

六. 匹配查询

当用户在输入框输入内容之后进行查询,需要到 title 和 content中查询


GET news/_search
{
  "_source": ["id", "title", "url", "content"], 
  "query": {
    "multi_match": {
      "query": "儿童节",
      "fields": ["title", "content"]
    }
  },
  "highlight": {
    "pre_tags": "<span class='hl'>",
    "post_tags": "</span>",
    "fields": {
      "title": {},
      "content": {}
    }
  }
}

对于查询的结果有两点需要注意:

  1. title和content的高亮结果中可能没有数据,如果没有数据就需要从原本的数据中获取。
  2. 高亮结果的是一个数组,是截取的一个个片段(Segment).

七. 关于搜索的准确度问题

​ 因为IK在分词的时候,是根据已有词语进行分词处理,但是可能会出现一种情况,在我们的实际的工作中可能出现一些针对自己公司业务的词库没有被纳入到IK的词库,在数据写入到ES的过程中,拆分为一个个字,就会导致查询出现查询的结果准确有偏差。

​ IK提供了两种方式来扩展我们自己的业务词库:

  1. 静态的方式;
  2. 动态的方式;

7.1 静态方式

​ 所谓静态方式就是将项目中业务(没有被纳入到ik分词器中)词库添加到一个静态文件然后进行配置的方式。具体的做法如下:

第一步,在 $IK_HOME/config 目录下新建一个文件夹 user(可以叫其他的名字,但是必须是英文的)

第二步,在 第一步创建的 user 目录下创建一个文件,例如:customer.dic;名字可以随意,但是必须是英文,而且后缀必须是 dic

第三步,在 $IK_HOME/config/IKAnalyzer.cfg.xmlcustomer.dic 进行配置,配置方式如下:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
     <!-- <entry key="ext_dict">customer/customize.dic</entry> -->
     <!-- 配置上面创建的静态文件 -->
     <entry key="ext_dict">user/customer.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
        <!-- <entry key="ext_stopwords">stopword/keys.dir</entry> -->
    <entry key="ext_stopwords"></entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <!-- <entry key="remote_ext_dict"></entry> -->
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

第四步,每次往 customer.dic 增加新的词库,必须要重启 ES,新的词库才能生效。

第五步,验证新加入的词库是否被切割成一个个汉字。


# 需要保证这个新加的词没有被拆分为一个个的汉字。
GET _analyze
{
  "analyzer": "ik_smart",
  "text": ["网红"]
}

第六步,刷新已有被写入大ES的数据,让新加入的词库生效;因为新加入的词库只能影响之后写入到ES的数据,但是之前已经存在于ES中的数据,还是按照一个个字的方式构建的倒排索引。


POST news/_update_by_query
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "content": "网"
          }
        },
        {
          "match": {
            "content": "红"
          }
        }
      ]
    }
  }
}

静态的方式存在问题:每次新增加了业务词库,都需要重启ES,这种方式在实际的生产是不可取的。

7.2 动态的方式

​ 动态的方式就能够解决静态的方式每次需要重启ES,但是需要引入一个新的服务器;具体的实现方式如下所示:

第一步,需要搭建一个nginx服务器,在nginx的访问根目录下创建一个文件,例如名为:words.txt;一定要注意的是文件的保存的编码为 UTF-8; 启动nginx服务器,验证该文件是否可以访问。

第二步,需要在 $IK_HOME/config/IKAnalyzer.cfg.xml 文件中配置 words.txt 文件的服务器路径,配置如下所示:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
        <!-- <entry key="ext_dict">customer/customize.dic</entry> -->
        <entry key="ext_dict">user/customer.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
        <!-- <entry key="ext_stopwords">stopword/keys.dir</entry> -->
    <entry key="ext_stopwords"></entry>
    <!-- <entry key="remote_ext_dict"></entry> -->
    <!-- 配置远程扩展字典 -->
    <entry key="remote_ext_dict">http://localhost/words.txt</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

第三步,需要重启 ES。

第四步,每次往 words.txt 文件中加入新的词库之后,需要等一会,才能生效,在ES就可以验证这个新加的词库是否有效:


# 需要保证这个新加的词没有被拆分为一个个的汉字。
GET _analyze
{
  "analyzer": "ik_smart",
  "text": ["网红"]
}

第六步,刷新已有被写入大ES的数据,让新加入的词库生效;因为新加入的词库只能影响之后写入到ES的数据,但是之前已经存在于ES中的数据,还是按照一个个字的方式构建的倒排索引。