狂神ES入门

2023-11-05

视频链接:https://www.bilibili.com/video/BV17a4y1x7zq

一、Elasticsearch与Solr对比

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
Elasticsearch 与 Solr 的比较总结

  • 二者安装都很简单;
  • Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;
  • Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式;
  • Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供;
  • Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch。
  • Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用。

二、环境安装

2.1 Elasticsearch 7.12.1安装

下载地址:https://www.elastic.co/cn/downloads/elasticsearch

安装    
     1 解压
     2 进入bin目录
     3 启动 elasticsearch.bat

目录:
     bin 启动文件
     config 配置文件
         log4j2 日志配置文件
         jvm.options java 虚拟机相关的配置
         elasticsearch.yml elasticsearch 的配置文件! 默认 9200 端口! 跨域!
     lib 相关jar包
     logs 日志!
     modules 功能模块
     plugins 插件!

访问:http://127.0.0.1:9200/
在这里插入图片描述

2.2 elasticsearch-head可视化界面安装

下载地址:https://github.com/mobz/elasticsearch-head

安装:
          1 elasticsearch-head-master 解压
          2 npm install   安装环境
          3.npm run start   启动

默认端口9100,想访问9200,出现跨域问题

解决:
打开elasticsearch-7.12.1\config\elasticsearch.yml

http.cors.enabled: true   #允许跨域
http.cors.allow-origin: "*"   #允许所有人访问

在这里插入图片描述

2.3 kibana 安装

下载地址:https://www.elastic.co/cn/downloads/kibana

 1、解压
 2、启动 kibana.bat
 3、访问测试 http://localhost:5601/
 4、开发工具! (Post、curl、head、谷歌浏览器插件测试!)
 5、汉化,kibana-7.12.1-windows-x86_64\config\kibana.yml
   - i18n.locale: "zh-CN"

在这里插入图片描述

三、ES核心概念

关系型数据库和elasticsearch客观的对比!一切都是json

Relational DB Elasticsearch
数据库(database) 索引(indices)
表(tables) 类型(types)
行(rows) 文档(documents)
字段(columns) fields

elasticsearch(集群)可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档(行),每个文档又包含多个字段(列)。

物理设计:

elasticsearch在后台把每个索引划分成多个分片。每个分片可以在集群中的不同服务器间迁移,一个人就是一个集群,默认集群名就是elasticsearch

逻辑设计:

一个索引类型中,包含多个文档,比如文档1,文档2,当我们索引一篇文档时,可以通过这样的一个顺序找到它:索引-》类型-》文档id,通过这个组合我们就能索引到某个具体的文档。注意:ID不必是整数,实际上它是一个字符串

文档

就是我们的一条条的记录

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

  • 自我包含, 一篇文档同时包含字段和对应的值,也就是同时包含key:value !
  • 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的! {就是一 个json对象! fastjson进行自动转换!}
  • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。

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

类型

类型是文档的逻辑容器,就像关系型数据库一样,表是行的容器。类型中对于字段的定 义称为映射,比如name映射为字符串类型。我们说文档是无模式的 ,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么elasticsearch是怎么做的呢?elasticsearch会自动的将新字段加入映射,但是这个字段的不确定它是什么类型, elasticsearch就开始猜,如果这个值是18 ,那么elasticsearch会认为它是整形。但是elasticsearch也可能猜不对 ,所以最安全的方式就是提前定义好所需要的映射,这点跟关系型数据库殊途同归了,先定义好字段,然后再使用,别整什么幺蛾子。

索引

就是数据库!

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

物理设计:节点和分片如何工作

一个集群至少有一 个节点,而一个节点就是一个elasricsearch进程 ,节点可以有多个索引默认的,如果你创建索引,那么索引将会有个5个分片( primary shard ,又称主分片)构成的,每一个主分片会有一个副本( replica shard ,又称复制分片)

在这里插入图片描述

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

倒排索引

倒排索引详解:https://www.cnblogs.com/zlslch/p/6440114.html

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

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

为为创建倒排索引,我们首先要将每个文档拆分成独立的词(或称为词条或者tokens) ,然后创建一一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:

term doc_1 doc_2
Study x
To x x
every
forever
day
study x
good
every
to x
up

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

term doc_1 doc_2
to x
forever
total 2 1

两个文档都匹配,但是第一个文档比第二个匹配程度更高。如果没有别的条件,现在,这两个包含关键字的文档都将返回。

再来看一个示例,比如我们通过博客标签来搜索博客文章。那么倒排索引列表就是这样的一个结构:

博客文章(原始数据) 博客文章(原始数据) 索引列表(倒排索引) 索引列表(倒排索引)
博客文章ID 标签 标签 博客文章ID
1 python python 1,2,3
2 python linux 3,4
3 linux,python
4 linux

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

elasticsearch的索引和Lucene的索引对比

在elasticsearch中,索引(库)这个词被频繁使用,这就是术语的使用。在elasticsearch中 ,索引被分为多个分片,每份分片是一个Lucene的索引。因为一个elasticsearch索引是由多个Lucene索引组成的。别问为什么,谁让elasticsearch使用Lucene作为底层呢!如无特指,说起索引都是指elasticsearch的索引。

接下来的一切操作都在kibana中Dev Tools下的Console里完成。基础操作!

四、ik分词器

什么是IK分词器 ?

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

如果要使用中文,建议使用ik分词器!

IK提供了两个分词算法: ik_ smartik_ max_ word,其中ik_ smart为最少切分, ik_ max_ _word为最细粒度划分!

什么是IK分词器:

  • 把一句话分词
  • 如果使用中文:推荐IK分词器
  • 两个分词算法:ik_smart(最少切分),ik_max_word(最细粒度划分)

ik分词器下载:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.12.1
在这里插入图片描述
下载完,在elasticsearch 的plugins目录下建一个ik文件夹,将压缩包的内容解压到ik文件夹中,重启ES,可以看到ik分词器被加载了!

kibana 中测试:

ik_smart(最少切分)

//_analyze:分词器
GET _analyze
{
  "analyzer": "ik_smart",
  "text": "我是社会主义接班人"
}

//结果
{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "社会主义",
      "start_offset" : 2,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "接班人",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 3
    }
  ]
}

ik_max_word(最细粒度划分)

GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是社会主义接班人"
}

//结果
{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "社会主义",
      "start_offset" : 2,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "社会",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "主义",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "接班人",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "接班",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "人",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "CN_CHAR",
      "position" : 7
    }
  ]
}

ik分词器增加自定义配置

elasticsearch-7.12.1\plugins\ik\config目录下增加自定义的dic文件,然后配置文件中加载自定义dic文件,重启即可!
在这里插入图片描述

加自定义词典前

GET _analyze
{
  "analyzer": "ik_smart",
  "text": "喜欢狂神说Java"
}

{
  "tokens" : [
    {
      "token" : "喜欢",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "狂",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "神",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "说",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "java",
      "start_offset" : 5,
      "end_offset" : 9,
      "type" : "ENGLISH",
      "position" : 4
    }
  ]
}

加自定义词典后

GET _analyze
{
  "analyzer": "ik_smart",
  "text": "喜欢狂神说Java"
}

{
  "tokens" : [
    {
      "token" : "喜欢",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "狂神说",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "java",
      "start_offset" : 5,
      "end_offset" : 9,
      "type" : "ENGLISH",
      "position" : 2
    }
  ]
}

可以发现狂神说变成一个词了!!!

五、Rest风格说明

method url地址 描述
PUT localhost:9200/索引名称/类型名称/文档id 创建文档(指定文档id)
POST localhost:9200/索引名称/类型名称 创建文档(随机文档id)
POST localhost:9200/索引名称/类型名称/文档id/_update 修改文档
DELETE localhost:9200/索引名称/类型名称/文档id 删除文档
GET localhost:9200/索引名称/类型名称/文档id 通过文档id查询文档
POST localhost:9200/索引名称/类型名称/_search 查询所有的数据

5.1 索引操作

5.1.1.创建索引

PUT /索引名/类型名(高版本都不写了,都是_doc)/文档id

在这里插入图片描述
在这里插入图片描述
ES的类型:
在这里插入图片描述

5.1.2.创建索引规则

在这里插入图片描述

5.1.3.获取索引信息

GET /索引名

在这里插入图片描述

5.1.4.不指定文档类型,创建索引

PUT /索引名/_doc/文档名

ES会猜是什么类型的,有可能猜错
在这里插入图片描述
在这里插入图片描述

5.1.5.cat命令

GET _cat/health 获取健康信息
GET _cat/indices?v 获取所有信息

在这里插入图片描述
在这里插入图片描述

5.1.6.修改索引

PUT /test1/type1/1 未设置的属性,会被覆盖为空
POST /test3/_doc/1 未设置的属性,会被覆盖为空
POST /test3/_doc/1/_update

在这里插入图片描述

在这里插入图片描述
常用 POST /test3/_doc/1/_update,这种不会被覆盖:
在这里插入图片描述
在这里插入图片描述

5.1.7.删除索引

DELETE 索引名

在这里插入图片描述

5.2 文档操作

5.2.1.简单查询

GET /kuangshen/_doc/_search?q=name:狂神

在这里插入图片描述

GET /kuangshen/_doc/_search

在这里插入图片描述

两种简单查询,结果一样!name是text类型,进行了分词,所以查狂神法外狂徒张三也能被查出来

5.2.2.多条件查询

在这里插入图片描述

复杂查询

5.2.3.过滤

在这里插入图片描述
也可以写成这种形式

GET /kuangshen/_doc/_search
{
  "query":{
    "match":{
      "name":"狂神"
    }
  },
  "_source":{
    "includes":["name","age"]
  }
}

source中不包含哪种属性
在这里插入图片描述

5.2.4.排序

在这里插入图片描述

5.2.5.分页

在这里插入图片描述

5.2.6.布尔查询

must:必须
相当于and
在这里插入图片描述
should:应该
相当于or
es的查询都会进行分词,如果查name:“狂神”,会把所有的都查出来
在这里插入图片描述
must_not:不必须
相当于 不等于not
在这里插入图片描述

5.2.7.区间查询

  • gt大于
  • gte大于等于
  • lte小于
  • lte小于等于
    在这里插入图片描述

5.2.8.精确查询

term查询是直接通过倒排索引指定的词条进程精确查找的

搜索前,term不会对搜索词进行分词拆解,term的搜索词必须在文档集合中,否则查不到!!
而match会先对搜索词进行分词拆分,分词只要在文档集合中有就查出来,匹配程度越大,sorce越大

详细使用参考:https://www.cnblogs.com/chong-zuo3322/p/14031602.html

在这里插入图片描述

5.2.9.高亮

"pre_tags":"<p style='color:red'>"  #前缀
"post_tags":"</p>"  #后缀

在这里插入图片描述

5.2.10.分词

标准的会被分词
在这里插入图片描述
keyword不会被分词
在这里插入图片描述

六、springboot整合es

创建springboot项目,导入依赖

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

查看maven的es版本是不是你下载的版本

<properties>
	<java.version>1.8</java.version>
	<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

在这里插入图片描述
新建一个es配置类,注入RestHighLevelClient 客户端

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

6.1 索引API

索引请求相关的API有5种,常用的有3种:添加,删除,判断是否存在
在这里插入图片描述

@SpringBootTest
class SpringbootEsApplicationTests {

	@Autowired
	@Qualifier("restHighLevelClient")
	private RestHighLevelClient client;

	@Test
	void testCreateIndex() throws IOException {
		//创建索引请求
		CreateIndexRequest request = new CreateIndexRequest("kuang_index");
		//获取索引客户端对象
		IndicesClient indices = client.indices();
		//索引客户端执行创建索引请求
		CreateIndexResponse createIndexResponse = indices.create(request, RequestOptions.DEFAULT);
		System.out.println(createIndexResponse);
	}

	@Test
	void testExistIndex() throws IOException {
		//创建获取索引请求
		GetIndexRequest request = new GetIndexRequest("kuang_index");
		//客户端执行请求
		boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
		System.out.println("索引是否存在:"+exists);
	}

	@Test
	void testDeleteIndex() throws IOException {
		//创建删除索引请求
		DeleteIndexRequest request = new DeleteIndexRequest("kuang_index");
		AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
		System.out.println("是否删除成功:"+response.isAcknowledged());
	}

}

6.2 文档API

文档的增删改查!

//测试添加文档
@Test
void testAddDocument() throws IOException {
	User user = new User("张三", 18);
	//创建一个请求
	IndexRequest request = new IndexRequest("kuang_index");
	//设置id,也可不设置,系统随机设置
	request.id("1");
	//设置超时时间
	request.timeout("1s");
	//将数据用json字符串形式放入请求中
	request.source(JSON.toJSONString(user), XContentType.JSON);
	//执行请求
	IndexResponse response = client.index(request, RequestOptions.DEFAULT);
	System.out.println("添加文档:"+response.toString());
	System.out.println("添加文档:"+response.status());
	//结果
	//添加文档:IndexResponse[index=kuang_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
	//添加文档:CREATED
}

//测试删除文档
@Test
void testDeleteDocument() throws IOException {
	DeleteRequest request = new DeleteRequest("kuang_index", "1");
	request.timeout("1s");
	DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
	System.out.println(response.status());
}

//测试修改文档
@Test
void testUpdateDocument() throws IOException {
	UpdateRequest request = new UpdateRequest("kuang_index", "1");
	request.timeout("1s");
	//只修改name,age被置0
	//只修改age,name正常
	//request.doc(JSON.toJSONString(new User("王五")),XContentType.JSON);
	request.doc(JSON.toJSONString(new User(10)),XContentType.JSON);

	UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
	System.out.println(response.status());
}

//测试获取文档
@Test
void testEGETDocument() throws IOException {
	GetRequest request = new GetRequest("kuang_index","1");
	boolean exists = client.exists(request, RequestOptions.DEFAULT);
	System.out.println("文档是否存在:"+exists);
	GetResponse response = client.get(request, RequestOptions.DEFAULT);
	System.out.println(response); //一个hits
	System.out.println(response.getSourceAsString());//hits中的数据
	//{"_index":"kuang_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"age":18,"name":"张三"}}
	//{"age":18,"name":"张三"}
}

批量插入

//测试批量插入
@Test
void testBulkAddDocument() throws IOException {
	List list = new ArrayList();
	list.add(new User("狂神1",7));
	list.add(new User("狂神2",8));
	list.add(new User("狂神3",9));
	list.add(new User("狂神4",10));
	list.add(new User("狂神5",11));

	//创建批处理请求
	BulkRequest request = new BulkRequest();
	request.timeout("1s");

	for (int i = 0; i < list.size(); i++) {
		//向批处理请求中添加创建文档请求
		request.add(new IndexRequest("kuang_index")
					.id(""+(i+1))
					.source(JSON.toJSONString(list.get(i)),XContentType.JSON)
		);
	}

	//执行批处理请求
	BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
	System.out.println("是否失败:"+response.hasFailures());
}

条件查询

//测试条件查询
@Test
void testSearchDocument() throws IOException {
	//创建查询请求
	SearchRequest request = new SearchRequest("kuang_index");
	//构建搜索条件
	SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
	//高亮
	sourceBuilder.highlighter();
	//排序
	sourceBuilder.sort("age", SortOrder.DESC);
	//分页
	sourceBuilder.from(1);
	sourceBuilder.size(3);
	//精确查询
	//TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "狂神2");
	//普通查询,会分词
	MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "狂神");
	sourceBuilder.query(matchQueryBuilder);
	//设置超时时间
	sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

	//搜索条件放入请求中
	request.source(sourceBuilder);
	SearchResponse response = client.search(request, RequestOptions.DEFAULT);
	System.out.println("测试条件查询:"+JSON.toJSONString(response));
	for (SearchHit documentFields:response.getHits().getHits()) {
		System.out.println(documentFields.getSourceAsString());
	}
}

在这里插入图片描述

七、ES练习-京东搜索

1.项目搭建

创建springboot项目,导入依赖,修改elasticsearch版本

<properties>
	<java.version>1.8</java.version>
	<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
<dependencies>
	<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
	<!--jsoup解析网页 tika解析电影-->
	<dependency>
		<groupId>org.jsoup</groupId>
		<artifactId>jsoup</artifactId>
		<version>1.10.2</version>
	</dependency>
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>1.2.62</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<scope>runtime</scope>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-configuration-processor</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

导入静态资源

链接:https://pan.baidu.com/s/1M5uWdYsCZyzIAOcgcRkA_A
提取码:qk8p

在这里插入图片描述
测试

@Controller
public class IndexController {
    
    @RequestMapping({"/","/index"})
    public String index() {
        return "index";
    }
}

启动项目,访问测试:http://localhost:9090/

在这里插入图片描述

2.爬取数据

爬取数据:获取请求返回的页面信息,筛选出我们想要的数据,jsoup 包可以实现

jsoup可以解析网页,不可以解析媒体

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

创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {
    private String name;
    private String price;
    private String img;
	//扩展属性
}

写一个工具类,解析网页,将需要的数据封装成list集合

public class HTMLParseUtil {
    public static void main(String[] args) throws Exception {
        HTMLParseUtil.parse("java").forEach(System.out::println);
    }

    /**
     * 爬取数据存入list中
     * @param keyword 关键字
     * @return
     * @throws Exception
     */
    public static List<Content> parse(String keyword) throws Exception {
		// 请求url
        String url="https://search.jd.com/search?keyword="+keyword;
		// 1.解析网页(jsoup 解析返回的对象是浏览器Document对象)
        Document document = Jsoup.parse(new URL(url), 50000);
        // 使用document可以使用在js对document的所有操作
        // 2.获取元素(通过id),id自己查网页
        Element element = document.getElementById("J_goodsList");
        // 3.获取J_goodsList ul 每一个
        Elements elements = element.getElementsByTag("li");
        // 4.获取li下的 img、price、name
        // list存储所有li下的内容
        List<Content> list=new ArrayList<>();
        for (Element li:elements) {
            String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
            String price = li.getElementsByClass("p-price").eq(0).text();
            String name = li.getElementsByClass("p-name").eq(0).text();

            Content content = new Content();
            content.setName(name);
            content.setPrice(price);
            content.setImg(img);

            list.add(content);
        }

        return list;
    }
}

开发者模式下看
在这里插入图片描述
在这里插入图片描述
抓取的数据交给 elasticsearch 即可用于 es 实现搜索

3.业务编写

添加 elasticsearch 配置类

@Configuration
public class ElasticSearchClientConfig {

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

在 elasticsearch 后台管理页面创建一个空索引 jd_goods

在这里插入图片描述
创建业务service

@Service
public class ContentService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 将解析的数据批量插入es数据库中
     * @param keyword 关键字
     * @return
     * @throws Exception
     */
    public Boolean parseContent(String keyword) throws Exception {
        List<Content> contents = HTMLParseUtil.parse(keyword);

		//批量插入请求
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("2m");

        for (int i = 0; i < contents.size(); i++) {
            bulkRequest.add(new IndexRequest("jd_goods")
            .source(JSON.toJSONString(contents.get(i)),XContentType.JSON));
        }

        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        //返回是否成功
        return !bulk.hasFailures();
    }

    /**
     * 从es数据库中搜索
     * @param keyword 关键字
     * @param pageNo 第几页
     * @param pageSize 几个
     * @return
     * @throws IOException
     */
    public List<Map<String,Object>> searchPage(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);
		
		//构建精确查询
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
        sourceBuilder.query(termQueryBuilder );
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

		// 执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

		// 解析结果
        List<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit documentFields:searchResponse.getHits().getHits()) {
            list.add(documentFields.getSourceAsMap());
        }
        return list;
    }
}
@RestController
public class ContentController {
    @Autowired
    private ContentService contentService;
    // 先从京东抓取数据并存入es中
    @GetMapping("/parse/{keyword}")
    public Boolean parse(@PathVariable("keyword") String keywords) throws Exception {
        return contentService.parseContent(keywords);
    }
    // 在es中搜索数据返回结果
    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
                                            @PathVariable("pageNo") int pageNo,
                                            @PathVariable("pageSize") int pageSize) throws IOException {
        return contentService.searchPage(keyword, pageNo, pageSize);
    }
}

测试成功后,现拿到了数据,还差前端页面展示,使用 Vue

4.前端页面展示

导入vue和axios的js

修改index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8"/>
    <title>狂神说Java-ES仿京东实战</title>
    <link rel="stylesheet" th:href="@{/css/style.css}"/>
    <script th:src="@{/js/jquery.min.js}"></script>
</head>

<body class="pg">
<div class="page" id="app">
    <div id="mallPage" class=" mallist tmall- page-not-market ">

        <!-- 头部搜索 -->
        <div id="header" class=" header-list-app">
            <div class="headerLayout">
                <div class="headerCon ">
                    <!-- Logo-->
                    <h1 id="mallLogo">
                        <img th:src="@{/images/jdlogo.png}" alt="">
                    </h1>

                    <div class="header-extra">

                        <!--搜索-->
                        <div id="mallSearch" class="mall-search">
                            <form name="searchTop" class="mallSearch-form clearfix">
                                <fieldset>
                                    <legend>天猫搜索</legend>
                                    <div class="mallSearch-input clearfix">
                                        <div class="s-combobox" id="s-combobox-685">
                                            <div class="s-combobox-input-wrap">
                                                <input v-model="keyword" type="text" autocomplete="off" value="dd" id="mq"
                                                       class="s-combobox-input" aria-haspopup="true">
                                            </div>
                                        </div>
                                        <button @click.prevent="searchKey" type="submit" id="searchbtn">搜索</button>
                                    </div>
                                </fieldset>
                            </form>
                            <ul class="relKeyTop">
                                <li><a>狂神说Java</a></li>
                                <li><a>狂神说前端</a></li>
                                <li><a>狂神说Linux</a></li>
                                <li><a>狂神说大数据</a></li>
                                <li><a>狂神聊理财</a></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 商品详情页面 -->
        <div id="content">
            <div class="main">
                <!-- 品牌分类 -->
                <form class="navAttrsForm">
                    <div class="attrs j_NavAttrs" style="display:block">
                        <div class="brandAttr j_nav_brand">
                            <div class="j_Brand attr">
                                <div class="attrKey">
                                    品牌
                                </div>
                                <div class="attrValues">
                                    <ul class="av-collapse row-2">
                                        <li><a href="#"> 狂神说 </a></li>
                                        <li><a href="#"> Java </a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </form>

                <!-- 排序规则 -->
                <div class="filter clearfix">
                    <a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
                </div>

                <!-- 商品详情 -->
                <div class="view grid-nosku">

                    <div class="product" v-for="result in results">
                        <div class="product-iWrap">
                            <!--商品封面-->
                            <div class="productImg-wrap">
                                <a class="productImg">
                                    <img :src="result.img">
                                </a>
                            </div>
                            <!--价格-->
                            <p class="productPrice">
                                <em>{{result.price}}</em>
                            </p>
                            <!--标题-->
                            <p class="productTitle">
                                <a v-html="result.name"> </a>
                            </p>
                            <!-- 店铺名 -->
                            <div class="productShop">
                                <span>店铺: 狂神说Java </span>
                            </div>
                            <!-- 成交信息 -->
                            <p class="productStatus">
                                <span>月成交<em>999笔</em></span>
                                <span>评价 <a>3</a></span>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script th:src="@{/js/vue.min.js}"></script>
<script th:src="@{/js/axios.min.js}"></script>
<script>

    new Vue({
        el: "#app",
        data: {
            keyword: '',
            results: []
        },
        methods: {
            searchKey(){
                let keyword=this.keyword;

                axios.get("search/"+keyword+"/1/10").then(response=>{
                    this.results=response.data;
                })
            }
        }
    });
</script>

</body>
</html>

5.高亮显示

修改 service业务层方法

/**
 * 高亮查询
 * @param keyword
 * @param pageNo
 * @param pageSize
 * @return
 * @throws IOException
 */
public List<Map<String,Object>> highlightSearch(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);

	//===========================================================================
    //高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("name");
    highlightBuilder.preTags("<span style='color:red;'>");
    highlightBuilder.postTags("</span>");
    //highlightBuilder.requireFieldMatch(false);
    sourceBuilder.highlighter(highlightBuilder);
	//===========================================================================

    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
    sourceBuilder.query(termQueryBuilder);
    sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

	//解析结果
    List<Map<String,Object>> list = new ArrayList<>();
    for (SearchHit documentFields:searchResponse.getHits().getHits()) {
        System.out.println("===============================");
        //没有高亮的结果
        Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
        System.out.println(sourceAsMap.get("name"));
        //高亮字段
        Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
        HighlightField name = highlightFields.get("name");
        //替换旧的结果
        if (name!=null){
            Text[] fragments = name.fragments();
            StringBuilder new_name = new StringBuilder();
            for (Text t:fragments) {
                new_name.append(t);
            }
            sourceAsMap.put("name",new_name.toString());
            System.out.println(sourceAsMap.get("name"));
        }
        list.add(sourceAsMap);

    }
    return list;
}

在这里插入图片描述
controller 将接受方法修改为高亮业务

// 在es中搜索数据返回结果
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
                                        @PathVariable("pageNo") int pageNo,
                                        @PathVariable("pageSize") int pageSize) throws IOException {
    return contentService.highlightSearch(keyword, pageNo, pageSize);
}

修改 index.html 页面,使其可以解析高亮标签

<!--标题-->
<p class="productTitle">
    <a v-html="result.title"></a>
</p>

6.测试结果

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

狂神ES入门 的相关文章

  • AWS Elasticsearch 和 CORS

    我正在试用 AWS Elasticsearch 服务 https aws amazon com elasticsearch service https aws amazon com elasticsearch service 设置非常简单
  • Spark任务仅在一个执行器上运行

    大家好 首先我知道这个线程的存在 Spark 中的任务仅在一个执行器上运行 https stackoverflow com questions 53425983 task is running on only one executor in
  • 未能向 master 发送加入请求

    我使用的是elasticsearch版本1 3 0 重启数据节点后 无法检测到主节点 我收到错误failed to send join request to master 在错误日志中 app101 dGRBqTFTQfae76IFCjsM
  • Elasticsearch 过滤器/计算嵌套字段

    我有带有嵌套字段的文档 如下所示 results id 1234 name asdf id 5678 name jkl ip 1 2 3 4 嵌套字段的映射如下所示 results type nested properties id typ
  • 如何用Python为ElasticSearch创建只读客户端?

    我想从 ES 读取数据 但不想意外向其中写入数据 无索引操作 这只是一种安全措施 以便以后修改查询函数的其他人不允许插入数据 当你说你想要只读客户端时 客户端强调您系统中的同一集群可能有其他客户端 然后阻止整个索引为只读将会阻止所有客户端的
  • Elasticsearch 对字符串排序未返回预期结果

    当对包含多个单词的字符串字段进行排序时 Elasticsearch 会拆分字符串值并使用最小值或最大值作为排序值 即 当对值为 老虎之眼 的字段进行升序排序时 排序值为 Eye 当按降序排序时 排序值为 Tiger 假设我的索引中有 老虎之
  • 将 Elasticsearch 结果导出到 CSV 文件

    我正在尝试将使用以下查询找到的结果导出到桌面上的 CSV 中 这是我第一次使用 Elasticsearch 和 cURL 所以我对如何做到这一点感到困惑 from elasticsearch import Elasticsearch es
  • Elastic Search 5.x 嵌套多个查询 C#

    我将 C 与这些 nuget 包一起使用
  • 了解elasticsearch如何在内部存储日期

    我想了解 ES 如何在其索引内部存储日期值 它会转换为 UTC 吗 我有一个日期类型的字段 t 这是映射 t type date 现在 当我向 ES 插入 添加文档时 它如何存储在索引中 t 1427700477165 从 Date now
  • 如何在logstash.conf文件中创建多个索引?

    我使用以下代码在logstash conf中创建索引 output stdout codec gt rubydebug elasticsearch host gt localhost protocol gt http index gt tr
  • 在elasticsearch结果中显示不匹配的单词

    我想显示返回文档的多单词查询不匹配哪些单词 是否有查询类型或参数来实现此目的 通常 此类不匹配的单词会以删除线字体显示给用户 我得到答案后的示例查询 POST posts search query bool should match nam
  • 如何提高elasticsearch中的索引类型?

    我以前是这样搜索的 curl XGET localhost 9200 users search 但 users 包含用户 a b c 如下所示 curl XGET localhost 9200 users a b c search user
  • ElasticSearch 多滚动 Java API

    我想从索引中获取所有数据 由于项目数量对于内存来说太大 我使用滚动 很好的功能 client prepareSearch index setTypes myType setSearchType SearchType SCAN setScro
  • 全文搜索 DynamoDB

    以下情况 我正在为我的客户将元素存储在 DyanmoDb 中 HashKey 是元素 ID Range Key 是客户 ID 除了这些字段之外 我还存储一个字符串数组 gt 标签 例如 Pets House 和多行文本 我想在我的应用程序中
  • 在流浪机器中使用elasticsearch设置graylog2服务器

    我正在尝试在本地开发计算机上安装graylog2 服务器 但遇到了elasticsearch 设置问题 我的elasticsearch作为服务安装在我的开发机器上运行的vagrant机器上 所以我的elasticsearch没有安装在127
  • 无法使用 java 8 在 Windows 10 上安装 elasticsearch 5.1.1

    我正在尝试在安装了 java 8 111 的 Windows 10 笔记本电脑上安装 ElasticSearch 5 1 1 当我尝试安装 Elastic search 时触发错误 C Users 用户名 Downloads elastic
  • 如何使用ElasticSearch来实现社交搜索?

    我正在尝试使用 ElasticSearch 创建具有社交功能的商业搜索 我有一个企业目录 用户可以通过不同的方式与这些企业进行交互 通过查看它们 检查它们等 当用户搜索商家时 我希望能够在结果顶部向他们显示他们的朋友与之互动过的商家 或根据
  • Elasticsearch 数组必须和must_not

    我的 elasticsearch 数据库中有一个如下所示的文档 tags gt tag 1 tag 2 tag 3 tag A created at gt 2013 07 02 12 42 19 UTC label gt Mon super
  • Elasticsearch Nest 通配符查询(带空格)

    简洁版本 我想使用 Nest 编写一个弹性搜索查询来获取完整的索引项 ContentIndexables在我的例子中作为我的自定义类型 已被索引 该查询受 some string 术语查询的约束 即 String StartsWith 其中
  • 自动完成建议中的输出字段

    当我想在 elasticsearch 中索引文档时 会发生此问题 message MapperParsingException failed to parse nested IllegalArgumentException unknown

随机推荐

  • typora使用picgo配置sftp上传图片

    mdftp 上传到uploadPath这个目录下 host 0 0 0 0 port 22 username root password uploadPath uploads year month fullName 返回的图片链接为 url
  • AcWing 1353. 滑雪场设计

    农夫约翰的农场上有 N 个山丘 每座山的高度都是整数 在冬天 约翰经常在这些山上举办滑雪训练营 不幸的是 从明年开始 国家将实行一个关于滑雪场的新税法 如果滑雪场的最高峰与最低峰的高度差大于17 国家就要收税 为了避免纳税 约翰决定对这些山
  • idea中 git版本回退

    1 版本回退 之 Reset Type 有四种 1 1 soft 移动本地库HEAD指针 意思就是 回滚后 仅仅是把本地库的指针移动了 而暂存区和你本地的代码是没有做任何改变的 而你上次改动已提交committed到本地库的代码显示是绿色即
  • 《数值分析》-- 拉格朗日插值

    文章目录 问题 一 拉格朗日插值基函数 二 拉格朗日插值多项式 三 n次Lagrange插值多项式余项 习题 总结 问题 一 拉格朗日插值基函数 n 1时一次基函数 两点线性插值问题 问题 即已知函数 f x 在点 x 0 x 0
  • 88. 合并两个有序数组 golang

    88 合并两个有序数组 给你两个有序整数数组 nums1 和 nums2 请你将 nums2 合并到 nums1 中 使 nums1 成为一个有序数组 说明 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 你可以假设 nu
  • 驱动接口函数调用过程

    本文就来揭秘一下到底一个驱动的接口函数是如何被调用的 首先我们来反汇编读驱动的程序 跳转到 libc read 发现他把r7赋值给3 3是传过去的参数 然后调用svc指令 进入内核态相应的入口 接下来就已经进入内核态 入口函数中将存入r7中
  • Windows 10 更新后无法安装共享打印机(0x0000011b)

    windows 10 前段时间更新之后突然无法连接共享打印机 出现报错0x0000011b的错误 很多人都是用卸载补丁KB5005569 KB5005573 KB5005568 KB5005566 KB5005565 这确实能暂时解决问题
  • 原生微信小程序 计算属性 wxs

    原生微信小程序 计算属性 wxs WXS官方文档 WXS 不依赖于运行时的基础库版本 可以在所有版本的小程序中运行 WXS 与 JavaScript 是不同的语言 有自己的语法 并不和 JavaScript 一致 WXS 的运行环境和其他
  • gpio相关介绍

    GPIO 通用输入输出端口 gpio的基本输出功能由STM32控制引脚输出高 低电平 实现开关控制 最基本的输入功能是检测外部输入电平 gpio工作模式 输入模式 上拉 下拉 浮空 在输入模式中 施密特触发器打开 输出被禁止 数据寄存器每隔
  • HTML,CSS,Javascript在Web开发中分别起什么作用?

    简单描述HTML CSS Javascript在Web开发中分别起什么作用 1 什么是HTML 超文本标记语言 Hyper Text Markup Language HTML 是用来描述网页的一种语言 2 CSS 层叠样式表 Cascadi
  • VUE联动下拉选择框

  • Javascript中大括号“{}”的多义性

    JS中大括号有四种语义作用语义1 组织复合语句 这是最常见的 if condition else for 语义2 对象直接量声明 var obj name jack age 23 整个是个赋值语句 其中的 name jack age 23
  • 蓝桥杯 空间

    题目1 本题为填空题 只需要算出结果后 在代码中使用输出语句将所填结果输出即可 小蓝准备用 256MB 的内存空间开一个数组 数组的每个元素都是 32 位 二进制整数 如果不考虑程序占用的空间和维护内存需要的辅助空间 请问 256MB 的空
  • IDEA : IDEA好用的插件集锦

    1 Free Mybatis plugin mybatis 插件 让你的mybatis xml像java代码一样编辑 我们开发中使用mybatis时时长需要通过mapper接口查找对应的xml中的sql语句 该插件方便了我们的操作 安装完成
  • 代理IP和Socks5代理:跨界电商与爬虫的智能引擎

    跨界电商 作为全球市场的一部分 对数据的需求越来越大 同时 随着互联网的发展 爬虫技术也在不断演进 成为了跨界电商的关键工具之一 然而 随之而来的是网站的反爬虫机制和网络安全风险 在这种情况下 代理IP和Socks5代理应运而生 为企业提供
  • Java复习(第一季)

    Java的特性与版本 最好的跨平台开源编程语言 第二章 常量和变量 2 1 Java中的关键字 Java关键字是区分大小写的 viod是关键字 但Viod就不是 使用标识符时 需要遵守几条规则 1 标识符可以由字母 数字 下划线 美元符 组
  • Linux初体验—整理了一些Linux的常用命令

    目录 查看当前目录下的内容 文件目录操作命令 作用 用于切换当前工作目录 即进入指定目录 作用 用于显示文件内容 作用 以分页的形式显示文件内容 作用 查看文件末尾内容 作用 创建目录 作用 删除空目录 作用 删除指定文件或目录 作用 用于
  • RGB格式解释说明

    RGB 是一种加色模型 将红 Red 绿 Green 蓝 Blue 三原色的色光以不同的比例相加 以产生多种多样的色光 且三原色的红绿蓝不可能用其他单色光合成 浮点表示方式 取值范围为 0 0 1 0 整数表示 取值范围为 0 255 或者
  • MATLAB算法实战应用案例精讲-【深度学习】CNN池化

    目录 计算机视觉与卷积神经网络 计算机视觉综述 计算机视觉的发展历程 卷积神经网络
  • 狂神ES入门

    视频链接 https www bilibili com video BV17a4y1x7zq 文章目录 一 Elasticsearch与Solr对比 二 环境安装 2 1 Elasticsearch 7 12 1安装 2 2 elastic