项目说明
本项目是一个基于ES基本知识和原生api使用构建的仿京东搜索开源项目,本人在B站上寻找学习elastsearch找到的相关教程视频,看完视频后自己敲出来的代码,也都一一实现了功能。
本人学习博客:https://bo-vane.github.io/
项目技术环境
- springboot+vue前后端分离
- 使用Jsoup爬取京东数据
- docker 安装elasticsearch
- docker安装kibana
- springboot集成es实现高亮搜索
项目成果展示
点击搜索:
爬虫(jsoup)
数据获取:数据库、消息队列、爬虫、…
①搜索京东搜索页面,并分析页面
http://search.jd.com/search?keyword=java
页面如下
审查页面元素
页面列表id:J_goodsList
②爬取数据(获取请求返回的页面信息,筛选出可用的)
创建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);
}
}
}
成功爬取数据,但是上面图片img是空的,为什么
原因是啥?
一般图片特别多的网站,所有的图片都是通过延迟加载的
// 打印标签内容
Elements lis = j_goodsList.getElementsByTag("li");
System.out.println(lis);
打印所有li标签,发现img标签中并没有属性src的设置,只是data-lazy-ing设置图片加载的地址
所以我们要获取的图片地址应该是data-lazy-img的地址
更改图片获取属性为 data-lazy-img
后成功获取img的地址
创建HtmlParseUtil、改写
- 更改图片获取属性为
data-lazy-img
- 与实体类结合,实体类如下
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content implements Serializable {
private String name;
private String img;
private String price;
}
- 封装为方法
public class HtmlParseUtil {
public static void main(String[] args) throws IOException {
System.out.println(parseJD("java"));
}
public static List<Content> parseJD(String keyword) throws IOException {
/// 使用前需要联网
// 请求url
String url = "http://search.jd.com/search?keyword=" + keyword;
// 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");
// System.out.println(lis);
// 4.获取li下的 img、price、name
// list存储所有li下的内容
List<Content> contents = new ArrayList<Content>();
for (Element li : lis) {
// 由于网站图片使用懒加载,将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(contents);
// 5.返回 list
return contents;
}
}
4、测试index中添加解析数据和搜索高亮
在3、的基础上添加内容
①ContentService //未完成搜索高亮
@Service
public class ContentServiceImpl implements ContentService {
private final HTMLParseUtil htmlParseUtil;
private final RestHighLevelClient client;
public ContentServiceImpl(HTMLParseUtil htmlParseUtil, RestHighLevelClient client) {
this.htmlParseUtil = htmlParseUtil;
this.client = client;
}
@Override
public boolean parseContent(String keyword) throws Exception {
return htmlParseUtil.putIntoIndex(keyword,"jd_goods");
}
//未完成搜索高亮
@Override
public List<Map<String, Object>> highlightSearch(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
return null;
}
}
获取ES中的数据实现搜索功能
@Override
public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException {
if (pageNo <= 1){
pageNo = 1;
}
//条件搜索
SearchRequest request = new SearchRequest("jd_goods");
// 创建搜索源建造者对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
// 条件采用:精确查询 通过keyword查字段name,注意这个QueryBuilders的类型,不能用spring的那个
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));// 60s
//执行搜索
request.source(searchSourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
ArrayList<Map<String,Object>> list = new ArrayList<>();
//解析结果,都在hits里
for (SearchHit hit : response.getHits().getHits()) {
list.add(hit.getSourceAsMap());
}
return list;
}
在controller写个接口测试:
②ContentController
@RestController
public class ContentController {
private final ContentService contentService;
public ContentController(ContentService contentService) {
this.contentService = contentService;
}
@GetMapping("/parse/{keyword}")
public boolean parseInto(@PathVariable String keyword) throws Exception {
return contentService.parseContent(keyword);
}
}
第二个搜索功能的controller方法测试:
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String,Object>> search(@PathVariable int pageNo, @PathVariable int pageSize, @PathVariable String keyword) throws IOException {
return contentService.searchPage(keyword, pageNo, pageSize);
}
测试搜索一到十条数据:
关于分词索引和查询
简单解释一下 text 、keyword、term、match text和keyword是数据类型,表示在创建的时候是否会分词建立索引。 term和match是指查询时是否启用分词查询。
例如:现在有一个数据 “my cat”是text类型。 那么,使用term查询“my cat”时,失败,因为“my cat”在被创建时分词器将其索引建立为“my”和“cat”。 使用term查询“my”或“cat”时,则会成功,因为索引又能够精确匹配的数据。 使用match查询“my cat”也能成功,因为match是模糊查询,查询语句“my cat”会被分词成为“my”和“cat”和“my cat”,只要有任意一个满足就会返回数据。
如果“my cat”是keyword,那么建立时,只会有一个索引“my cat” 所以此时,不管是term还是match都只有查询“my cat”时才会返回数据,其余的都查询不到。
③结果展示
5、前后端分离(简单使用Vue)
导入vue依赖:
现在js目录只有一个jQuery,需要导入vue和axios
导入完之后开始写axios
后面前端的东西很容易,就不写了