Elasticsearch学习笔记-狂神说

目录

一、Elasticsearch的安装、kibana、Ik分词器

二、Elasticsearch所使用的lucene核心

三、Elasticsearch介绍、及和redis、solr的对比

四、新建Elasticsearch项目,文档索引的增删改查

五、模仿京东搜索、爬虫

六、高亮和VUE


Gitee仓库:狂神-Elasticsearch: 跟随狂神视频教程,完成的练习项目 - Gitee.com

课程地址:1、ElasticSearch课程简介_哔哩哔哩_bilibili

一、Elasticsearch的安装、kibana、Ik分词器

安装资料合集地址:百度网盘 请输入提取码
提取码:qk8p

1、解压缩得到Elasticsearch本体、可视化谷歌插件、ik分词器、kibana交互式工具

Elasticsearch学习笔记-狂神说_第1张图片

JDK版本最低要8

2、在windows上安装Elasticsearch

下载地址:下载 Elastic 产品 | Elastic

点击第一个文件夹,继续点击进入bin目录

Elasticsearch学习笔记-狂神说_第2张图片

点击Elasticsearch.bat启动脚本,运行30秒后出现started代表成功

访问localhost:9200,可以看到版本信息,代表安装成功

Elasticsearch学习笔记-狂神说_第3张图片

3、在windows上安装Elasticsearch-head谷歌可视化插件

因为我本地没有npm环境,所以采用了谷歌商店的安装方式

访问谷歌商店,魔法道具请自行寻找

Elasticsearch学习笔记-狂神说_第4张图片

搜索得到Elasticsearch-head

Elasticsearch学习笔记-狂神说_第5张图片

下载安装,并启动插件

Elasticsearch学习笔记-狂神说_第6张图片

4、在windows上安装ik分词器

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

找到bin目录下面的plugins文件夹

Elasticsearch学习笔记-狂神说_第7张图片

将之前解压出来的elasticsearch-analysis-ik-7.6.1文件夹复制过来即可

5、在windows上安装kibana

下载地址:下载 Elastic 产品 | Elastic

点击kibana文件夹,继续点击进入bin目录

Elasticsearch学习笔记-狂神说_第8张图片

点击kibana.bat启动脚本,运行30秒后出现running at localhost:5601代表成功

访问 localhost:5601

Elasticsearch学习笔记-狂神说_第9张图片

使用kibana进行测试,点击Dev Tools进入控制台

Elasticsearch学习笔记-狂神说_第10张图片

二、Elasticsearch所使用的lucene核心

1、lucene发展史

 Elasticsearch学习笔记-狂神说_第11张图片1997 年年末,Doug Cutting (lucene 的原作者) 因为工作不稳定,产生了把自己的一些软件商业化的想法。再加上当时 Java 已经是一门炙手可热的编程语言 , 作者也正好需要一个学习它的理由。因为作者以前有编写搜索软件的经验,于是觉得可以用 Java 写一个搜索软件以维持生计。于是 lucene 就此诞生。

        2000 年 ,作者由于对商业运作并没有什么兴趣 ,就把 lucene 放到了 SourceForge 上 , 希望通过开源的方式来让自己保持程序创作的激情。2000年10月份发布1.0版本,2001年7月发布了最后一个SourceForge版本1.01b。

        2001年9月份Lucene作为高质量的Java开源软件产品加入Apache软件基金会的Jakarta家族。2005年Lucene升级成为Apache顶级项目。目前Lucene包含大量相关项目,具体情况可以访问http://lucene.apache.org/网站了解。

       自从Lucene成为Apache开源项目以后,项目质量得到明显增强,也吸引了越来越多的用户和开发者的关注和参与,Lucene社区规模也不断发展壮大。2010 年 ,原作者已经不再参与 lucene 的日常开发和维护了 , 已经有很多对 lucene 核心有着深入了解的开发者参与到项目中。

        截止到2013年初,最新的Lucene版本是4.1。lucene 发展出了入 C++ , C# , Prel , Python 等其他语言的版本。

Elasticsearch学习笔记-狂神说_第12张图片

2、lucene源码的增删改查

参考博客:Lucene介绍与使用_lucene使用_是小方啦的博客-CSDN博客

创建索引的lucene源代码

 // 创建索引
    @Test
    public void testCreate() throws Exception{
        //1 创建文档对象
        Document document = new Document();
        // 创建并添加字段信息。参数:字段的名称、字段的值、是否存储,这里选Store.YES代表存储到文档列表。Store.NO代表不存储
        document.add(new StringField("id", "1", Field.Store.YES));
        // 这里我们title字段需要用TextField,即创建索引又会被分词。 
        document.add(new TextField("title", "谷歌地图之父跳槽facebook", Field.Store.YES));

        //2 索引目录类,指定索引在硬盘中的位置
        Directory directory = FSDirectory.open(new File("d:\\indexDir"));
        //3 创建分词器对象
        Analyzer analyzer = new StandardAnalyzer();
        //4 索引写出工具的配置对象
        IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
        //5 创建索引的写出工具类。参数:索引的目录和配置信息
        IndexWriter indexWriter = new IndexWriter(directory, conf);

        //6 把文档交给IndexWriter
        indexWriter.addDocument(document);
        //7 提交
        indexWriter.commit();
        //8 关闭
        indexWriter.close();
 }

查询索引

@Test
    public void testSearch() throws Exception {
        // 索引目录对象
        Directory directory = FSDirectory.open(new File("d:\\indexDir"));
        // 索引读取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜索工具
        IndexSearcher searcher = new IndexSearcher(reader);

        // 创建查询解析器,两个参数:默认要查询的字段的名称,分词器
        QueryParser parser = new QueryParser("title", new IKAnalyzer());
        // 创建查询对象
        Query query = parser.parse("谷歌");

        // 搜索数据,两个参数:查询条件对象要查询的最大结果条数
        // 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。
        TopDocs topDocs = searcher.search(query, 10);
        // 获取总条数
        System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");
        // 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文档编号
            int docID = scoreDoc.doc;
            // 根据编号去找文档
            Document doc = reader.document(docID);
            System.out.println("id: " + doc.get("id"));
            System.out.println("title: " + doc.get("title"));
            // 取出文档得分
            System.out.println("得分: " + scoreDoc.score);
        }
    }

修改索引

/* 测试:修改索引
     * 注意:
     * 	A:Lucene修改功能底层会先删除,再把新的文档添加。
     * 	B:修改功能会根据Term进行匹配,所有匹配到的都会被删除。这样不好
     * 	C:因此,一般我们修改时,都会根据一个唯一不重复字段进行匹配修改。例如ID
     * 	D:但是词条搜索,要求ID必须是字符串。如果不是,这个方法就不能用。
     * 如果ID是数值类型,我们不能直接去修改。可以先手动删除deleteDocuments(数值范围查询锁定ID),再添加。
     */
@Test
public void testUpdate() throws Exception{
    // 创建目录对象
    Directory directory = FSDirectory.open(new File("indexDir"));
    // 创建配置对象
    IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
    // 创建索引写出工具
    IndexWriter writer = new IndexWriter(directory, conf);

    // 创建新的文档数据
    Document doc = new Document();
    doc.add(new StringField("id","1",Store.YES));
    doc.add(new TextField("title","谷歌地图之父跳槽facebook ",Store.YES));
    /* 修改索引。参数:
         * 	词条:根据这个词条匹配到的所有文档都会被修改
         * 	文档信息:要修改的新的文档数据
         */
    writer.updateDocument(new Term("id","1"), doc);
    // 提交
    writer.commit();
    // 关闭
    writer.close();
}

删除索引

/*
     * 演示:删除索引
     * 注意:
     * 	一般,为了进行精确删除,我们会根据唯一字段来删除。比如ID
     * 	如果是用Term删除,要求ID也必须是字符串类型!
     */
@Test
public void testDelete() throws Exception {
    // 创建目录对象
    Directory directory = FSDirectory.open(new File("indexDir"));
    // 创建配置对象
    IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
    // 创建索引写出工具
    IndexWriter writer = new IndexWriter(directory, conf);

    // 根据词条进行删除
    //		writer.deleteDocuments(new Term("id", "1"));

    // 根据query对象删除,如果ID是数值类型,那么我们可以用数值范围查询锁定一个具体的ID
    //		Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
    //		writer.deleteDocuments(query);

    // 删除所有
    writer.deleteAll();
    // 提交
    writer.commit();
    // 关闭
    writer.close();
}

分页查询

// 分页
	@Test
	public void testPageQuery() throws Exception {
		// 实际上Lucene本身不支持分页。因此我们需要自己进行逻辑分页。我们要准备分页参数:
		int pageSize = 2;// 每页条数
		int pageNum = 3;// 当前页码
		int start = (pageNum - 1) * pageSize;// 当前页的起始条数
		int end = start + pageSize;// 当前页的结束条数(不能包含)
		
		// 目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建读取工具
		IndexReader reader = DirectoryReader.open(directory);
		// 创建搜索工具
		IndexSearcher searcher = new IndexSearcher(reader);
		
		QueryParser parser = new QueryParser("title", new IKAnalyzer());
		Query query = parser.parse("谷歌地图");
		
		// 创建排序对象,需要排序字段SortField,参数:字段的名称、字段的类型、是否反转如果是false,升序。true降序
		Sort sort = new Sort(new SortField("id", Type.LONG, false));
		// 搜索数据,查询0~end条
		TopDocs topDocs = searcher.search(query, end,sort);
		System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
		
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (int i = start; i < end; i++) {
			ScoreDoc scoreDoc = scoreDocs[i];
			// 获取文档编号
			int docID = scoreDoc.doc;
			Document doc = reader.document(docID);
			System.out.println("id: " + doc.get("id"));
			System.out.println("title: " + doc.get("title"));
		}
	}

3、lucene的核心原理

 参考博客:Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理_njpjsoftdev的博客-CSDN博客

参考教程:1.光速入门ES底层内核Lucene_哔哩哔哩_bilibili

倒排索引又叫反向索引(右下图)以字或词为文档中出现的位置情况。

Elasticsearch学习笔记-狂神说_第13张图片

在实际的运用中,我们可以对数据库中原始的数据结构(左图),在业务空闲时事先根据左图内容,创建新的倒排索引结构的数据区域(右图)。

用户有查询需求时,先访问倒排索引数据区域(右图),得出文档id后,通过文档id即可快速,准确的通过左图找到具体的文档内容。

这一过程,可以通过我们自己写程序来实现,也可以借用已经抽象出来的通用开源技术来实现。

Elasticsearch学习笔记-狂神说_第14张图片

lucene的文件结构

Elasticsearch学习笔记-狂神说_第15张图片

lucene的数据类型

Elasticsearch学习笔记-狂神说_第16张图片

三、Elasticsearch介绍、及和redis、solr的对比

1、Elasticsearch的历史

Elasticsearch学习笔记-狂神说_第17张图片

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

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

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

        第一个公开版本出现在2010年2月,在那之后Elasticsearch已经成为Github上最受欢迎的项目之一,代码贡献者超过300人。一家主营Elasticsearch的公司就此成立,他们一边提供商业支持一边开发新功能,不过Elasticsearch将永远开源且对所有人可用。

        Shay的妻子依旧等待着她的食谱搜索…..

2、什么是Elasticsearch

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

        据国际权威的数据库产品评测机构DB Engines的统计,在2016年1月,ElasticSearch已超过Solr等,成为排名第一的搜索引擎类应用

谁在使用:

1、维基百科,类似百度百科,全文检索,高亮,搜索推荐/2
2、The Guardian (国外新闻网站) ,类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论) +社交网络数据(对某某新闻的相关看法) ,数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈(好,坏,热门,垃圾,鄙视,崇拜)
3、Stack Overflow (国外的程序异常讨论论坛) , IT问题,程序的报错,提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案
4、GitHub (开源代码管理),搜索 上千亿行代码
5、电商网站,检索商品
6、日志数据分析, logstash采集日志, ES进行复杂的数据分析, ELK技术, elasticsearch+logstash+kibana
7、商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅牙膏的监控,如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买
8、BI系统,商业智能, Business Intelligence。比如说有个大型商场集团,BI ,分析一下某某区域最近3年的用户消费 金额的趋势以及用户群体的组成构成,产出相关的数张报表, **区,最近3年,每年消费金额呈现100%的增长,而且用户群体85%是高级白领,开-个新商场。ES执行数据分析和挖掘, Kibana进行数据可视化
9、国内:站内搜索(电商,招聘,门户,等等),IT系统搜索(OA,CRM,ERP,等等),数据分析(ES热门
的一一个使用场景)

3、对比Redis

参考博客:聊聊redis和Elasticsearch_elasticsearch和redis_小叶曲的博客-CSDN博客

项目 Redis Elasticsearch
介绍 Redis是内存中的数据结构存储,用作数据库,缓存和消息代理 Elasticsearch是一个基于Apache Lucene的现代搜索和分析引擎
主数据库模型 键值存储 搜索引擎
DB-Engines排名 得分120.41总排名第9,key-value存储排名第7 得分120.00总排名第10,搜索引擎排名第1
网站 redis.io www.elastic.co/cn/elasticsearch
技术文档 redis.io/documentation www.elastic.co/cn/elasticsearch/features
由开发 Salvatore Sanfilippo
初始发行 2009 2010
当前版本 5.0.8,2020年3月 7.6.1,2020年3月
许可证信息 开源 开源
基于云的信息 没有 没有
实现语言 C Java
支持的操作系统 BSD Linux OS X Windows 所有带有Java VM的操作系统
数据scheme 无scheme 无scheme
打字 局部
XML支持   没有
二级索引 没有
SQL 没有 没有
API和其他访问方法 专有协议 Java API RESTful HTTP / JSON API
支持的编程语言 C C#C ++ Clojure Crystal D Dart Elixir Erlang Fancy Go Haskell Haxe Java JavaScript(Node.js)Lisp Lua MatLab Objective-C OCaml Perl PHP Prolog Python R Rebol Ruby Rust Scala Smalltalk Tcl .Net Clojure Erlang Go Groovy Haskell Java JavaScript Lua Perl PHP Python Ruby Scala
服务器端脚本 Lua
触发器 没有
分区方法 拆分 拆分
复制方法 主从复制
MapReduce的 没有 没有
一致性概念 最终的一致性 最终的一致性
外键 没有 没有

4、对比Solr

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

当单纯的对已有数据进行搜索时,Solr更快

Elasticsearch学习笔记-狂神说_第18张图片

当实时建立索引时,Solr会产生io阻塞,查询性能较差,ElasticSearch具有明显的优势

Elasticsearch学习笔记-狂神说_第19张图片

随着数据量的增加,Solr的搜索效率会变得更低,而ElasticSearch却没有明显的变化

Elasticsearch学习笔记-狂神说_第20张图片

转变我们的搜索基础设施后从Solr ElasticSearch,我们看见一个即时~ 50x提高搜索性能!

Elasticsearch学习笔记-狂神说_第21张图片

总结

1、es基本是开箱即用(解压就可以用!) ,非常简单。Solr安装略微复杂一丢丢!
2、Solr 利用Zookeeper进行分布式管理,而Elasticsearch自身带有分布式协调管理功能
3、Solr 支持更多格式的数据,比如JSON、XML、 CSV ,而Elasticsearch仅支持json文件格式
4、Solr 官方提供的功能更多,而Elasticsearch本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑
5、Solr 查询快,但更新索引时慢(即插入删除慢) ,用于电商等查询多的应用;

  • ES建立索引快(即查询慢) ,即实时性查询快,用于facebook新浪等搜索。
  • Solr是传统搜索应用的有力解决方案,但Elasticsearch更适用于新兴的实时搜索应用。

6、Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而Elasticsearch相对开发维护者较少,更新太快,学习使用成本较高。

四、Kibana测试增删改查

 1、ELK三剑客

ELK是Elasticsearch、Logstash、 Kibana三大开源框架首字母大写简称。市面上也被成为Elastic Stack。

Elasticsearch学习笔记-狂神说_第22张图片

  • 其中Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。
  • Logstash是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ )收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。
  • Kibana可以将elasticsearch的数据通过友好的页面展示出来 ,提供实时分析的功能

收集清洗数据(Logstash) ==> 搜索、存储(ElasticSearch) ==> 展示(Kibana)

市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称 ,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。

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/索引名称/类型名称/文档id/_search 查询所有数据

3、使用kibana测试Elasticsearch增删改查

暂时没写

五、新建Elasticsearch项目,文档索引的增删改查

1、新建项目

新建SpringBoot项目,勾选Elasticsearch依赖

Elasticsearch学习笔记-狂神说_第23张图片

项目结构

Elasticsearch学习笔记-狂神说_第24张图片

导入依赖,本人用的SpringBoot是2.3.2.RELEASE

        
            org.springframework.boot
            spring-boot-starter-data-elasticsearch
        
        
        
            com.alibaba
            fastjson
            1.2.71
        
        
        
            org.projectlombok
            lombok
            true
        

编写ElasticsearchConfig.java配置类

@Configuration
public class ElasticsearchConfig {

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

}

编写User.java实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = -3843548915035470817L;

    private String name;
    private Integer age;
}

编写索引操作方法

@SpringBootTest
class KuangApplicationTests {

    @Autowired
    public RestHighLevelClient restHighLevelClient;

    // 测试索引的创建
    @Test
    public void textCreateIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("乱码测试");
        CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);

        System.out.println(response.isAcknowledged());
        restHighLevelClient.close();
    }

    // 测试索引的查询
    @Test
    public void textCreateIsExists() throws IOException {
        GetIndexRequest request = new GetIndexRequest("sly1");
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);

        System.out.println(exists);
        restHighLevelClient.close();
    }

    // 测试索引的删除
    @Test
    public void textCreateDelete() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("sly");
        AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);

        System.out.println(response.isAcknowledged());
        restHighLevelClient.close();
    }
}

2、进行文档的增删改查

新增

    // 测试文档的创建
    @Test
    public void textAddDocument() throws IOException {
        User user = new User("小狂狂", 18);

        // 创建文档
        IndexRequest request = new IndexRequest("文档1号");
        // 设置文档的ID
        request.id("2");
        // 设置超时时间1秒
        request.timeout(TimeValue.timeValueMillis(1000));
        // 将请求放入
        request.source(JSON.toJSONString(user), XContentType.JSON);
        IndexResponse index = restHighLevelClient.index(request, RequestOptions.DEFAULT);

        // 获取索引的状态
        System.out.println(index.status());
        System.out.println(index);
        /**
         *  CREATED
         * IndexResponse[index=文档1号,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,
         * shards={"total":2,"successful":1,"failed":0}]
         */
        restHighLevelClient.close();
    }

查询

    // 测试文档的查询
    @Test
    public void textGetDocument() throws IOException {
        GetRequest request = new GetRequest("文档1号", "1");
        GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);

        // 打印文档的内容
        System.out.println(response.getSourceAsString());
        System.out.println(response);
        /**
         *  {"age":18,"name":"小狂狂"}
         * {"_index":"文档1号","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,
         * "found":true,"_source":{"age":18,"name":"小狂狂"}}
         */
        restHighLevelClient.close();
    }

修改

    // 测试文档的更新
    @Test
    public void textUpdateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("文档1号", "1");
        User user = new User("lmk", 11);
        request.doc(JSON.toJSONString(user), XContentType.JSON);
        UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);

        System.out.println(response.status());
        System.out.println(response);
        /**
         *  OK
         * UpdateResponse[index=文档1号,type=_doc,id=1,version=4,seqNo=3,primaryTerm=1,result=updated,
         * shards=ShardInfo{total=2, successful=1, failures=[]}]
         */
        restHighLevelClient.close();
    }

删除

    // 测试文档的删除
    @Test
    public void textDeleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("文档1号", "2");
        request.timeout("1s");
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);

        System.out.println(response.status());
        System.out.println(response);
        /**
         *  OK
         * DeleteResponse[index=文档1号,type=_doc,id=1,version=2,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]
         */
        restHighLevelClient.close();
    }

判断是否存在

    // 测试文档的存在
    @Test
    public void textExistsDocument() throws IOException {
        GetRequest request = new GetRequest("文档1号", "1");
        request.fetchSourceContext(new FetchSourceContext(false));
        request.storedFields("_nine_");
        boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);

        System.out.println(exists);
        restHighLevelClient.close();
    }

3、进行复杂查询新增操作

复杂查询、高亮

    // 查询
    // SearchRequest 搜索请求
    // SearchSourceBuilder 条件构造
    // HighlightBuilder 高亮
    // TermQueryBuilder 精确查询
    // MatchAllQueryBuilder
    // xxxQueryBuilder ...
    @Test
    public void testSearch() throws IOException {
        // 1.创建查询请求对象
        SearchRequest searchRequest = new SearchRequest();
        // 2.构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // (1)查询条件 使用QueryBuilders工具类创建
        // 精确查询
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "liuyou");
        // 匹配查询
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        // (2)其他<可有可无>:(可以参考 SearchSourceBuilder 的字段部分)
        // 设置高亮
        searchSourceBuilder.highlighter(new HighlightBuilder());
        // 分页,默认已经配置了
        searchSourceBuilder.from();
        searchSourceBuilder.size();
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        // (3)条件投入
        searchSourceBuilder.query(matchAllQueryBuilder);
        // 3.添加条件到请求
        searchRequest.source(searchSourceBuilder);
        // 4.客户端查询请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 5.查看返回结果
        SearchHits hits = searchResponse.getHits();

        System.out.println(JSON.toJSONString(hits));
        for (SearchHit documentFields : hits.getHits()) {
            System.out.println(documentFields.getSourceAsMap());
        }
        /**
         * {"fragment":true,"hits":[],"maxScore":null,"totalHits":{"relation":"EQUAL_TO","value":0}}
         */
    }

失败的批量新增

    // 上面的这些api无法批量增加数据(只会保留最后一个source)
    @Test
    public void testBatchAdd() throws IOException {
        IndexRequest request = new IndexRequest("bulk");// 没有id会自动生成一个随机ID
        request.id("3");
        request.source(JSON.toJSONString(new User("liu", 1)), XContentType.JSON);
        request.source(JSON.toJSONString(new User("min", 2)), XContentType.JSON);
        request.source(JSON.toJSONString(new User("kai", 3)), XContentType.JSON);

        IndexResponse index = restHighLevelClient.index(request, RequestOptions.DEFAULT);

        System.out.println(index.status());
        System.out.println(index);
        /**
         * CREATED
         * IndexResponse[index=bulk,type=_doc,id=3,version=1,result=created,seqNo=1,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
         */
    }

成功的批量新增

    // 特殊的,真的项目一般会 批量插入数据
    @Test
    public void testBulk() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        ArrayList users = new ArrayList<>();
        users.add(new User("liuyou-1", 1));
        users.add(new User("liuyou-2", 2));
        users.add(new User("liuyou-3", 3));
        users.add(new User("liuyou-4", 4));
        users.add(new User("liuyou-5", 5));
        users.add(new User("liuyou-6", 6));

        // 批量请求处理
        for (int i = 0; i < users.size(); i++) {
            bulkRequest.add(
                    // 这里是数据信息
                    new IndexRequest("bulk")
                            .id("" + (i + 1)) // 没有设置id 会自定生成一个随机id
                            .source(JSON.toJSONString(users.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);

        System.out.println(bulk.status());
        System.out.println(bulk);
        /**
         * OK
         * org.elasticsearch.action.bulk.BulkResponse@77ab5214
         */
    }

五、模仿京东搜索、爬虫

1、解决控制台乱码

打开config文件夹,找到jvm.options文件

Elasticsearch学习笔记-狂神说_第25张图片

在文件里加上 -Dfile.encoding=GBK

Elasticsearch学习笔记-狂神说_第26张图片

2、项目构建

追加导入依赖

        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
            2.2.13.RELEASE
        

        
        
        
            org.jsoup
            jsoup
            1.10.2
        

        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        

打开第一节下载的文件,将其中的前端素材复制过来

Elasticsearch学习笔记-狂神说_第27张图片

Elasticsearch学习笔记-狂神说_第28张图片

编写application.properties配置文件

# 防止端口冲突
server.port=9999
# 关闭thymeleaf缓存
spring.thymeleaf.cache=false

编写IndexController.java

@Controller
public class IndexController {

    @RequestMapping({"toIndex","/"})
    public String toIndex() {
        System.out.println("toIndex方法");
        return "/index";
    }
}

启动项目测试

Elasticsearch学习笔记-狂神说_第29张图片

3、爬虫原理分析

搜索京东搜索页面,并分析页面

https://search.jd.com/search?keyword=java

Elasticsearch学习笔记-狂神说_第30张图片

F12打开审查元素,找到目标元素img、name、price

Elasticsearch学习笔记-狂神说_第31张图片

创建HtmlParseUtil,并简单编写​​​​​​​
public class HtmlParseUtil {
    public static void main(String[] args) throws IOException {
        /// 使用前需要联网
        // 请求url
        String url = "http://search.jd.com/search?keyword=java";
        // 1.解析网页(jsoup 解析返回的对象是浏览器Document对象)
        Document document = Jsoup.parse(new URL(url), 30000);
        // 使用document可以使用在js对document的所有操作
        // 2.获取元素(通过id)
        Element j_goodsList = document.getElementById("J_goodsList");
        // 3.获取J_goodsList ul 每一个 li
        Elements lis = j_goodsList.getElementsByTag("li");
        // 4.获取li下的 img、price、name
        for (Element li : lis) {
            String img = li.getElementsByTag("img").eq(0).attr("src");// 获取li下 第一张图片
            String name = li.getElementsByClass("p-name").eq(0).text();
            String price = li.getElementsByClass("p-price").eq(0).text();
            System.out.println("=======================");
            System.out.println("img : " + img);
            System.out.println("name : " + name);
            System.out.println("price : " + price);
        }
    }
}

运行结果

Elasticsearch学习笔记-狂神说_第32张图片

图片没有,一般图片特别多的网站,所有的图片都是通过延迟加载的

Elasticsearch学习笔记-狂神说_第33张图片

4、编写爬虫接口

编写Content.java实体类

Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {

    private String name;
    private String img;
    private String price;
}

编写ContentService.java业务类

@Service
public class ContentService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    // 1、解析数据放入 es 索引中
    public Boolean parseContent(String keyword) throws IOException {
        /* 获取内容 */
        List contents = HtmlParseUtil.parseJD(keyword);
        // 内容放入 es 中
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("2m"); // 可更具实际业务是指
        for (int i = 0; i < contents.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("jd_goods")
                            .id("" + (i + 1))
                            .source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        restHighLevelClient.close();
        return !bulk.hasFailures();
    }

    // 2、根据keyword分页查询结果
    public List> search(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
        if (pageIndex < 0) {
            pageIndex = 0;
        }
        SearchRequest jd_goods = new SearchRequest("jd_goods");
        // 创建搜索源建造者对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 条件采用:精确查询 通过keyword查字段name
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
        searchSourceBuilder.query(termQueryBuilder);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        // 分页
        searchSourceBuilder.from(pageIndex);
        searchSourceBuilder.size(pageSize);
        // 高亮
        // ....
        // 搜索源放入搜索请求中
        jd_goods.source(searchSourceBuilder);
        // 执行查询,返回结果
        SearchResponse searchResponse = restHighLevelClient.search(jd_goods, RequestOptions.DEFAULT);
        restHighLevelClient.close();
        // 解析结果
        SearchHits hits = searchResponse.getHits();
        List> results = new ArrayList<>();
        for (SearchHit documentFields : hits.getHits()) {
            Map sourceAsMap = documentFields.getSourceAsMap();
            results.add(sourceAsMap);
        }
        // 返回查询的结果
        return results;
    }
}

编写ContentController.java接口类

@Controller
public class ContentController {

    @Autowired
    private ContentService contentService;

    @ResponseBody
    @GetMapping("/parse/{keyword}")
    public Boolean parse(@PathVariable("keyword") String keyword) throws IOException {

        System.out.println("parse方法");
        return contentService.parseContent(keyword);
    }

    @ResponseBody
    @GetMapping("/search/{keyword}/{pageIndex}/{pageSize}")
    public List> parse(@PathVariable("keyword") String keyword,
                                           @PathVariable("pageIndex") Integer pageIndex,
                                           @PathVariable("pageSize") Integer pageSize) throws IOException {

        System.out.println("search方法");
        return contentService.search(keyword, pageIndex, pageSize);
    }
}

编写HtmlParseUtil.java爬虫工具类

public class HtmlParseUtil {
    public static void main(String[] args) throws IOException {
        System.out.println(parseJD("java"));
    }

    public static List parseJD(String keyword) throws IOException {
        // 使用前需要联网
        String url = "http://search.jd.com/search?keyword=" + keyword;
        // 设置cookie
        Map cookies = new HashMap();
        cookies.put("thor", "C4A4AD9C3168F09250467E0EBD6AFE81A42CD20AE853DEC1D45F02E03249B2C2444F82111661DD7FE1D03F0503C174C86D2B4457D8FA8AE63E8896434CEA43FD780830239AB7A0F929F6FF5BA158823DA7DF84FD28FEC9056BCEB61204EB5F8F9BAE613681CCDAAFAFA768D2C91C43C8DC7070192A62C517CDA347D47230FFE416A71AAC377DD76257F49FCBA4A67C92684CA45C5A778594E076C028233D601D;");
        // 1.解析网页(jsoup 解析返回的对象是浏览器Document对象)
        Document document = Jsoup.connect(url).cookies(cookies).get();
//        Document document = Jsoup.parse(new URL(url), 30000);
        // 使用document可以使用在js对document的所有操作
        // 2.获取元素(通过id)
        Element j_goodsList = document.getElementById("J_goodsList");
        // 3.获取J_goodsList ul 每一个 li
        Elements lis = j_goodsList.getElementsByTag("li");
//        System.out.println(lis);

        // 4.获取li下的 img、price、name
        // list存储所有li下的内容
        List contents = new ArrayList();
        int i = 0;
        for (Element li : lis) {
            if (i < 10) {
                i++;
                // 由于网站图片使用懒加载,将src属性替换为data-lazy-img
                String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
                // 获取li下 第一张图片
                String name = li.getElementsByClass("p-name").eq(0).text();
                String price = li.getElementsByClass("p-price").eq(0).text();
                // 封装为对象
                Content content = new Content(name, img, price);
                // 添加到list中
                contents.add(content);
                System.out.println(content);
            }
        }
        // 5.返回 list
        return contents;
    }
}

启动项目测试,调用parse接口

看到数据存入了ES

Elasticsearch学习笔记-狂神说_第34张图片

调用search方法

Elasticsearch学习笔记-狂神说_第35张图片

六、高亮和VUE

1、高亮

Controller添加高亮接口

    @ResponseBody
    @GetMapping("/h_search/{keyword}/{pageIndex}/{pageSize}")
    public List> highlightParse(@PathVariable("keyword") String keyword,
                                                    @PathVariable("pageIndex") Integer pageIndex,
                                                    @PathVariable("pageSize") Integer pageSize) throws IOException {

        System.out.println("h_search方法");
        return contentService.highlightSearch(keyword, pageIndex, pageSize);
    }

Service编写相关业务

    // 3、 在2的基础上进行高亮查询
    public List> highlightSearch(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 精确查询,添加查询条件
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchSourceBuilder.query(termQueryBuilder);
        // 分页
        searchSourceBuilder.from(pageIndex);
        searchSourceBuilder.size(pageSize);
        // 高亮 =========
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        highlightBuilder.preTags("");
        highlightBuilder.postTags("");
        searchSourceBuilder.highlighter(highlightBuilder);
        // 执行查询
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 解析结果 ==========
        SearchHits hits = searchResponse.getHits();
        List> results = new ArrayList<>();
        for (SearchHit documentFields : hits.getHits()) {
            // 使用新的字段值(高亮),覆盖旧的字段值
            Map sourceAsMap = documentFields.getSourceAsMap();
            // 高亮字段
            Map highlightFields = documentFields.getHighlightFields();
            HighlightField name = highlightFields.get("name");
            // 替换
            if (name != null) {
                Text[] fragments = name.fragments();
                StringBuilder new_name = new StringBuilder();
                for (Text text : fragments) {
                    new_name.append(text);
                }
                sourceAsMap.put("name", new_name.toString());
            }
            results.add(sourceAsMap);
        }
        return results;
    }

2、VUE

从第一节下载的课件里找到js文件

Elasticsearch学习笔记-狂神说_第36张图片

Elasticsearch学习笔记-狂神说_第37张图片

复制到项目里

Elasticsearch学习笔记-狂神说_第38张图片

 引入js


修改后的静态页面总览





    
    狂神说Java-ES仿京东实战
    
    



店铺: 狂神说Java

月成交999笔 评价 3

启动项目测试

你可能感兴趣的:(学习,笔记)