es

ElasticSearch

ElasticSearch和Lucene的关系

ElasticSearch是基于Lucene做了一些封装和增强的

核心概念

  1. 索引
  2. 字段类型(mapping)
  3. 文档

概述

Node 与 Cluster

Elastic本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个elastic实例。单个elastic称为一个节点(node)。一组节点构成一个集群(cluster)

index

Elastic会索引所有字段,经过处理后会写入一个反向索引(inverted index)。查找数据的时候,直接查找该索引。所以,Elastic数据管理顶层单位就叫做index索引。他是单个数据库的同义词。每个index即数据库的名字必须是小写。通过一下命令查看所有的索引

1
GET '127.0.0.1:9200/_cat/indices?v'

document

Index里面的单条记录又称为Document(文档),多条document构成了一个Index。

Document使用Json格式表示

1
2
3
4
{
"user":"zhangsan",
"title": "工程师",
}

同一个index里面的document不要求有相同的结构scheme,但是最好保持相同,这样有利于提高搜索效率。

type

Document可以分组,比如weather这个index里面可以按城市分组(北京上海),也可以按照其后分组,这种分组叫做type,他是虚拟的逻辑分组,用来过滤document。

不同的type应该有相似的结构scheme,举例来说,id字段不能 在这个组里是字符串在另一个组里是数值。这与关系型数据库截然不同。MySQL里面不同表里的相同字段的类型是不受彼此影响的。现在在elastic里面性质完全不同的数据比如product和logs应该存成两个index而不是一个index里面的两个type(虽然可以做到)

下面可以列出每个index所包含的type

1
curl "127.0.0.1:9200/_mapping?pretty=true"

在elastic 6.X中每个index只允许包含一个type,7.X版本将彻底移除type

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

历史

多年前,一个叫做Shay Banon的刚结婚不久的失业开发者,由于妻子要去伦敦学习厨师,她便跟着去了。在找工作的过程中为了给妻子构建一个食谱的搜索引擎,他便开始构建一个早期版本的Luence。

直接基于Luence的工作比较困难,所以Shay开始抽象Luence代码以便Java程序员可以在应用中添加搜索功能。他发布了他的第一个开源项目叫做Compass

后来Shay找到一份工作,这份工作在高性能和内存数据网格的分布式环境中,因此高性能的、实时的、分布式的搜索引擎也是理所当然的。然后他决定重写Compass库使其成为一个独立的服务叫做ElasticSearch

第一个公开版本出现于2010年二月分,在那之后ElasticSearch已经成为GitHub上最受欢迎的项目之一,代码贡献者超过300人。一家主营ElasticSearch的公司就此成立,他们一边提供服务一边开发新功能

ElasticSearch和solr的差别

Elasticsearch是一个实时分布式搜索和分析引擎。它让你以前所未有的速度处理大数据成为可能。它用于全文搜索、结构化搜索、分析以及将这三者混合使用︰维基百科使用Elasticsearch提供全文搜索并高亮关键字,以及输入实时搜索(search-asyou-type)和搜索纠错(did-you-mean)等搜索建议功能。英国卫报使用Elasticsearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回应。StackOverflow结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。Github使用Elasticsearch检索1300亿行的代码。但是Elasticsearch不仅用于大型企业,它还让像DataDog以及Klout这样的创业公司将最初的想法变成可扩展的解决方案。Elasticsearch可以在你的笔记本上运行,也可以在数以百计的服务器上处理PB级别的数据。Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。Elasticsearch也使用lava开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。Elasticsearch也使用lava开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

Solr简介

Solr是Apache下的一个顶级开源项目,采用lava开发,它是基于Lucene的全文搜索服务器。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化Solr可以独立运行,运行在Jetty、Tomcat等这些Servlet容器中,Solr索引的实现方法很简单,用POST方法向Solr服务器发送一个描述Field及其内容的XML文档,Solr根据xml文档添加、删除、更新索引。Solr搜索只需要发送HTTP GET请求,然后对Solr返回Xml、json等格式的查询结果进行解析,组织页面布局。Solr不提供构建UI的功能,Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的文件,生成索引;也可以通过提出查找请求,并得到返回结果。Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的文件,生成索引;也可以通过提出查找请求,并得到返回结果。

ElasticSearch和Solr的比较

image-20210818170935687 image-20210818171053767

ElasticSearch VS Solr总结

  1. es 开箱即用,解压就可以用,非常简单。Solr安装比较复杂
  2. Solr利用zookeeper进行分布式管路,而ElasticSearch自身带有分布式协调管理的功能
  3. Solr支持更多的格式,比如Json、xml、CSV、,而es仅支持JSON但是当前用的最多 的还是JSON
  4. Solr提供更多的功能,而ES本身更注重于核心功能,高级的功能由第三方插件提供比如IK,和图形界面kibana
  5. Solr查询快,但是更新索引时慢(即插入删除慢),用于电商等查询多的应用;ES建立索引快(即查询快)及时性查询快,用于Facebook新浪等搜索。Solr是传统搜索应用的有利解决方案,但是ElasticSearch更适用于新型的实时搜索应用
  6. Solr比较成熟没有一个更大、更成熟的用户、开发和贡献者社区,而es相对开发维护者比较少,更新太快学习成本高

下载

image-20210818173234918

1
2
3
4
5
6
7
8
9
/bin 启动文件
/config 配置文件
/log4j2 日志文件
/jvm.options java虚拟机相关的配置 内存小的可以配置 -Xms
/elasticsearch.yml es的配置文件 默认9200端口 跨域问题
/lib 相关jar包
/logs 日志
/modules 功能模块
/plugins 插件 ik

默认情况下ES只允许本机访问,如果需要远程访问可以修改ES安装目录下面的config/elasticsearch.yml文件,去掉network.host的注释,将他的值改为0.0.0.0,然后重新启动

1
2
# 0.0.0.0可以让任何人访问,上线时应该时具体的IP地址
network.host:0.0.0.0

启动

双击/bin/elasticsearch.bat 文件

image-20210818175204320

image-20210818175433324

安装可视化界面

  1. 配置跨域

    1
    2
    3
    4
    # 打开跨域支持
    http.cors.enabled: true
    # 设置为允许所有人访问
    http.cors.allow-origin: "*"

    image-20210818181524735

  2. 启动指令

    1
    2
    1. npm install
    2. net run start

image-20210818182617047

初学可以把es当作数据库,可以建立索引(库),文档(库中的数据)这个head我们就把它当作数据展示工具

了解Elk

ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称。市面上也被成为Elastic Stack。其中Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用Elasticsearch作为底层支持框架,可见Elasticsearch提供的搜索能力确实强大,市面上很多时候我们简称Elasticsearch为es。Logstash是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ)收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等 )。Kibana可以将elasticsearch的数据通过友好的页面展示出来,提供实时分析的功能。市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。

image-20210818195903364

安装Kibana

Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana ,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板( dashboard )实时显示Elasticseaich查询动态。设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。

目录

image-20210818201754160

image-20210818202509587

在/config/kibana.yml里面进行汉化

elasticsearch是面向文档的,关系型数据库和elasticsearch的对比

关系型数据库 ElasticSearch
数据库 DB 索引(includes)
表(tables) types(慢慢在弃用)
行(rows) document
字段(columns) fields

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

物理设计

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

文档

就是我们的一条条数据

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

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

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

类型

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

索引

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

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

image-20210818213035706

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

image-20210818212805059

倒排锁引

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

image-20210818214001508

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

image-20210818214631688

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

image-20210818214940948

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

IK分词器

安装

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

  2. 下载完毕之后解压放到elasticsearch插件中即可

  3. 重启ElasticSearch

    image-20210819155215150

  4. 也可以通过elasticsearch-plugin list来查看加载进来的插件

  5. 使用kibana测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    GET _analyze
    {
    "analyzer": "ik_smart",
    "text": "Dean"
    }

    GET _analyze
    {
    "analyzer": "ik_max_word",
    "text": "隔壁老樊"
    }
    //
    {
    "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
    }
    ]
    }
  6. IK分词器增加自己的配置

    • 新建一个.dic文件

      image-20210819162238583

    • 在里面添加我们需要的词语

    • 把我们的字典配置ik的配置文件中

      image-20210819162734350

      image-20210819163140511

    • 再次测试

      image-20210819164610288

    Rest风格说明

    image-20210819164828608

    基础测试

    1. 创建一个索引

      1
      2
      3
      4
      5
      6
      7
      PUT /索引名/类型名/文档id
      {请求体}
      PUT /test1/type1/1
      {
      "name":"Dean超给力",
      "age":18
      }

      image-20210819165444903

    2. ~~~ json
      // 新建一个索引,这一个就相当于是一种规范对字段有约束,就类似于MySQL类里面的字段类型
      PUT /test2
      {
      “mappings”: {

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

      }
      }

      1
      2
      3
      4
      5
      6
      7

      如果我们的文档字段没有指定类型,那么es就会给我们配置默认的字段类型

      **扩展**:通过命令查看elasticsearch索引情况

      ~~~ bash
      GET _cat/

    修改

    1
    2
    3
    4
    5
    6
    POST /test2/_doc/2/_update
    {
    "doc":{
    "name":"法外狂徒王五"
    }
    }

    1
    2
    3
    4
    // 查找所有的
    POST /test2/_doc/_search
    // 获取指定id的
    GET /test2/_doc/2

    删除索引

    1
    2
    3
    DELETE test1
    // 删除指定的文档
    DELETE /test2/_doc/2

    关于文档的操作

    添加数据

    1
    2
    3
    4
    5
    6
    PUT /test2/_doc/5
    {
    "name":"王富国",
    "age":18,
    "birthday":"1999-03-07"
    }

    查询数据

    1
    2
    3
    4
    // 查询所有的数据
    POST /test2/_search
    // 查询指定的
    GET /test2/_doc/3

    更新数据

    1
    2
    3
    4
    5
    6
    PUT /test2/_doc/5
    {
    "name":"王国副",
    "age":18,
    "birthday":"1999-03-07"
    }
    image-20210819175705264

集成springboot

导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<-- 导入依赖,一个高级客户端的依赖,一个服务的依赖,两个都必须要导入-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.14.0</version>
</dependency>

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

配置配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.dean.config;


import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticSearchConfig {

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

测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void contextLoads() throws IOException {
// 创建索引请求
CreateIndexRequest request = new CreateIndexRequest("dean3_index");
// 执行请求
CreateIndexResponse createIndexResponse = restHighLevelClient
.indices()
.create(request, RequestOptions.DEFAULT);
System.out.println("1111111111111110");
System.out.println(createIndexResponse);
}
}

获取客户端索引是否存在

1
2
3
4
5
6
7
@Test
void testExistIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("dean3_index");
boolean exists = restHighLevelClient.indices()
.exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}

image-20210821114536794

添加一个文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testAddDoc() throws IOException {
User user = new User("Dean",18);
// 创建请求
IndexRequest request = new IndexRequest("dean_index");
// 规则
request.id("1");
// 设置超时时间
request.timeout("1s");
// 将我们的数据放入请求 json
// 把我们的数据转换为json,然后放到resource中
request.source(JSON.toJSONString(user),XContentType.JSON);
// 客户端发送请求
IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());

// 对应我们命令的返回状态CREATED
System.out.println(indexResponse.status());
}

我们可以将需要添加的文档写成一个java实体,然后将 Java实体转换为json然后再放到请求体中

判断文档是否存在

1
2
3
4
5
6
7
8
@Test 
void testDocExit() throws IOException {
// 首先构造一个get请求
GetRequest getRequest = new GetRequest("dean_index", "1");
// 然后在判断是否存在
boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}

获取文档的相关信息

1
2
3
4
5
6
7
8
9
@Test
void getDocument() throws IOException {
// 首先还是构建get请求,哪个索引,哪个文档
GetRequest getRequest = new GetRequest("dean_index", "1");
// 获得请求响应
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
// 响应里面有文档里所有的内容我们可以选择查看
System.out.println(getResponse.getSource());
}

查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void testSearch() throws IOException {
// 新建一个查询请求
SearchRequest searchRequest = new SearchRequest("dean_index");

// 构建搜索查询
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

// 构造查询条件,我们可以使用QueryBuilder工具来实现
// QueryBuilders.termQuery精确查询
// QueryBuilders.matchAllQuery匹配所有
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "dean");
searchSourceBuilder.query(termQueryBuilder);
// 设置查询超时时间
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
System.out.println("===============");
for (SearchHit documentFields : searchResponse.getHits().getHits()) {
System.out.println(documentFields.getSourceAsMap());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 这个就是searchReponse.getHits()
{
"fragment":true,
"hits":[
{"fields":{},
"fragment":false,
"highlightFields":{},
"id":"1",
"matchedQueries":[],
"primaryTerm":0,
"rawSortValues":[],
"score":1.540445,
"seqNo":-2,
"sortValues":[],
"sourceAsMap":{"name":"dean","age":1},
"sourceAsString":"{\"age\":1,\"name\":\"dean\"}",
"sourceRef":{"fragment":true},
"type":"_doc","version":-1}
],
"maxScore":1.540445,
"totalHits":{
"relation":"EQUAL_TO",
"value":1}
}

实战

爬虫

数据问题?数据库获取,消息队列获取,都可以成为数据源

爬取数据:获取请求返回的页面信息,筛选出我们想要的数据

jsoup包

  1. 导入依赖

  2. 写一个爬取网页的工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package com.dean.deanjd.utils;

    import com.dean.deanjd.pojo.Content;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    import org.springframework.stereotype.Component;

    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;

    @Component
    public class HtmlPraseUtils {

    public static void main(String[] args) throws IOException {
    new HtmlPraseUtils().PraseJD("java").forEach(System.out::println);
    }

    public List<Content> PraseJD(String keywords) throws IOException {
    HashMap<String, String> header = new HashMap<>();
    header.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
    header.put("Accept-Language", "zh-cn,zh;q=0.5");
    header.put("Accept-Charset", "GB2312,utf-8;q=0.7,*;q=0.7");
    header.put("Connection", "keep-alive");
    header.put("cookie","qrsc=3");
    header.put("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36");
    // 构建url
    String url = "https://search.jd.com/Search?keyword=" + keywords;

    // 解析网页 (jsoup返回的Document就是浏览器的document对象)
    // 直接请求会跳转至登录界面因为请求头里没有cookie或者token请求会被拦截
    Document document = Jsoup.connect(url).headers(header).get();
    Element jGoodsList = document.getElementById("J_goodsList");
    // 获取所有的li标元素
    Elements elements = jGoodsList.getElementsByTag("li");
    // 获取元素中的内容,这里的el就是每一个li标签了
    ArrayList<Content> goodList = new ArrayList<>();
    for (Element el : elements) {
    // 关于这种图片特别多的网站,所有的图片都是延迟加载的
    String img = el.getElementsByTag("img").eq(0).attr("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);
    goodList.add(content);
    }
    return goodList;
    }
    }

    说一下这里需要注意的问题

    1. 现在的网页想要请求服务器,后端服务器会对请求先进行拦截,看你有没有携带cookie或者token,如果没有cookie或者token直接拒绝访问请求,要求重新登录。所以这里有的时候是不能直接进行connect的有的时候要设置头部信息
    2. 一般有大量图片的网站,他们的图片都不是直接加载的,都是懒加载,所以想要获取图片的时候要注意它对应的属性
    3. 通过connect获得页面后我们就可以像js一样操作页面了
      • getElementsByTag获取指定的标签
      • getElementById获取指定id的元素
      • el.getElementsByTag(“img”).eq(0).attr(“data-lazy-img”),意思是获取img标签里的data-lazy-img的属性值
      • el.getElementsByClass(“p-price”).eq(0).text();获取类p-price的文字,eq(0)代表第一个
  3. 编写服务层将爬取到的数据插入到es

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 把解析的数据放到es中,并返回是否插入成功
    public Boolean praseContent(String keywords) throws IOException {
    // 解析数据
    List<Content> contentList = htmlPraseUtils.PraseJD("java");
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("2m");
    for (Content content : contentList) {
    bulkRequest.add(
    // 需要操作的数据库
    new IndexRequest("good_list")
    // 往数据库插入的内容
    .source(JSON.toJSONString(content), XContentType.JSON)
    );
    }
    // 执行请求
    BulkResponse responses = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    return !responses.hasFailures();
    }
  4. 查找数据

    前面我们已经爬取到了数据,并 把数据存到了es里面,现在我们要从es里面是把数据读出来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    指定搜索哪个索引
    SearchRequest searchRequest = new SearchRequest("good_list");

    // 新建一个搜索
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

    // 构造搜索条件
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keywords);

    // 指定搜索的分页
    searchSourceBuilder.from(pageNum);
    searchSourceBuilder.size(pageSize);

    // 根据查询条件构造出一个完整的查询
    searchSourceBuilder.query(termQueryBuilder);
    searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

    // 把这个完整的搜索和索引结合起来
    searchRequest.source(searchSourceBuilder);
    // 客户端执行搜索
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);

    ArrayList<Map<String, Object>> list = new ArrayList<>();
    // 获取返回的结果
    for (SearchHit hit : searchResponse.getHits().getHits()) {
    list.add(hit.getSourceAsMap());
    }
    return list;
    }
  5. 前端调用后端es查询的接口,前端拿到数据后直接把数据渲染到模板上去。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!