elastic-search学习笔记

27 篇文章 1 订阅
1 篇文章 0 订阅

es的起源

Elasticsearch是一个实时分布式搜索和分析引擎,它让你以前所未有的速度处理大数据。es常用于全文搜索、结构化搜索、分析以及将这三者混合使用,它是当下业界最流行的开源搜索框架。很多流行的网站采用es来提供搜索功能。

lucene

说到搜索领域,不得不提Doug Cutting。他开发了lucene,一个用于文本搜索的函数库。2004年,Doug Cutting再接再励,在Lucene的基础上,和Apache开源伙伴Mike Cafarella合作,开发了一款可以代替当时的主流搜索的开源搜索引擎,命名为Nutch。Nutch是一个建立在Lucene核心之上的网页搜索应用程序,可以下载下来直接使用。它在Lucene的基础上加了网络爬虫和一些网页相关的功能,目的就是从一个简单的站内检索推广到全球网络的搜索上,就像Google一样。

后来,Doug Cutting加盟Yahoo,基于google和业界的一些研究成果,开创了流行的大数据框架hadoop。

es

Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

Shay Banon基于lucene开发了Elasticsearch。

solr

搭建es

windows环境

安装es

1解压安装

es7相比es6的更新较大,我们选择使用较新的es7。直接在es官网下载7.14.1安装包,解压即用。

2目录介绍

  • bin 启动目录

  • conf 配置目录

    • log4j2 日志配置
    • jvm.options jvm相关的配置
    • elasticsearch.yml es服务器的配置,默认9200端口
  • lib 相关jar包

  • modules 功能模块

  • plugins 插件,比如ik分词器

3启动

执行elasticsearch.bat

4访问

127.0.0.1:9200

img

安装head master

head master是一款es可视化界面插件。尽管它也提供了一些操作es的接口,但一般我们仅使用它浏览数据。

npm install  #安装必须的前端依赖

npm start  #启动插件,在package.json目录运行此命令

尽管安装依赖显示失败,但是显示启动成功就访问试试吧。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8LCaTVg4-1656686798061)(.\es-photo\启动head-master.jpg)]

访问head master

http://localhost:9100

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPVTrpO5-1656686798062)(.\es-photo\访问head-master.jpg)]

图中看不到es集群的信息,这是因为端口9100和9200之间存在端口跨域问题。

跨域问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKicajF2-1656686798063)(.\es-photo\head-master跨域问题.jpg)]

在elasticsearch.yml中添加跨域配置,重启es服务

http.cors.enabled: true

http.cors.allow-origin: "*"

再次访问head-master,可以新建索引测试一下能否使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1UDfxt5n-1656686798064)(.\es-photo\访问head-master2.jpg)]

安装kibana

  1. 官网下载kibana安装包并解压

  2. 进入bin目录,启动服务即可。ELK基本都是拆箱即用的

  3. 访问页面

kibana会自动去访问9200,也就是elasticsearch的端口号,我们直接访问kibana即可

http://ip:5601
  1. 操作es

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A0W5mR3b-1656686798065)(.\es-photo\kibana开发工具.jpg)]

  1. 汉化

中文包在 kibana\x-pack\plugins\translations\translations\zh-CN.json,只需要在配置文件 kibana.yml 中加入

i18n.locale: “zh-CN”

重启查看效果!成功切换为中文的了!

错误记录

第一次启动kibana7.14.1时,报错7.14.1的kibana与7.7.1的es版本不匹配。可使我启动的es也是7.7.1啊,不管报错了。我直接重启了一个7.7.1的kibana,然后可以正常使用kibana。

ik分词器

这个后面操作es时再用也行

1下载

https://github.com/medcl/elasticsearch-analysis-ik

2放到es的plugins下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLjoqO7F-1656686798066)(.\es-photo\配置ES分词器ik.jpg)]

3重启es

观察日志,可以看到分词器被加载了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fl33YIKf-1656686798067)(.\es-photo\加载ik.jpg)]

也可以执行elasticsearch-plugin list 命令,确认分词插件安装成功

ES概念

基本概念

Relational DBElasticsearch备注
数据库(database)索引(indices)
表(tables)typestype在新版es中已被废除
行(rows)documents
字段(columns)fields

索引

是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。 然后它们被存储到了各个分片上了。 我们来研究下分片是如何工作的。

类型

新版本已不再使用,无须过多关注

文档

之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个 重要属性 :

1自我包含,一篇文档同时包含字段和对应的值,也就是同时包含 key:value!

2可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的!

3灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。

尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符 串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。

分片

一个集群至少有一个节点,而一个节点就是一个elasricsearch进程。创建索引时,需要指定分片数和副本数。默认会有个5个分片 ( primary shard ,又称主分片 ) ,每一个主分片有一个副本 ( replica shard ,又称复制分片 )

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQiR4cAE-1656686798068)(.\es-photo\es集群.jpg)]

上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉 了,数据也不至于丢失。 实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使 得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。

倒排索引

elasticsearch使用的是一种称为倒排索引的结构,采用Lucene倒排索作为底层。这种结构适用于快速的全文搜索, 一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。 例如,现在有两个文档, 每个文档包含如下内容:

Study every day, good good up to forever # 文档1包含的内容 

To forever, study every day, good good up # 文档2包含的内容

为了创建倒排索引,我们首先要将每个文档拆分成独立的词(这也是es需要分词器的原因),然后创建一个包含所有不重 复的词条的排序列表,然后列出每个词条出现在哪个文档 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6F9lhEPx-1656686798068)(file:///C:_docs\wendyma\AppData\Local\Temp\ksohtml24892\wps12.jpg)]

现在,我们试图搜索 to forever,只需要查看包含每个词条的文档

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6DTVDIE-1656686798069)(file:///C:_docs\wendyma\AppData\Local\Temp\ksohtml24892\wps13.jpg)]

es有一套根据匹配程度进行打分的算法,分数越高,搜索越靠前。再来看一个示例,比如我们通过博客标签来搜索博客文章。那么倒排索引列表就是这样的一个结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqLrwaYP-1656686798071)(.\es-photo\倒排索引.jpg)]

如果要搜索含有 python 标签的文章,那相对于查找所有原始数据而言,查找倒排索引后的数据将会快的多。只需要 查看标签这一栏,然后获取相关的文章ID即可。

综上,es提供数据查找功能之前,需要先为所有数据创建索引。数据量越大,维持索引的数据也就越大,这也是es需要较大磁盘空间的原因。

ik分词器

分词:即把一段中文或者别的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如 “我爱菜鸟” 会被分为"我",“爱”,“菜”,“鸟”,这显然是不符合要求的,所以我们需要安装中文分词器ik来解决这个问题。

IK提供了两个分词算法:ik_smart 和 ik_max_word。其中 ik_smart 为最少切分,会将文本拆分为一个词;ik_max_word为最细粒度划分,会将文本拆分为尽可能多的词。

kibana测试

ik_smart拆分

GET _analyze
{
 "analyzer": "ik_smart",
 "text": "中国共产党"
}

响应

{
    "tokens": [
        {
            "token": "中国共产党",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}

ik_max_word拆分

请求

GET _analyze
{
    "analyzer": "ik_max_word",
    "text": "中国共产党"
}

响应

{
    "tokens": [
        {
            "token": "中国共产党",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "中国",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "国共",
            "start_offset": 1,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "共产党",
            "start_offset": 2,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 3
        },
        {
            "token": "共产",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 4
        },
        {
            "token": "党",
            "start_offset": 4,
            "end_offset": 5,
            "type": "CN_CHAR",
            "position": 5
        }
    ]
}

自定义分词

如果我们想让系统识别“菜鸟”是一个词,需要编辑自定义词库。步骤

1进入elasticsearch/plugins/ik/config目录

2编写自己的字典文件

新建一个my.dic文件,编辑内容:

菜鸟

3修改IKAnalyzer.cfg.xml(在ik/config目录下)

<properties> 
  <comment>IK Analyzer 扩展配置</comment> 
  <!-- 用户可以在这里配置自己的扩展字典 --> 
  <entry key="ext_dict">my.dic</entry> 
  <!-- 用户可以在这里配置自己的扩展停止词字典 --> 
  <entry key="ext_stopwords"></entry> 
</properties> 

修改完配置重新启动elasticsearch即可

ES的基本使用

可以通过restful风格的接口操纵es

methodurl地址操作类型描述
PUTlocalhost:9200/索引/类型/文档id创建文档(指定id)
POSTlocalhost:9200/索引/类型创建文档(随机id)
DELETElocalhost:9200/索引/类型/文档id删除文档
POSTlocalhost:9200/索引/类型/文档id/_update修改文档
POSTlocalhost:9200/索引/类型/_search查询所有数据
GETlocalhost:9200/索引/类型/文档id通过id查询文档

注意,因为类型在新版ES中已经弃用,所以采用默认的唯一类型 _doc 即可。

索引操作

增加

因为types在新版es中已被废除,所以默认为_doc

请求

PUT /test1/_doc/1 
{
    "name": "菜鸟",
    "age": 16
}

响应

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bZ5Hk4Up-1656686798072)(file:///C:_docs\wendyma\AppData\Local\Temp\ksohtml24892\wps15.jpg)]

索引test1相当于关系型数据库的库,类型_doc就相当于表 ,1 代表数据中的主键 id

指定字段类型创建

PUT /test2

{
    "mappings": {
        "properties": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "long"
            },
            "birthday": {
                "type": "date"
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xcFkiHjM-1656686798072)(file:///C:_docs\wendyma\AppData\Local\Temp\ksohtml24892\wps16.jpg)]

获取

GET test2

GET /test1/_doc/1

GET _cat/health //查看健康值

GET _cat/indices?v //查看所有索引的信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXg96KMu-1656686798073)(.\es-photo\查看所有索引.jpg)]

更新

方式1

这种方式需要用完整数据覆盖原数据。比如如果不指定age,更新后会变为null

PUT /test1/_doc/1 
{
    "name": "小傻瓜",
    "age": 16
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utUKg2DE-1656686798075)(file:///C:_docs\wendyma\AppData\Local\Temp\ksohtml24892\wps18.jpg)]

方式2

指定更新特定属性

POST /test1/_doc/1/_update 
{
    "doc": {
        "age": 20
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PPiTcyEv-1656686798076)(file:///C:_docs\wendyma\AppData\Local\Temp\ksohtml24892\wps19.jpg)]

删除

DELETE /test1

DELETE /test1/_doc/1

文档基本操作

增加数据

PUT /test_es/_doc/1 
{
    "name": "菜鸟",
    "age": 18,
    "desc": "一顿操作猛如虎,一看工资2500",
    "tags": [
        "直男",
        "技术宅",
        "温暖"
    ]
}

PUT /test_es/_doc/2 
{
    "name": "张三",
    "age": 20,
    "desc": "法外狂徒",
    "tags": [
        "交友",
        "渣男",
        "夜店"
    ]
}

PUT /test_es/_doc/3
{
    "name": "李四",
    "age": 25,
    "desc": "迷失在一声声靓仔中",
    "tags": [
        "靓仔",
        "搞笑",
        "唱歌"
    ]
}

更新

注意POST和PUT的区别

搜索

简单查询

就是根据id查询

GET test_es/_doc/1
GET test_es/_doc/_search  //会查询所有数据,就像select * from table_name一样

条件查询

通常在url参数加q指定查询条件

GET test_es/_doc/_search?q=name:菜鸟  //查询条件是name属性有菜鸟的那些数据
GET test_es/_doc/_search?q=男  //会查到所有带男的文档

响应

{
    "took": 6,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 0.48034602,
        "hits": [
            {
                "_index": "test_es",
                "_type": "_doc",
                "_id": "2",
                "_score": 0.48034602,
                "_source": {
                    "name": "张三",
                    "age": 20,
                    "desc": "法外狂徒",
                    "tags": [
                        "交友",
                        "渣男",
                        "夜店"
                    ]
                }
            },
            {
                "_index": "test_es",
                "_type": "_doc",
                "_id": "1",
                "_score": 0.45059985,
                "_source": {
                    "name": "菜鸟",
                    "age": 18,
                    "desc": "一顿操作猛如虎,一看工资2500",
                    "tags": [
                        "直男",
                        "技术宅",
                        "温暖"
                    ]
                }
            }
        ]
    }
}

查询结果中会给每条数据打个score,score越大的数据越靠前,上述响应中_score就是分数

文档进阶操作

json模糊查询

又称构建查询。用json格式构建查询条件比较灵活,可以构建更加复杂的查询条件,也更加一目了然

GET test_es/_doc/_search  //查询名字属性中包含“菜鸟”的
{
    "query": {
        "match": {
            "name": "菜鸟"
        }
    }
}

查询全部

GET test_es/_doc/_search  //查询全部
{
    "query": {
        "match_all": {}
    }
}

查看特定属性

GET test_es/_doc/_search  //仅查看 name 和 desc 两个属性
{
    "query": {
        "match_all": {}
    },
    "_source": [
        "name",
        "desc"
    ]
} 

排序

在排序的过程中,只能使用可排序的属性进行排序。可排序属性有数字、日期、ID。

GET test_es/_doc/_search  //对查询结果排序
{
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "age": {
                "order": "desc"  //desc降序, asc升序
            }
        }
    ]
}

分页查询

类似于Mysql中的LIMIT。

GET test_es/_doc/_search
{
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "age": {
                "order": "desc"
            }
        }
    ],
    "from": 0, //从第0条开始
    "size": 2  //查询2条
}

and查询

使用must命令可以查询满足多个条件的数据,类似于Mysql的and

GET test_es/_doc/_search 
{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "name": "菜鸟"
                    }
                },
                {
                    "match": {
                        "age": 3
                    }
                }
            ]
        }
    }
}

or查询

使用should查询满足任一条件的数据,类似于Mysql的or

GET test_es/_doc/_search
{
    "query": {
        "bool": {
            "should": [
                {
                    "match": {
                        "name": "张三"
                    }
                },
                {
                    "match": {
                        "age": 25
                    }
                }
            ]
        }
    }
}

not查询

must_not查询不满足条件的数据,类似于Mysql的not

GET test_es/_doc/_search 
{
    "query": {
        "bool": {
            "must_not": [
                {
                    "match": {
                        "name": "张三"
                    }
                }
            ]
        }
    }
}

短语检索

按照标签检索,可以同时写多个标签,只要存在一个就能查查到

GET test_es/_doc/_search

GET test_es/_doc/_search
{
    "query": {
        "match": {
            "tags": "男 技术"
        }
    }
} 

过滤

使用filter可以过滤掉不匹配的数据。下面是用年龄区间过滤

GET test_es/_doc/_search 
{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "name": "菜鸟"
                    }
                }
            ],
            "filter": [
                {
                    "range": {
                        "age": {
                            "gte": 10,
                            "lte": 20
                        }
                    }
                }
            ]
        }
    }
}

精确查询

使用GET _analyze分析句子时,analyzer为keyword,句子不会被拆分;而analyzer为standard,句子会被拆分。可通过下面的命令验证这点

GET _analyze   //不拆分
{
    "analyzer": "keyword",
    "text": "菜鸟Java name"
}

GET _analyze  //拆分
{
    "analyzer": "standard",
    "text": "菜鸟Java name"
}

这意味着用 mappings properties 去给多个字段(fields)指定类型的时候,text类型会被分析器进行分析,keyword类型不会被分析器处理。

我理解默认建立倒排索引与此这个分词结果有关,每个分词结果都是一个倒排索引。

term精确查询

match是经过分析(analyer)的,也就是说,文档是先被分析器处理了,根据不同的分析器,分析出的结果也会不同,查询根据分词结果进行匹配。

term是不经过分词的,直接去倒排索引查找精确的值。

验证

PUT testdb 
{
    "mappings": {
        "properties": {
            "name": {
                "type": "text"
            },
            "desc": {
                "type": "keyword"
            }
        }
    }
}

// 插入数据

PUT testdb/_doc/1 
{
    "name": "菜鸟Java name",
    "desc": "菜鸟Java desc"
}

PUT testdb/_doc/2
{
    "name": "菜鸟Java name",
    "desc": "菜鸟Java desc2"
}

PUT testdb/_doc/3
{
    "name": "爱学习",
    "desc": "菜鸟爱学习"
}

下面的语句可以查到name属性的倒排索引中含 “学”的文档。

GET testdb/_search 
{
    "query": {
        "term": {
            "name": "学"
        }
    }
}

如果我这里查询学习,结果为空,我认为是倒排索引中无“学习”这个索引。观察下面standard分词结果,也没有学习,或许生成倒排索引用的分词器就是standard吧

GET _analyze 
{
    "analyzer": "standard",
    "text": "爱学习"
}

下面命令只能查到desc精确等于给定值的文档

GET testdb/_search 
{
    "query": {
        "term": {
            "desc": "菜鸟Java desc"
        }
    }
}

高亮查询

highlight提供高亮查询,它默认会在查询结果的关键词上加标签

默认高亮查询

GET test_es/_doc/_search 
{
    "query": {
        "match": {
            "name": "菜鸟"
        }
    },
    "highlight": {
        "fields": {
            "name": {}
        }
    }
}

自定义高亮样式

GET test_es/_doc/_search
{
    "query": {
        "match": {
            "name": "菜鸟"
        }
    },
    "highlight": {
        "pre_tags": "<b class='key' style='color:red'>",
        "post_tags": "</b>",
        "fields": {
            "name": {}
        }
    }
}

springboot集成ES

集成步骤

导入jar包

<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.14.1</version>
</dependency>

或者导入springboot提供的starter

<dependency> 
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置客户端

es中提供了很多种客户端,此处我们选择RestHighLevelClient。读者有时间可以研究一下es几种客户端的异同。

@Configuration
public class ElasticsearchClientConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")));
        return client;
    }
}

当然你可以选择在yml中配置es,网上资料很多。配置好客户端之后,就可以用客户端执行命令操作ES服务器了。

基本操作

@SpringBootTest
public class ESApplicationoTest {
    //@Autowired
    //@Qualifier("restHighLevelClient")
    //private RestHighLevelClient client; //如果对象名是自己取的,需要使用 @Qualifier 才能注入RestHighLevelClient

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * create by: wendyMa
     * description: 创建索引
     * create time: 2022/6/5 14:27
     *
     * @return
     */
    @Test
    void testCreateIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("ma_index");
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(createIndexResponse);
    }

    /**
     * create by: wendyMa
     * description: 获取索引
     * create time: 2022/6/5 14:27
     *
     * @return
     */
    @Test
    void testExistsIndex() throws IOException {
        GetIndexRequest request = new GetIndexRequest("ma_index");
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    /**
     * create by: wendyMa
     * description: 删除索引
     * create time: 2022/6/5 14:32
     *
     * @return
     */
    @Test
    void testDeleteIndexRequest() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("ma_index");
        AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(response.isAcknowledged());
    }

    /**
     * create by: wendyMa
     * description: 添加文档
     * create time: 2022/6/5 15:23
     *
     * @return
     */
    @Test
    void testAddDocument() throws IOException {
        // 创建对象
        _doc _doc = new _doc("wendy", 25);
        // 创建请求
        IndexRequest request = new IndexRequest("ma_index");
        // 设置文档id
        request.id("1");
        // 设置过期时间
        request.timeout(TimeValue.timeValueSeconds(1));
        // 转为json字符串再保存
        ObjectMapper objectMapper = new ObjectMapper();
        request.source(objectMapper.writeValueAsString(_doc), XContentType.JSON);
        // 发送请求
        IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        // 获取响应结果
        System.out.println(indexResponse.toString());
        RestStatus Status = indexResponse.status();
        System.out.println(Status == RestStatus.OK || Status == RestStatus.CREATED);
    }

    /**
     * create by: wendyMa
     * description: 判断文档是否存在  get  /index/_doc/id
     * create time: 2022/6/5 15:26
     *
     * @return
     */
    @Test
    void testIsExists() throws IOException {
        GetRequest getRequest = new GetRequest("ma_index", "1");
        // 不获取_source上下文 storedFields
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        getRequest.storedFields("_none_");
        // 判断此id是否存在!
        boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    /**
     * create by: wendyMa
     * description: 获取文档信息  get  /index/_doc/id
     * create time: 2022/6/5 15:28
     *
     * @return
     */
    @Test
    void testGetDocument() throws IOException {
        GetRequest getRequest = new GetRequest("ma_index", "1");
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        // 打印文档内容
        System.out.println(getResponse);
    }

    // 更新文档记录
    @Test
    void testUpdateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("ma_index", "1");
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");
        _doc _doc = new _doc("wendyma", 28);
        request.doc(JSONUtils.objectToString(_doc), XContentType.JSON);
        UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        System.out.println(updateResponse.status() == RestStatus.OK);
    }

    // 删除文档测试
    @Test
    void testDelete() throws IOException {
        DeleteRequest request = new DeleteRequest("ma_index", "1");
        request.timeout(TimeValue.timeValueSeconds(1));
        DeleteResponse deleteResponse = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.println(deleteResponse.status() == RestStatus.OK);
    }

    @Test
    void testBulkRequest() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout(TimeValue.timeValueMinutes(2));
        bulkRequest.timeout("2m");
        ArrayList<_doc> _docList = new ArrayList<>();
        _docList.add(new _doc("test_es1", 11));
        _docList.add(new _doc("test_es2", 12));
        _docList.add(new _doc("test_es3", 13));
        _docList.add(new _doc("qinjiang1", 14));
        _docList.add(new _doc("qinjiang2", 15));
        _docList.add(new _doc("qinjiang3", 16));
        for (int i = 0; i < _docList.size(); i++) {
            bulkRequest
                    .add(new IndexRequest("ma_index")
                            .id("" + (i + 1))
                            .source(JSONUtils.objectToString(_docList.get(i)), XContentType.JSON));
        }// bulk
        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(!bulkResponse.hasFailures());
    }

    /**
     * create by: wendyMa
     * description:
     *   SearchRequest  1 搜搜请求
     *   SearchSourceBuilder   2 条件构造
     *   HighlightBuilder   3-1 构造高亮
     *   TermQueryBuilder  3-2 精确查询
     *   MatchAllQueryBuilder  3-3 查询所有
     *
     * create time: 2022/6/5 17:27
     * @return
     */
    @Test
    void testSearch() throws IOException {
        SearchRequest searchRequest = new SearchRequest("ma_index");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //AbstractQueryBuilder queryBuilder = QueryBuilders.termQuery("name", "qinjiang1");
        AbstractQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
        sourceBuilder.query(queryBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(JSONUtils.objectToString(response.getHits()));
        System.out.println("================SearchHit==================");
        for (SearchHit documentFields : response.getHits().getHits()) {
            System.out.println(documentFields.getSourceAsMap());
        }
    }
}

实战测试

爬取数据

jsoup是一个java爬虫库,mavne引入

<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.13.1</version>
</dependency>

爬取数据

@Service
public class CrawlerDao {
    public List<Content> parseJD(String keywords) throws Exception {
        String url = "https://search.jd.com/Search?keyword=" + keywords;
        // 爬取得到 html 数据
        Document document = Jsoup.parse(new URL(url), 30000);
        // 从html 中得到物品列表,Element对象可以封装 html 元素
        Element element = document.getElementById("J_goodsList");
        // 用 Elements 封装 ui 列表中的所有元素
        Elements elements = element.getElementsByTag("li");
        // 将所有 html 元素解析为java对象
        ArrayList<Content> goodsList = new ArrayList<>();

        // 获取京东的商品信息
        for (Element el : elements) {
            String img = el.getElementsByTag("img").eq(0).attr("src");
            //String img = el.getElementsByTag("img").eq(0).attr("source-data- lazy-img");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String title = el.getElementsByClass("p-name").eq(0).text();

            // 封装获取的数据
            Content content = new Content();
            content.setImg(img);
            content.setPrice(price);
            content.setTitle(title);
            goodsList.add(content);
        }
        return goodsList;
    }
}

ES处理数据

controller

@RestController
public class ContentController {
    @Autowired
    private ContentService contentService;

    // 获取数据存储到ES
    @GetMapping("/parse/{keyword}")
    public boolean parse(@PathVariable("keyword") String keyword) throws Exception {
        return contentService.parseContent(keyword);
    }

    //展示数据
    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public List<Map<String, Object>> search(
            @PathVariable("keyword") String keyword,
            @PathVariable("pageNo") int pageNo,
            @PathVariable("pageSize") int pageSize) throws Exception {
        return contentService.searchContentPage(keyword, pageNo, pageSize);
    }
}

service

@Service
public class ContentService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private CrawlerDao crawlerDao;

    // 1、解析数据存入es
    public boolean parseContent(String keywords) throws Exception {
        // 解析查询出来的数据
        List<Content> contents = this.crawlerDao.parseJD(keywords);
        // 封装数据到索引库中!
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout(TimeValue.timeValueMinutes(2));
        bulkRequest.timeout("2m");
        for (int i = 0; i < contents.size(); i++) {
            bulkRequest.add(new IndexRequest("jd_goods").source(JSONUtils.objectToString(contents.get(i)), XContentType.JSON));
        }
        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return !bulkResponse.hasFailures();
    }

    // 2、实现搜索功能,带分页处理
    public List<Map<String, Object>> searchContentPage(String keyword, int pageNo, int pageSize) throws IOException {
        // 基本的参数判断!
        if (pageNo <= 1) {
            pageNo = 1;
        }
        // 基本的条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
        // 精准匹配 QueryBuilders 根据自己要求配置查询条件即可!
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(termQueryBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        // 搜索
        searchRequest.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 解析结果!
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields : response.getHits().getHits()) {
            list.add(documentFields.getSourceAsMap());
        }
        return list;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值