实战
项目搭建
controller层
/**
* @author 王庆华
* @version 1.0
* @date 2020/12/5 13:03
* @Description TODO
* @pojectname wang-es-jd
*/
@Controller
public class IndexController {
@GetMapping({"/","/index"})
public String index(){
return "index";
}
}
server.port=9090
#关闭thymeleaf缓存
spring.thymeleaf.cache=false
还有我们的自定义版本,要与本地ES版本一致
其他的css,index页面又狂神说UP提供,有兴趣的可以去搜一下
启动项目
数据获取
数据库获取,消息队列中获取,都可以成为数据源,我们在这里用了一个简单的爬虫,不违法的哦
爬取数据
获取请求返回的页面信息,筛选出我们想要的数据====>Jsoup包
<!-- 解析网页-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
爬取数据代码
public class HtmlParseUtil {
public static void main(String[] args) throws IOException {
new HtmlParseUtil().parseHtml("Java").forEach(System.out::println);
}
public List<Content> parseHtml(String keywords) throws IOException {
//获取请求 https://search.jd.com/Search?keyword=java
//要联网,而且不能获取到ajax数据
String url ="https://search.jd.com/Search?keyword="+keywords;
//解析网页 返回的document就是浏览器的document对象
Document document = Jsoup.parse(new URL(url), 3000);
//所有js的方法都可以使用
//获取商品列表外层的大的div J_goodsList
Element element = document.getElementById("J_goodsList");
//System.out.println(element.html());
//获取全部的li元素
Elements elements = element.getElementsByTag("li");
ArrayList<Content> contents = new ArrayList<>();
//获取元素中的内容,el就是每一个li标签
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);
contents.add(content);
}
return contents;
}
}
运行会发现我们的img地址获取不到,因为图片特别多 的网站,他们所有的图片都是延迟加载的方式data-lazy-img
根据我们的工具类,写出我们自己的实体类封装
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {
private String title;
private String img;
private String price;
//可以自己添加属性
}
下面我们开始准备我们的业务实现
首先是我们的配置类,配置我们的ES
/**
* @author 王庆华
* @version 1.0
* @date 2020/12/4 19:48
* @Description TODO
* @pojectname es
*/
@Configuration
public class ElasticSearchClientConfig {
@Bean
RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1",9200,"http")));
return client;
}
}
接下来是service层,实现我们的业务层
//业务编写
@Service
public class ContentService {
@Autowired
RestHighLevelClient restHighLevelClient;
//解析数据,放入es索引
public Boolean parseContent(String keywords) throws IOException {
List<Content> contents = new HtmlParseUtil().parseHtml(keywords);
//批量插入到es中
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("2m");
for (int i = 0; i < contents.size(); i++) {
System.out.println(JSON.toJSONString(contents.get(i)));
bulkRequest.add(
new IndexRequest("jd_goods")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures();
}
//获取这些数据
//实现搜索功能
public List<Map<String,Object>> searchPage(String keyword,int pageNo,int pageSize) throws IOException {
if (pageNo<1){
pageNo = 1;
}
//条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
//构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//实现分页功能
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
//精确匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title",keyword);
searchSourceBuilder.query(termQueryBuilder);
//60s加载时间
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 documentFields : searchResponse.getHits().getHits()) {
list.add(documentFields.getSourceAsMap());
}
return list;
}
}
这里有两个功能,一个是一个是向 ES中增加数据,一个是ES中取出数据对应的他的controller也会有两个get请求
编写我们的ContentController
//请求编写
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
@GetMapping("/parse/{keyword}")
public Boolean parse(@PathVariable("keyword") String keyword) throws IOException {
return contentService.parseContent(keyword);
}
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String ,Object>> search(@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize) throws IOException {
return contentService.searchPage(keyword, pageNo, pageSize);
}
}
我在前面测试的时候已经向ES数据库插入了一些关于java的数据,vue的数据,还有医学的数据
成功拿到我们的数据
如果我们查询的有中文的话,我们是要去规范编码的,否则是查不出来的
绑定前端
<script th:src="@{/js/axios.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
<script>
new Vue({
el:'#app',
data:{
keyword:'' ,//搜索的关键字
results:[] //搜索的结果
},
methods:{
searchKey(){
var keyword = this.keyword;
console.log(keyword);
//对接后端接口
axios.get('search/'+keyword+"/1/10").then(response=>{
console.log(response);
//绑定数据
this.results = response.data;
})
}
}
})
在我们相应的地方 搜索框和按钮绑定数据和事件
</script>
搜索高亮功能
//业务编写
@Service
public class ContentService {
@Autowired
RestHighLevelClient restHighLevelClient;
//解析数据,放入es索引
public Boolean parseContent(String keywords) throws IOException {
List<Content> contents = new HtmlParseUtil().parseHtml(keywords);
//批量插入到es中
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("2m");
for (int i = 0; i < contents.size(); i++) {
System.out.println(JSON.toJSONString(contents.get(i)));
bulkRequest.add(
new IndexRequest("jd_goods")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures();
}
//获取这些数据
//实现搜索功能
public List<Map<String,Object>> searchPage(String keyword,int pageNo,int pageSize) throws IOException {
if (pageNo<1){
pageNo = 1;
}
//条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
//构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//实现分页功能
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
//精确匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title",keyword);
searchSourceBuilder.query(termQueryBuilder);
//60s加载时间
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
//这里可以配置多个字段,信息等等
//关闭多个高亮
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
//执行搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//解析结果
ArrayList<Map<String,Object>> list = new ArrayList<>();
for (SearchHit documentFields : searchResponse.getHits().getHits()) {
//解析我们的高亮字段
Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
HighlightField title = highlightFields.get("title");
//原来的结果
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
if (documentFields != null)
{
Text[] fragments = title.fragments();
String n_title = "";
//将高亮替换原来的字段
for (Text text : fragments) {
n_title += text;
}
sourceAsMap.put("title",n_title);
}
list.add(sourceAsMap);
}
return list;
}
}
区别就是我们现实解析了需要高亮的地方,然后是有高亮格式包裹,最后一点改变是在我们封装结果回去的时候,再次解析高亮字段,但是我们跑起来发现一个问题
我们还要解析这个标签,vue就能帮助我们了
<p class="productTitle">
<a v-html="result.title"> </a>
</p>
完成收工
不过对于中文的爬取可以成功,但是搜索的时候会被编码所打乱,搜索不到,我们就得对关键字进行编码处理,后续我们研究一下,有了解的人看到了可以交流一下