文章目录
版本匹配
我安装的elasticsearch是7.6.2的,所以我的springboot版本得使用2.3.x的
可视化工具ElasticHD
1、下载
方式一:公众号获取
关注I am Walker
,回复elasticHD
方式二:github下载
https://github.com/qax-os/ElasticHD/releases
我这里使用的是64位的windows,所以直接点击下载即可
2、解压并启动
解压后,将文件夹打开,里面又ElasticHD.exe
双击执行即可,执行会出现命令行
然后访问localhost:9800
,有时候0.0.0.0:9800
是不生效的
页面如下:
之后启动elasticsearch,并且连接自己的elasticsearch即可
springboot使用
前提要求
版本匹配
本案例中使用的es时7.6.2的,所以spring-boot的版本应为2.3.x
使用
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- lombok也引入,方便实体类getter和setter的使用 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
2、配置
方式一:application.yaml中配置
spring:
elasticsearch:
rest:
# es的访问路径
uris: localhost:9200
# 端口随意定义即可
server:
port: 8079
方式二:编写RestClientConfig配置(当配置比较复杂时,可采用该方法)
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration= ClientConfiguration.builder()
.connectedTo("localhost:9200") //连接自己的elasticsearch的url
.build();
return RestClients.create(clientConfiguration).rest();
}
}
具体的客户端配置,可以查看文档
3、实体类创建
例如,以userEntity作为案例对象
package com.walker.es.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
//文档
@Document(indexName = "user")
public class UserEntity {
//文档的id
@Id
private String id;
@Field(type = FieldType.Text)
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Text)
private String hobby;
}
在实体类中,可以使用的注解主要如下:
@Document:文档,用于类
- indexName代表索引的名称定义好之后,可以根据operations方法创建对应的索引,indexName也可以使用表达式,例如:“log-#{T(java.time.LocalDate).now().toString()}”
- createIndex 默认为true,代表着当查询数据的时候,如果所以不存在,则会自动进行创建
**@Id: **文档id
@Field: 属性,一般用于对象属性上
- name :属性名称,这可以用于重命名,如果没有设置的话,属性名和类的属性名称一致
- type:属性类型,类型主要有以下:Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type,常用的已经加粗,基本跟java常用的类型差不多,其他的大家可以自己进行研究
- pattern:例如时间格式,@Field(type=FieldType.Date, pattern=“dd.MM.uuuu”)
- store:标记原始字段是否存储在es中
其他注解:
@Transient: 不会被写入映射到es文档中
@ReadOnlyProperty:只读属性,无法修改值,但是会从es中返回对应的数据回来
@WriteOnlyProperty:只写属性,数据存在于es中,可以进行修改,但是不会从es中返回来
@GeoPoint:地理位置
4、操作实体 Elasticsearch Operations
主要分为以下几种,分别是
- IndexOperations :一般用来操作索引
- DocumentOperations :操作文档
- SearchOperations :操作查询
- ElasticsearchOperations : 包含DocumentOperations 和SearchOperations ,关系如下:
一般来说使用ElasticSearchOperations比较多,下面以ElasticSearchOperations为案例进行使用
5、ElasticSearchOperations 基础操作
在DocumentOperations中主要有以下的方法,主要针对于document进行操作,也就是对文档的增删改查
而对于SearchOperations的方法主要如下:
在编写测试代码前,先引入ElasticsearchOperations
@Autowired
private ElasticsearchOperations elasticsearchOperations;
新增数据 save
@Test
void save() {
UserEntity userEntity = new UserEntity();
userEntity.setName("walker");
userEntity.setAge(18);
userEntity.setHobby("篮球");
userEntity.setCreateTime(new Date());
elasticsearchOperations.save(userEntity);
}
一般来说如果id不进行赋值的话,会自动生成id,但是类型为String
注意
- 当自己填写id,且id的值存在的时候,会直接覆盖数据,而不是生成新的数据,因为id是唯一的
批量添加 save
@Test
void saveBatch() {
ArrayList<UserEntity> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
UserEntity userEntity = new UserEntity();
userEntity.setName("测试"+new Random().nextInt(10000));
userEntity.setAge(new Random().nextInt(50));
userEntity.setHobby("爱好"+i);
userEntity.setCreateTime(new Date());
list.add(userEntity);
}
//批量添加
elasticsearchOperations.save(list);
}
根据id查询单个数据 get
<T> T get(String id, Class<T> clazz);
传入实体类和id即可查询,如果查询到则会直接返回结果,否则则会返回null
@Test
void get() {
//先新增一条数据
UserEntity userEntity = new UserEntity();
userEntity.setId("abcd1");
userEntity.setName("test");
userEntity.setAge(11);
userEntity.setHobby("羽毛球");
userEntity.setCreateTime(new Date());
elasticsearchOperations.save(userEntity);
//获取数据
System.out.println(elasticsearchOperations.get(userEntity.getId(), UserEntity.class));
}
根据id删除数据 delete
String delete(String id, Class<?> entityType);
- id
- entityType: 类
@Test
void delete() {
//删除数据
String res = elasticsearchOperations.delete("abcd1", UserEntity.class);
//返回的是id
System.out.println(res);
}
根据id查询是否存在 exists
boolean exists(String id, Class<?> clazz);
@Test
void exists(){
boolean res = elasticsearchOperations.exists("P9I8mIcBk_im7nqSwlT5", UserEntity.class);
System.out.println(res);
}
批量新增修改 bulkIndex
default List<String> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
}
@Test
void bulkIndex(){
ArrayList<IndexQuery> indexQueries = new ArrayList<>();
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId("aaa");
UserEntity u = new UserEntity();
u.setName("hello");
u.setHobby("pingpong");
indexQuery.setSource(JSON.toJSONString(u));
indexQueries.add(indexQuery);
IndexQuery iq2 = new IndexQuery();
iq2.setId("bbb");
UserEntity u2 = new UserEntity();
u2.setName("a2");
u2.setHobby("aaa");
iq2.setSource(JSON.toJSONString(u2));
indexQueries.add(iq2);
//批量新增/修改文档 如果id相同则进行覆盖
List<String> res = elasticsearchOperations.bulkIndex(indexQueries, IndexCoordinates.of("user"));
System.out.println(res);
}
查询 Query
查询结果类型分类:
- SearchHit 命中单个
- SearchHits 命中集合
- SearchPage 命中分页
- SearchScrollHits
- SearchHitsIterator
查询方式主要有三种
- CriteriaQuery 标准查询
- StringQuery 字符串查询
- NativeQuery 原生查询
CriteriaQuery
标准查询,可以先构建Criteria,然后放入CriteriaQuery。
@Test
void criteriaQuery(){
//is:精确匹配
Criteria c = new Criteria("name").is("walker");
CriteriaQuery criteriaQuery = new CriteriaQuery(c)
//分页
.setPageable(PageRequest.of(1,1));
SearchHits<UserEntity> search = elasticsearchOperations.search(criteriaQuery, UserEntity.class);
log.info(JSON.toJSONString(search));
}
返回结果如下:
{
"empty": false, // 是否为空
"maxScore": 1.9365597, //最大分数
"searchHits": [{ //命中数据
"content": {
"age": 18,
"createTime": 1681862400000,
"hobby": "篮球",
"id": "PNI4mIcBk_im7nqS7VSg",
"name": "walker"
},
"highlightFields": {},
"id": "PNI4mIcBk_im7nqS7VSg", //id
"score": 1.9365597, //分数
"sortValues": []
}],
"totalHits": 1, //总数
"totalHitsRelation": "EQUAL_TO"
}
这里就简单的教大家怎么使用,具体的可以自己尝试了
Criteria的主要方法有以下:
and 且,可以添加新的Criteria
or 或 有些类似于should
exists 存在
is 是
contains 包含
startsWith 以什么为开头
endsWith 以什么结尾
between 介于
expression
lessThanEqual 小于等于
not 非
fuzzy 模糊
greaterThan 大于
greaterThanEqual 大于等于
lessThan 小于
in 存在于
notIn 不存在于
...
StringQuery
该查询方法主要是使用JSON表达式去编写的,比较类似于我们直接使用ES api的感觉
@Test
void stringQuery(){
StringQuery query = new StringQuery("{ \"match\": { \"name\": { \"query\": \"walker\" } } } ");
SearchHits<UserEntity> search = elasticsearchOperations.search(query, UserEntity.class);
log.info(JSON.toJSONString(search));
}
NativeQuery (包含高亮)
当CriteriaQuery没法支持的时候,可以使用该query,例如同时进行查询和聚合的时候,或者分页高亮等等,原生查询,可以进行的操作相对较多,案例如下:
@Test
void nativeQuery(){
NativeSearchQueryBuilder nb = new NativeSearchQueryBuilder();
PageRequest page = PageRequest.of(1, 1);
NativeSearchQuery build = nb.withQuery(QueryBuilders
.matchQuery("name", "walker"))
//排序
.withSort(SortBuilders.fieldSort("age").sortMode(SortMode.MAX))
//高亮
.withHighlightBuilder(new HighlightBuilder().field("name"))
//分页
.withPageable(page)
.build();
SearchHits<UserEntity> search = elasticsearchOperations.search(build, UserEntity.class);
log.info(JSON.toJSONString(search));
}
NativeSearchQueryBuilder中可以使用的方法
withQuery 查询
withFilter 过滤
withSort 排序
withScriptField 脚本属性
withCollapseField
addAggregation //聚合函数
withHighlightBuilder //高亮
withHighlightFields //高亮
withPageable //分页
withMinScore //最小分数
withIds //id
withSearchType //查询类型
在spring-boot-elasticsearch中,还提供了另外一种方式进行查询的,叫做
Elasticsearch Repositories
,有些类似于JPA的使用,只需要定义对应的接口方法,就可以使用了,而且在使用过程中有一定的规则,接下来便听我讲讲
6、Elasticsearch Repositories
在Repository中按照格式定义方法,就会自动转换成对应的表达式,极大地方便我们使用,例如模糊查找属性name的时候,可以使用findByName
,就会被映射为:
{
"query": {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
]
}
}
}
如果需要使用且,就使用And,或就是用Or,例如根据name和price同时查询,可以使用
findByNameAndPrice(String name, Integer price)
,会被映射为:
{
"query": {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}
}
关键词
关键词主要有如下这些:大家可以查看以下文档
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, | ||
{ “query_string” : { “query” : “?”, “fields” : [ “price” ] } } | ||
] | ||
} | ||
}} | ||
Or | findByNameOrPrice | { “query” : { |
“bool” : { | ||
“should” : [ | ||
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, | ||
{ “query_string” : { “query” : “?”, “fields” : [ “price” ] } } | ||
] | ||
} | ||
}} | ||
Is | findByName | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } } | ||
] | ||
} | ||
}} | ||
Not | findByNameNot | { “query” : { |
“bool” : { | ||
“must_not” : [ | ||
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } } | ||
] | ||
} | ||
}} | ||
Between | findByPriceBetween | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } | ||
] | ||
} | ||
}} | ||
LessThan | findByPriceLessThan | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } | ||
] | ||
} | ||
}} | ||
LessThanEqual | findByPriceLessThanEqual | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } | ||
] | ||
} | ||
}} | ||
GreaterThan | findByPriceGreaterThan | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } | ||
] | ||
} | ||
}} | ||
GreaterThanEqual | findByPriceGreaterThan | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } | ||
] | ||
} | ||
}} | ||
Before | findByPriceBefore | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } | ||
] | ||
} | ||
}} | ||
After | findByPriceAfter | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } | ||
] | ||
} | ||
}} | ||
Like | findByNameLike | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } | ||
] | ||
} | ||
}} | ||
StartingWith | findByNameStartingWith | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } | ||
] | ||
} | ||
}} | ||
EndingWith | findByNameEndingWith | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } | ||
] | ||
} | ||
}} | ||
Contains/Containing | findByNameContaining | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } | ||
] | ||
} | ||
}} | ||
In (when annotated as FieldType.Keyword) | findByNameIn(Collectionnames) | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“bool” : {“must” : [ | ||
{“terms” : {“name” : [“?”,“?”]}} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
}} | ||
In | findByNameIn(Collectionnames) | { “query”: {“bool”: {“must”: [{“query_string”:{“query”: “\”?\" \“?\”", “fields”: [“name”]}}]}}} |
NotIn (when annotated as FieldType.Keyword) | findByNameNotIn(Collectionnames) | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{“bool” : {“must_not” : [ | ||
{“terms” : {“name” : [“?”,“?”]}} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
}} | ||
NotIn | findByNameNotIn(Collectionnames) | {“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(\”?\" \“?\”)", “fields”: [“name”]}}]}}} |
True | findByAvailableTrue | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “true”, “fields” : [ “available” ] } } | ||
] | ||
} | ||
}} | ||
False | findByAvailableFalse | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “false”, “fields” : [ “available” ] } } | ||
] | ||
} | ||
}} | ||
OrderBy | findByAvailableTrueOrderByNameDesc | { “query” : { |
“bool” : { | ||
“must” : [ | ||
{ “query_string” : { “query” : “true”, “fields” : [ “available” ] } } | ||
] | ||
} | ||
}, “sort”:[{“name”:{“order”:“desc”}}] | ||
} | ||
Exists | findByNameExists | {“query”:{“bool”:{“must”:[{“exists”:{“field”:“name”}}]}}} |
IsNull | findByNameIsNull | {“query”:{“bool”:{“must_not”:[{“exists”:{“field”:“name”}}]}}} |
IsNotNull | findByNameIsNotNull | {“query”:{“bool”:{“must”:[{“exists”:{“field”:“name”}}]}}} |
IsEmpty | findByNameIsEmpty | {“query”:{“bool”:{“must”:[{“bool”:{“must”:[{“exists”:{“field”:“name”}}],“must_not”:[{“wildcard”:{“name”:{“wildcard”:“*”}}}]}}]}}} |
IsNotEmpty | findByNameIsNotEmpty | {“query”:{“bool”:{“must”:[{“wildcard”:{“name”:{“wildcard”:“*”}}}]}}} |
返回结果类型
- List
- Stream
- SearchHits
- List<SearchHit>
- Stream<SearchHit>
- SearchPage
注解
@Query 原生query查询
当然,也可以使用@Query进行json字符串原生查询的编写,在遇到不大好处理的情况时,这个时候名称就可以不需要按照findByXX的格式进行编写了
@Query(value = "{\"match\": {\"age\": {\"query\": \"?0\"}}}")
SearchHits<UserEntity> age(Integer age);
@Highlight 高亮
可以repository方法中可以使用@Highlight进行配置
例如下边的案例,是对hobby字段进行高亮处理
@Highlight(fields = {
@HighlightField(name = "hobby")
})
SearchHits<UserEntity> findByHobbyLike(String hobby);
使用案例
1、首先先定义实体类
这里在上边已经接收过了,就不详细介绍了
package com.walker.es.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
@Data
@Document(indexName = "user",createIndex = true)
public class UserEntity {
@Id
private String id;
@Field(type = FieldType.Text)
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Text)
private String hobby;
@Field(type = FieldType.Date,format = DateFormat.basic_date)
private Date createTime;
}
2、编写Repository接口类
package com.walker.es.repository;
import com.walker.es.model.UserEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Service;
@Service
public interface UserRepository extends ElasticsearchRepository<UserEntity, String> {
/**
* 查询单个:默认会获取第一个
*/
UserEntity findByName(String name);
/**
* 分页查询
*/
Page<UserEntity> findByNameLike(String name, Pageable pageable);
/**
* 使用@Query注解
*/
@Query(value = "{\"match\": {\"age\": {\"query\": \"?0\"}}}")
SearchHits<UserEntity> age(Integer age);
/**
* 高亮查询
*/
@Highlight(fields = {
@HighlightField(name = "hobby")
})
SearchHits<UserEntity> findByHobbyLike(String hobby);
}
3、测试使用
package com.walker.es.repository;
import com.alibaba.fastjson.JSON;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import com.walker.es.model.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import java.util.Iterator;
import java.util.List;
@Slf4j
@SpringBootTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void query(){
//查询单个
System.out.println(userRepository.findByName("walker"));
//模糊查询
System.out.println(userRepository.findByNameLike("walker"));
//分页
PageRequest of = PageRequest.of(1, 2);
Page<UserEntity> page = userRepository.findByNameLike("walker", of);
List<UserEntity> content = page.getContent();
System.out.println(content);
//高亮
System.out.println("高亮");
SearchHits<UserEntity> hits = userRepository.findByHobbyLike("篮球");
log.info(JSON.toJSONString(hits));
//原生查询
SearchHits<UserEntity> age = userRepository.age(18);
log.info(JSON.toJSONString(age));
}
}
具体实践
商品搜索【模仿京东】
参考:https://blog.csdn.net/qq_41432730/article/details/121723456
拉取数据
主要是从京东商品中爬取数据,使用的是jsoup进行拉取
爬取的网址如下:
[https://search.jd.com/Search?keyword=](https://search.jd.com/Search?keyword=)关键词
1、导入依赖
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
2、编写商品实体类和Jsoup工具类,从京东中拉取数据
- 实体类
package com.walker.es.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
/**
* 京东商品实体类
*/
@Data
//es 文档注解类
@Document(indexName = "jdgood")
public class JdGoodEntity {
@Id
private String id;
private String price;
private String title;
private String img;
}
- Jsoup工具类
package com.walker.es.utils;
import com.alibaba.fastjson.JSON;
import com.walker.es.model.JdGoodEntity;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class JsoupUtils {
//这里自己可以调用main方法测试一下,能否爬取到对应的数据
public static void main(String[] args) throws IOException {
List<JdGoodEntity> res = parseJD("java");
log.info(JSON.toJSONString(res));
}
//从京东中爬取商品数据
public static List<JdGoodEntity> parseJD(String keywords) throws IOException {
// 中文需要转义
Document document = parse("https://search.jd.com/Search?keyword=",keywords,30000);
//所有js中的操作都可以使用。
Element element = document.getElementById("J_goodsList");
//获取所有Li标签
Elements elements = element.getElementsByTag("li");
List<JdGoodEntity> goodsList = new ArrayList<>();
//获取元素中的内容 这个的el就是每一个li标签
for (Element el : elements) {
//关于获取不到图片,由于是懒加载的,所以获取src是获取不到的
String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
String price = el.getElementsByClass("p-price").eq(0).text();
String name = el.getElementsByClass("p-name").eq(0).text();
JdGoodEntity content = new JdGoodEntity();
content.setPrice(price);
content.setImg(img);
content.setTitle(name);
goodsList.add(content);
}
return goodsList;
}
/**
* 解析网站
*/
public static Document parse(String url,String keywords,int timeoutMillis) throws IOException {
url=url + keywords;
//解析网页(document就是浏览器页面对象)
Document document = Jsoup.parse(new URL(url), timeoutMillis);
return document;
}
}
3、controller编写
- controller
package com.walker.es.controller;
import com.walker.es.model.JdGoodEntity;
import com.walker.es.service.JdGoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
@RequestMapping("/jd")
@RestController
public class JDController {
@Autowired
private JdGoodService jdGoodService;
/**
* 爬取京东数据并添加到es中
*/
@GetMapping("/parseData/{keyword}")
public String parseData(@PathVariable(value = "keyword") String keyword) {
jdGoodService.parseData(keyword);
return "ok";
}
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public ArrayList<JdGoodEntity> search(@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize){
return jdGoodService.search(keyword,pageNo,pageSize);
}
}
- service
package com.walker.es.service;
import com.walker.es.model.JdGoodEntity;
import java.util.ArrayList;
public interface JdGoodService {
void parseData(String keyword);
ArrayList<JdGoodEntity> search(String keyword, int pageNo, int pageSize);
}
- service实现类
package com.walker.es.service.impl;
import com.walker.es.model.JdGoodEntity;
import com.walker.es.repository.JdGoodRepository;
import com.walker.es.service.JdGoodService;
import com.walker.es.utils.JsoupUtils;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
public class JdGoodServiceImpl implements JdGoodService {
@Autowired
private ElasticsearchOperations elasticsearchOperations;
@Autowired
private JdGoodRepository jdGoodRepository;
@Override
public void parseData(String keyword) {
try {
List<JdGoodEntity> res = JsoupUtils.parseJD("java");
elasticsearchOperations.save(res);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public ArrayList<JdGoodEntity> search(String keyword, int pageNo, int pageSize) {
//这里是可以使用多种方式进行查询的
//可以使用NativeSearchQuery
//也可以使用repository方式进行
NativeSearchQueryBuilder nb = new NativeSearchQueryBuilder();
PageRequest page = PageRequest.of(pageNo, pageSize);
NativeSearchQuery query = nb.withQuery(QueryBuilders.matchQuery("title", keyword))
.withHighlightBuilder(new HighlightBuilder().field("title"))
.withPageable(page)
.build();
SearchHits<JdGoodEntity> search = elasticsearchOperations.search(query, JdGoodEntity.class);
ArrayList<JdGoodEntity> entities = new ArrayList<>();
for (SearchHit<JdGoodEntity> hit : search) {
JdGoodEntity content = hit.getContent();
content.setTitle(hit.getHighlightField("title").get(0));
entities.add(content);
}
return entities;
}
}
如果是使用repository方式的话 repository如此编写
这里需要注意,返回结果使用SearchPage,而不是Page,如果是Page的话,没法获取到高亮的返回结果
@Highlight(fields = {
@HighlightField(name = "title")
})
SearchPage<JdGoodEntity> findByTitle(String title, Pageable pageable);
4、调用测试
- 爬取解析上传数据
返回数据为ok即可,之后再使用查询进行测试一下,看一下是否能够查询到数据
- 查询
返回结果如下:
结果已经对高亮的部分获取,并进行了替换
[
{
"id": "Q9hLn4cB2uWypaEqQ9Dr",
"price": "¥59.90",
"title": "【动力节点著作】漫画<em>Java</em>编程 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img14.360buyimg.com/n7/jfs/t1/169886/36/37126/66675/6440e096F40c55ebc/3341b7ca43cf1b10.jpg"
},
{
"id": "L9hLn4cB2uWypaEqQ9Dr",
"price": "¥35.60",
"title": "O'Reilly:Head First <em>Java</em>(中文版 第2版 涵盖Java5.0) 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img10.360buyimg.com/n7/jfs/t1/203043/24/32689/53051/6440da57Fec2ec08c/ad5ed54beaf84a09.jpg"
},
{
"id": "ONhLn4cB2uWypaEqQ9Dr",
"price": "¥64.50",
"title": "大话设计模式 <em>Java</em>溢彩加强版 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img12.360buyimg.com/n7/jfs/t1/170340/1/36868/66744/6440de7bFa9b0310e/c8f3eb616cba42d9.jpg"
},
{
"id": "RNhLn4cB2uWypaEqQ9Dr",
"price": "¥149.00",
"title": "<em>Java</em>核心技术 第11版 套装共2册 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img10.360buyimg.com/n7/jfs/t1/220363/2/28321/59463/6440d7cdF62467337/de186c21b70c8b33.jpg"
},
{
"id": "NNhLn4cB2uWypaEqQ9Dr",
"price": "¥100.00",
"title": "计算机二级C语言办公软件office硬件运维网络工程师<em>JAVA</em>程序员平面设计考试IT证书考证培训 Pro/E设计师",
"img": "//img12.360buyimg.com/n7/jfs/t1/58711/30/19751/395110/62a807a5E54f52128/9429184ff08da8a0.jpg"
},
{
"id": "PdhLn4cB2uWypaEqQ9Dr",
"price": "¥83.40",
"title": "<em>Java</em>语言程序设计 基础篇 原书第12版 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img12.360buyimg.com/n7/jfs/t1/158063/34/21117/158566/6080deecEe8a120f5/133314834a5093d1.jpg"
},
{
"id": "KdhLn4cB2uWypaEqQ9Dr",
"price": "¥74.50",
"title": "<em>Java</em>核心技术 卷I:开发基础(原书第12版) 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img10.360buyimg.com/n7/jfs/t1/36950/34/19440/55191/6440d87eF269de47e/5c7dbb3889501aa1.jpg"
},
{
"id": "MthLn4cB2uWypaEqQ9Dr",
"price": "¥74.50",
"title": "<em>Java</em>核心技术 卷II:高级特性 原书第12版 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img13.360buyimg.com/n7/jfs/t1/100230/24/32336/84778/641d0582Fe1b45780/f28cc0c3d824fc7b.jpg"
},
{
"id": "PNhLn4cB2uWypaEqQ9Dr",
"price": "¥3600.00",
"title": "佳沃(<em>JAVA</em>) 公路自行车鱼雷3代碟刹22变速弯把男女碳纤维前叉男女成人赛车 3代黑色DECA版 22速 50#建议173-178CM 700C",
"img": "//img12.360buyimg.com/n7/jfs/t1/198893/6/761/440240/6104f84dE8b85f555/0bb465d9a3b8295c.jpg"
},
{
"id": "KNhLn4cB2uWypaEqQ9Dr",
"price": "¥39.90",
"title": "<em>Java</em>从入门到精通(第6版)(软件开发视频大讲堂) 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
"img": "//img13.360buyimg.com/n7/jfs/t1/159462/12/37290/61622/6440dcdcFa88a8131/f99130288178cf39.jpg"
}
]
在我们的实际场景中,也是一样,一般是将跑个定时任务,将数据定时上传到es中,然后进行分页查询即可
问题
Method has to have one of the following return types!
原因:
参数使用了Pageable,但是返回结果没有使用Page
总结
对于springboot整合elasticsearch的使用,首先需要注意版本的匹配,然后阅读对应版本的文档去对接,在使用过程中,elasticsearch可以选择多种方式,大家可以选择自己觉得比较习惯便捷的方式去执行。
希望对你有所帮助,我是Walker