Elastic stack 技术栈学习(十一)—— 京东项目实战

目录

零、项目思路

一、项目搭建

1.1 新建Module

1.2 添加依赖、添加静态资源等准备工作

 二、数据获取—— 爬虫

2.1进入京东官网,搜索java

2.2 导入专门解析页面的jsoup依赖

2.3 编写工具类

 2.4 封装

2.4.1 新增一个pojo类:商品类

2.4.2  封装工具类

三、后端业务逻辑

3.1 目录结构

3.2 捋清业务逻辑

3.3 业务逻辑编写

3.3.1 新建索引、解析网页、数据放入es

 3.3.2 搜索ES,返回内容在前端展示

四、绑定前端及前后端交互

4.1 导入vue

4.2 使用vue渲染页面(改写index.html)

4.3 测试前后端交互


零、项目思路

后端处理

        1. 数据来源:在es中新建索引jd_goods

        2. 数据来源:提供一个爬取页面中商品信息(商品名称、价格、图片)的业务,对外提供接口,接收要搜索的关键字。

        3. 数据来源:将爬取到的信息作为文档插入到es索引jd_goods中

        4. 提供一个在es中进行搜索的业务,对外提供接口,接收要搜索的关键字。

前端处理

        1. 提供了一个静态页面

 绑定前后端

        1.使用vue

一、项目搭建

1.1 新建Module

        在上一篇博客使用的empty project里新建一个module。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第1张图片

        选spring框架。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第2张图片

        命名。(我需要改java版本为8) 

 Elastic stack 技术栈学习(十一)—— 京东项目实战_第3张图片

         选依赖。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第4张图片

 Elastic stack 技术栈学习(十一)—— 京东项目实战_第5张图片

 Elastic stack 技术栈学习(十一)—— 京东项目实战_第6张图片

 Elastic stack 技术栈学习(十一)—— 京东项目实战_第7张图片

         finish。

        删一下用不到的文件。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第8张图片

1.2 添加依赖、添加静态资源等准备工作

        仿照上次测试api使用的spring-es-api模块,把改动的地方直接搬过来。

Elastic stack技术栈学习(九)—— SpringBoot集成ES_玛丽莲茼蒿的博客-CSDN博客_springboot 整合es网上的教程不一定适用,别人能做出来到你这里可能就不行了,所以“最牛”的办法就是去找官方文档。网址如下:Elastic Stack and Product Documentation | Elastic在手册中找到ElasticSearch Client(ES 客户端)进入后,可以看到官方提供了各种语言的API,其中两个我们可以用到的是Java原生态API和Java REST Client API,虽然后者已经有些过时了,但是教程中使用的REST,因为我很少用Java语言,所以还是选择跟着教程https://blog.csdn.net/qq_44886213/article/details/122957956?spm=1001.2014.3001.5502Elastic stack技术栈学习(十)— springboot集成ES API详解_玛丽莲茼蒿的博客-CSDN博客在test里测试一下各个API。打开es,也运行es-head,方便观察。一、关于索引的API详解这里的client对ES发出请求,就相当于我们的kibana。1.1 声明客户端@SpringBootTestclass SpringEsApiApplicationTests {@Autowired@Qualifier("restHighLevelClient")private RestHighLevelClient client; //加上@@Qualifier,就可以https://blog.csdn.net/qq_44886213/article/details/123425024?spm=1001.2014.3001.5502(1)加上fastjson依赖

PS:写给我自己看的

这次没用给出仓库地址,就找到了fastjson。说明上次爆红没找到可能是没点击reload那个按钮的原因吧。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第9张图片

 (2)关闭 thymeleaf的缓存

Elastic stack 技术栈学习(十一)—— 京东项目实战_第10张图片

 (3)导入静态资源

链接:https://pan.baidu.com/s/1M5uWdYsCZyzIAOcgcRkA_A
提取码:qk8p
复制这段内容后打开百度网盘手机App,操作更方便哦

        下载,然后解压这个“搜索页面.rar”

Elastic stack 技术栈学习(十一)—— 京东项目实战_第11张图片

Elastic stack 技术栈学习(十一)—— 京东项目实战_第12张图片

        把解压后的两个文件夹拷贝到项目资源里

 Elastic stack 技术栈学习(十一)—— 京东项目实战_第13张图片

 (4)静态页面的访问测试

        看一下能不能访问到这个页面。

        如下图位置新建controller包,新增一个IndexController类。然后运行主程序,可以在命令行看到内置的Tomcat启动了。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第14张图片

         来localhost:8080看一下,出现如下页面即为测试成功。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第15张图片

 二、数据获取—— 爬虫

        在真实项目中数据获取有如下几种方式

  • 从数据库导入
  • 从消息队列中获取(比如Deepstream)
  • 比较少用的爬虫

2.1进入京东官网,搜索java

        下图就是我们要爬取信息的页面

Elastic stack 技术栈学习(十一)—— 京东项目实战_第16张图片

        观察并获得其url

2.2 导入专门解析页面的jsoup依赖

    
            org.jsoup
            jsoup
            1.10.2
    

如果爆红就点一下Reload  All Maven Projects。

2.3 编写工具类

        新增utils包,新建HtmlParseUtil类。

Elastic stack 技术栈学习(十一)—— 京东项目实战_第17张图片

step01:使用jsoup解析网页,返回Document对象,就是浏览器document对象,就是一个JavaScript文件。所有在js中能用的方法这里都能用。

//被爬取页面的url https://search.jd.com/Search?keyword=Java
String url = "https://search.jd.com/Search?keyword=Java";
// jsoup解析网页,返回浏览器document对象,所有在js可以使用的方法这里都能用!
Document document = Jsoup.parse(new URL(url), 30000);  //url 最长解析时间
       

step02:下图中id为J_goodsList的标签下就是我们要爬取的数据。一个

  • 标签存放一个商品。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第18张图片

     // 获取J_goodsList区域
    Element element = document.getElementById("J_goodsList");
    // 获取J_goodsList下的所有的li元素
    Elements elements = element.getElementsByTag("li");

     step03:再用一个for循环展开每个商品的

  • 标签,获取商品的图片、名称、价格等信息。

    • 获取商品图片路径:第一个标签的 src属性
    • 获取商品价格:
      下的所有文字
    • 获取商品名称: 
      下的所有文字

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第19张图片

     到此为止,完整代码如下。

    package com.example.springesjd.utils;
    
    
    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.MalformedURLException;
    import java.net.URL;
    
    public class HtmlParseUtil {
        public static void main(String[] args) throws IOException {
            //被爬取页面的url https://search.jd.com/Search?keyword=Java
            String url = "https://search.jd.com/Search?keyword=Java";
            // jsoup解析网页,返回浏览器document对象,所有在js可以使用的方法这里都能用!
            Document document = Jsoup.parse(new URL(url), 30000);  //url 最长解析时间
            Element element = document.getElementById("J_goodsList");
            // 获取J_goodsList下的所有的li元素
            Elements elements = element.getElementsByTag("li");
    
            for (Element el : elements) {
                //              找到标签; 找到
  • 标签下的第1个标签; 获取其src属性 String img = el.getElementsByTag("img").eq(0).attr("src"); // 找到"p-price"类;找到
  • 标签下的第1个"p-price"; 将其内容转为文字 String price = el.getElementsByClass("p-price").eq(0).text(); // 同上 String name = el.getElementsByClass("p-name").eq(0).text(); System.out.println(img); System.out.println(price); System.out.println(name); } } }
  • 用3行print语句打印我们爬到的图片地址、价格、商品名称。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第20张图片

     问题:成功获取到了价格和商品名称,没有获取到图片。

    分析及解决:一个网页中文字是加载最快的,图片比较慢,网站为了避免用户等待图片的加载,出现了“懒加载”这一技术。先加载文字,然后图片的位置用空白(或者文字等其他容易加载的媒体资源)去顶替,先给用户一个“整个页面都加载出来”的感觉,然后再慢慢加载图片。这种“顶替”是稍瞬即逝的,但是也能被肉眼察觉。

            所以我们上面获取到的起始是“懒加载”中用来顶替的图片的东西。

            那么真实的图片如何获取呢?

    在网页的源码中,我们看到图片的路径确实在src属性下面,那么为什么用

    String img = el.getElementsByTag("img").eq(0).attr("src");

    会获取不到呢?我们用以下语句去打印

            Element element = document.getElementById("J_goodsList");
            System.out.println(element.html());

    发现标签内并没有src属性,而是被“data-lazy-img”给替换了。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第21张图片

     所以这行代码要改成:

    String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
    

    再运行就可以获取图片了。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第22张图片

    PS: html的升级可能会导致这个参数发生变化,之前是“source-data-lazy-img”,现在这个参数被改成了“data-lazy-img”。所以我们还是得掌握打印html查看源码这种分析方法才能以不变应万变。

     2.4 封装

    2.4.1 新增一个pojo类:商品类

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第23张图片

    package com.example.springesjd.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.stereotype.Component;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Component
    public class Good {
        private String name;
        private String price;
        private String img;
    }
    

    2.4.2  封装工具类

    package com.example.springesjd.utils;
    
    
    import com.example.springesjd.pojo.Good;
    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.MalformedURLException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;
    
    public class HtmlParseUtil {
        public static void main(String[] args) throws IOException {
           new HtmlParseUtil().parseJD("方便面").forEach(System.out::println);
        }
        public List parseJD(String keywords) throws IOException {
            String url = "https://search.jd.com/Search?keyword="+keywords;
            // jsoup解析网页,返回浏览器document对象
            Document document = Jsoup.parse(new URL(url), 30000);  //url 最长解析时间
            Element element = document.getElementById("J_goodsList");
            Elements elements = element.getElementsByTag("li");
    
            ArrayList goodsList = new ArrayList<>();
    
            for (Element el : elements) {
                //              找到标签; 找到
  • 标签下的第1个标签; 获取其src属性 String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img"); // 找到"p-price"类;找到
  • 标签下的第1个"p-price"; 将其内容转为文字 String price = el.getElementsByClass("p-price").eq(0).text(); // 同上 String name = el.getElementsByClass("p-name").eq(0).text(); Good good = new Good(); good.setImg(img); good.setName(name); good.setPrice(price); goodsList.add(good); // System.out.println(img); // System.out.println(price); // System.out.println(name); } return goodsList; } }
  • 运行。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第24张图片

    三、后端业务逻辑

    3.1 目录结构

    (1)将解析京东这个工具类,加@Controller,注入spring中。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第25张图片

     (2)新增一个控制类和一个业务类

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第26张图片

     (3)新增config包(之前在测试模块配过了,直接粘过来)

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第27张图片

    package com.example.springesjd.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration  //相当于这个client的xml配置文件-bean
    public class ElasticSearchClientConfig {
    
        @Bean  // bean id= class=
        public RestHighLevelClient restHighLevelClient (){
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1", 9200, "http")));
            return client;
        }
    
    }
    

    3.2 捋清业务逻辑

    • 新建索引 jd_goods
    • 解析网页,得到的数据放入es
    • 搜索es,将搜索内容传给前端展示

    3.3 业务逻辑编写

    3.3.1 新建索引、解析网页、数据放入es

            在ContentService中Elastic stack 技术栈学习(十一)—— 京东项目实战_第28张图片写一个业务,实现新建索引、解析网页、数据放入es的功能。

    @Service
    public class ContentService {
    
        @Autowired
        @Qualifier("restHighLevelClient")
        private RestHighLevelClient client;  //加上@@Qualifier,就可以用client去替换restHighLevelClient
        // client就相当于kibana
    
        public Boolean parseContent(String keywords) throws IOException {
            /*------------------1.新建索引jd_goods--------------*/
            GetIndexRequest request = new GetIndexRequest("jd_goods");
            boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);
            if(!exists){  //如果不存在,就新建索引库
                CreateIndexRequest createIndexRequest = new CreateIndexRequest("jd_goods");
                CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
                System.out.println("创建索引:"+response);
            }
            
            /*------------------ 2.解析网页--------------------*/
            List goodList = new HtmlParseUtil().parseJD(keywords);
    
            /*------3.将爬取的数据添加到es中(批量添加文档操作-------*/
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.timeout("2m");
    
            for(int i =0; i

    编写controllerElastic stack 技术栈学习(十一)—— 京东项目实战_第29张图片,测试上面写的业务。

    package com.example.springesjd.controller;
    
    import com.example.springesjd.service.ContentService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    
    @RestController
    public class ContentController {
    
        @Autowired
        ContentService contentService;
    
        @GetMapping("/parse/{keywords}")  // http://localhost/parse/kerwords
        public boolean parse(@PathVariable("keywords") String keywords) throws IOException {
            return contentService.parseContent(keywords);
        }
    
    }
    

     启动之前有一些配置需要调整一下,如下图,有的配置带着红色叉号。

     Elastic stack 技术栈学习(十一)—— 京东项目实战_第30张图片

    把带叉号的配置给移除。 

     Elastic stack 技术栈学习(十一)—— 京东项目实战_第31张图片

     别忘了Apply,然后OK。

    运行整个程序,看到springboot处于挂载状态,然后访问http://localhost:8080/parse/方便面

    京东一个页面(30个商品)的信息就被添加到es的jd_goods索引当中去了。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第32张图片

    去head中看一下。 

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第33张图片

     3.3.2 搜索ES,返回内容在前端展示

    增加了这个业务后,我们的业务就写完了。直接给出结合了前面的完整业务代码:

    package com.example.springesjd.service;
    
    import com.alibaba.fastjson.JSON;
    import com.example.springesjd.pojo.Good;
    import com.example.springesjd.utils.HtmlParseUtil;
    import org.elasticsearch.action.bulk.BulkRequest;
    import org.elasticsearch.action.bulk.BulkResponse;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.client.ml.EvaluateDataFrameRequest;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.core.TimeValue;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.index.query.TermQueryBuilder;
    import org.elasticsearch.index.seqno.RetentionLeaseActions;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class ContentService {
    
        @Autowired
        @Qualifier("restHighLevelClient")
        private RestHighLevelClient client;  //加上@@Qualifier,就可以用client去替换restHighLevelClient
        // client就相当于kibana
    
        public Boolean parseContent(String keywords) throws IOException {
            /*------------------1.新建索引jd_goods--------------*/
            GetIndexRequest request = new GetIndexRequest("jd_goods");
            boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);
            if(!exists){  //如果不存在,就新建索引库
                CreateIndexRequest createIndexRequest = new CreateIndexRequest("jd_goods");
                CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
                System.out.println("创建索引:"+response);
            }
    
            /*------------------ 2.解析网页--------------------*/
            List goodList = new HtmlParseUtil().parseJD(keywords);
    
            /*------3.将爬取的数据添加到es中(批量添加文档操作-------*/
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.timeout("2m");
    
            for(int i =0; i> searchContent(String keywords, int pageOn, int pageSize) throws IOException {
            if(pageOn<=1){
                pageOn=1;
            }
            /*-----------------文档搜索(三步走)--------------------*/
            //1.创建搜索请求对象,构建对象
            SearchRequest searchRequest = new SearchRequest("jd_goods");
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            
            //2.封装这两个对象
            searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
            searchSourceBuilder.from(pageOn);  //分页
            searchSourceBuilder.size(pageSize);
         
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keywords);
            searchSourceBuilder.query(termQueryBuilder);
            searchRequest.source(searchSourceBuilder);
    
            //3.client发起请求    
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    
            //解析结果
            ArrayList> list = new ArrayList<>();  //为了返回new的对象
            for (SearchHit documentFields : searchResponse.getHits().getHits()) {
                list.add(documentFields.getSourceAsMap());
            }
            return list;
    
        }
    }
    

            完整Controller代码 

    package com.example.springesjd.controller;
    
    import com.example.springesjd.service.ContentService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    
    @RestController
    public class ContentController {
    
        @Autowired
        ContentService contentService;
    
        @GetMapping("/parse/{keywords}")  // http://localhost/parse/kerwords
        public boolean parse(@PathVariable("keywords") String keywords) throws IOException {
            return contentService.parseContent(keywords);
        }
    
        @GetMapping("/search/{keywords}/{pageOn}/{pageSize}")
        public List> search(@PathVariable("keywords") String keywords,
                                               @PathVariable("pageOn") int pageOn,
                                               @PathVariable("pageSize") int pageSize) throws IOException {
            return contentService.searchContent(keywords,pageOn,pageSize);
        }
    
    }
    
    

    再次运行测试http://localhost/search/关键字/页面起始/页面大小

    问题:搜索结果是空的。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第34张图片

             我又往es里加入了java的搜索结果,然后测试http://localhost/search/java/页面起始/页面大小,能成功返回!

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第35张图片

    看样子问题在于“方便面”的中文解码。 暂时没有去解决这个问题。

    后台完毕。

    四、绑定前端及前后端交互

    我们有了后台业务的接口(左图),也有了前端页面(右图)

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第36张图片      Elastic stack 技术栈学习(十一)—— 京东项目实战_第37张图片

     剩下要做的就是绑定前端,用到Vue.。

    4.1 导入vue

    导入vue和axios的.min.js文件,到resources/static/js文件夹下。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第38张图片

     Elastic stack 技术栈学习(十一)—— 京东项目实战_第39张图片

    问题:新版本的vue没有vue.min.js文件。

    解决

    链接:https://pan.baidu.com/s/1M5uWdYsCZyzIAOcgcRkA_A
    提取码:qk8p
    复制这段内容后打开百度网盘手机App,操作更方便哦

    4.2 使用vue渲染页面(改写index.html)

            将原先的index.html静态页面改写为:

    
    
    
    
        
        狂神说Java-ES仿京东实战
        
    
    
    
    
    

    {{result.price}}

    {{result.name}}

    店铺: 爱买不买小店

    月成交999笔 评价 3

            通过以上代码,可以实现:前端在搜索框中输入关键字,点击“搜素”按钮后,controller调用

    @GetMapping("/search/{keywords}/{pageOn}/{pageSize}")

     这个业务,去es的索引中搜索,结果返回给前端显示。下面我们测试一下。

    4.3 测试前后端交互

            运行这个项目。先通过访问“http://localhost:8080/parse/java”,向jd_goods索引存入数据。然后通过“http://localhost:8080”访问index.html页面。在这个页面的搜索栏中输入“java”,点击搜索。

    Elastic stack 技术栈学习(十一)—— 京东项目实战_第40张图片

     测试成功!

    PS:项目放在GitHub

  • 你可能感兴趣的:(ELK,elasticsearch,java,大数据)