admin 管理员组文章数量: 887021
elastic
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
安装head master
head master是一款es可视化界面插件。尽管它也提供了一些操作es的接口,但一般我们仅使用它浏览数据。
npm install #安装必须的前端依赖npm start #启动插件,在package.json目录运行此命令
尽管安装依赖显示失败,但是显示启动成功就访问试试吧。
访问head master
http://localhost:9100
图中看不到es集群的信息,这是因为端口9100和9200之间存在端口跨域问题。
跨域问题
在elasticsearch.yml中添加跨域配置,重启es服务
http.cors.enabled: truehttp.cors.allow-origin: "*"
再次访问head-master,可以新建索引测试一下能否使用
安装kibana
-
官网下载kibana安装包并解压
-
进入bin目录,启动服务即可。ELK基本都是拆箱即用的
-
访问页面
kibana会自动去访问9200,也就是elasticsearch的端口号,我们直接访问kibana即可
http://ip:5601
- 操作es
- 汉化
中文包在 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下载
2放到es的plugins下
3重启es
观察日志,可以看到分词器被加载了
也可以执行elasticsearch-plugin list 命令,确认分词插件安装成功
ES概念
基本概念
Relational DB | Elasticsearch | 备注 |
---|---|---|
数据库(database) | 索引(indices) | |
表(tables) | types | type在新版es中已被废除 |
行(rows) | documents | |
字段(columns) | fields |
索引
是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。 然后它们被存储到了各个分片上了。 我们来研究下分片是如何工作的。
类型
新版本已不再使用,无须过多关注
文档
之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个 重要属性 :
1自我包含,一篇文档同时包含字段和对应的值,也就是同时包含 key:value!
2可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的!
3灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符 串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。
分片
一个集群至少有一个节点,而一个节点就是一个elasricsearch进程。创建索引时,需要指定分片数和副本数。默认会有个5个分片 ( primary shard ,又称主分片 ) ,每一个主分片有一个副本 ( replica shard ,又称复制分片 )
上图是一个有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有一套根据匹配程度进行打分的算法,分数越高,搜索越靠前。再来看一个示例,比如我们通过博客标签来搜索博客文章。那么倒排索引列表就是这样的一个结构
如果要搜索含有 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
method | url地址 | 操作类型 | 描述 |
---|---|---|---|
PUT | localhost:9200/索引/类型/文档id | 增 | 创建文档(指定id) |
POST | localhost:9200/索引/类型 | 增 | 创建文档(随机id) |
DELETE | localhost:9200/索引/类型/文档id | 删 | 删除文档 |
POST | localhost:9200/索引/类型/文档id/_update | 改 | 修改文档 |
POST | localhost:9200/索引/类型/_search | 查 | 查询所有数据 |
GET | localhost: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 //查看所有索引的信息
更新
方式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 /test1DELETE /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 {@Beanpublic 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@Autowiredprivate RestHighLevelClient restHighLevelClient;/*** create by: wendyMa* description: 创建索引* create time: 2022/6/5 14:27** @return*/@Testvoid 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*/@Testvoid 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*/@Testvoid 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*/@Testvoid testAddDocument() throws IOException {// 创建对象_doc _doc = new _doc("wendy", 25);// 创建请求IndexRequest request = new IndexRequest("ma_index");// 设置文档idrequest.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*/@Testvoid testIsExists() throws IOException {GetRequest getRequest = new GetRequest("ma_index", "1");// 不获取_source上下文 storedFieldsgetRequest.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*/@Testvoid testGetDocument() throws IOException {GetRequest getRequest = new GetRequest("ma_index", "1");GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);// 打印文档内容System.out.println(getResponse);}// 更新文档记录@Testvoid 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);}// 删除文档测试@Testvoid 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);}@Testvoid 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));}// bulkBulkResponse 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*/@Testvoid 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 = "=" + 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 {@Autowiredprivate 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 {@Autowiredprivate RestHighLevelClient restHighLevelClient;@Autowiredprivate CrawlerDao crawlerDao;// 1、解析数据存入espublic 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;}
}
本文标签: elastic
版权声明:本文标题:elastic 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1687199656h75337.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论