目录
一、Elasticsearch的安装、kibana、Ik分词器
二、Elasticsearch所使用的lucene核心
三、Elasticsearch介绍、及和redis、solr的对比
四、新建Elasticsearch项目,文档索引的增删改查
五、模仿京东搜索、爬虫
六、高亮和VUE
Gitee仓库:狂神-Elasticsearch: 跟随狂神视频教程,完成的练习项目 - Gitee.com
课程地址:1、ElasticSearch课程简介_哔哩哔哩_bilibili
安装资料合集地址:百度网盘 请输入提取码
提取码:qk8p
JDK版本最低要8
下载地址:下载 Elastic 产品 | Elastic
点击第一个文件夹,继续点击进入bin目录
点击Elasticsearch.bat启动脚本,运行30秒后出现started代表成功
访问localhost:9200,可以看到版本信息,代表安装成功
因为我本地没有npm环境,所以采用了谷歌商店的安装方式
访问谷歌商店,魔法道具请自行寻找
搜索得到Elasticsearch-head
下载安装,并启动插件
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
找到bin目录下面的plugins文件夹
将之前解压出来的elasticsearch-analysis-ik-7.6.1文件夹复制过来即可
下载地址:下载 Elastic 产品 | Elastic
点击kibana文件夹,继续点击进入bin目录
点击kibana.bat启动脚本,运行30秒后出现running at localhost:5601代表成功
访问 localhost:5601
使用kibana进行测试,点击Dev Tools进入控制台
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 等其他语言的版本。
参考博客: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"));
}
}
参考博客:Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理_njpjsoftdev的博客-CSDN博客
参考教程:1.光速入门ES底层内核Lucene_哔哩哔哩_bilibili
倒排索引又叫反向索引(右下图)以字或词为文档中出现的位置情况。
在实际的运用中,我们可以对数据库中原始的数据结构(左图),在业务空闲时事先根据左图内容,创建新的倒排索引结构的数据区域(右图)。
用户有查询需求时,先访问倒排索引数据区域(右图),得出文档id后,通过文档id即可快速,准确的通过左图找到具体的文档内容。
这一过程,可以通过我们自己写程序来实现,也可以借用已经抽象出来的通用开源技术来实现。
lucene的文件结构
lucene的数据类型
多年前,一个叫做Shay Banon的刚结婚不久的失业开发者,由于妻子要去伦敦学习厨师,他便跟着也去了。在他找工作的过程中,为了给妻子构建一个食谱的搜索引擎,他开始构建一个早期版本的Lucene。
直接基于Lucene工作会比较困难,所以Shay开始抽象Lucene代码以便Java程序员可以在应用中添加搜索功能。他发布了他的第一个开源项目,叫做“Compass”。
后来Shay找到一份工作,这份工作处在高性能和内存数据网格的分布式环境中,因此高性能的、实时的、分布式的搜索引擎也是理所当然需要的。然后他决定重写Compass库使其成为一个独立的服务叫做Elasticsearch。
第一个公开版本出现在2010年2月,在那之后Elasticsearch已经成为Github上最受欢迎的项目之一,代码贡献者超过300人。一家主营Elasticsearch的公司就此成立,他们一边提供商业支持一边开发新功能,不过Elasticsearch将永远开源且对所有人可用。
Shay的妻子依旧等待着她的食谱搜索…..
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热门
的一一个使用场景)
参考博客:聊聊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的 | 没有 | 没有 |
一致性概念 | 最终的一致性 | 最终的一致性 |
外键 | 没有 | 没有 |
当单纯的对已有数据进行搜索时,Solr更快
当实时建立索引时,Solr会产生io阻塞,查询性能较差,ElasticSearch具有明显的优势
随着数据量的增加,Solr的搜索效率会变得更低,而ElasticSearch却没有明显的变化
转变我们的搜索基础设施后从Solr ElasticSearch,我们看见一个即时~ 50x提高搜索性能!
总结
1、es基本是开箱即用(解压就可以用!) ,非常简单。Solr安装略微复杂一丢丢!
2、Solr 利用Zookeeper进行分布式管理,而Elasticsearch自身带有分布式协调管理功能。
3、Solr 支持更多格式的数据,比如JSON、XML、 CSV ,而Elasticsearch仅支持json文件格式。
4、Solr 官方提供的功能更多,而Elasticsearch本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑
5、Solr 查询快,但更新索引时慢(即插入删除慢) ,用于电商等查询多的应用;
6、Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而Elasticsearch相对开发维护者较少,更新太快,学习使用成本较高。
ELK是Elasticsearch、Logstash、 Kibana三大开源框架首字母大写简称。市面上也被成为Elastic Stack。
收集清洗数据(Logstash) ==> 搜索、存储(ElasticSearch) ==> 展示(Kibana)
市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称 ,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。
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 | 查询所有数据 |
暂时没写
新建SpringBoot项目,勾选Elasticsearch依赖
项目结构
导入依赖,本人用的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();
}
}
新增
// 测试文档的创建
@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();
}
复杂查询、高亮
// 查询
// 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
*/
}
打开config文件夹,找到jvm.options文件
在文件里加上 -Dfile.encoding=GBK
追加导入依赖
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
打开第一节下载的文件,将其中的前端素材复制过来
编写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";
}
}
启动项目测试
搜索京东搜索页面,并分析页面
https://search.jd.com/search?keyword=java
F12打开审查元素,找到目标元素img、name、price
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);
}
}
}
运行结果
图片没有,一般图片特别多的网站,所有的图片都是通过延迟加载的
编写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
编写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
调用search方法
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;
}
从第一节下载的课件里找到js文件
复制到项目里
引入js
修改后的静态页面总览
狂神说Java-ES仿京东实战
启动项目测试