Jsoup爬虫——自学习梳理

------项目已完结(源码在文末)

一个较大的项目,通过后台进行网站爬虫,选择的是一个招聘类型的网站,爬取数据后会选择一部分放入到我们的数据库中,前台通过后台返回的Json数据进行展示;大概就是这样的一个项目。

首先来说说Json爬虫,然后一步步带大家进入项目。

1.先展示一下如何使用Jsoup爬取数据,展示在IDEA控制台

导入依赖 放入pom.xml

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.14.3</version>
</dependency>

使用Jsoup进行网页爬取时,你可以根据需要选择和提取特定的HTML元素、属性或文本内容。Jsoup提供了丰富的选择器和方法来操作和处理HTML。

java 复制代码
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;


public class JsoupExample {
    public static void main(String[] args) {
        String url = "https://sou.zhaopin.com/?jl=530&kw=java";
        try {
            Document doc0 = Jsoup.connect(url).get();   //请求方式获取到页面   Document元素  HTML文档
            String d = doc0.toString();
            Document doc = Jsoup.parse(d, "UTF-8"); //将页面转换为Document对象
            Elements content = doc.getElementsByClass("joblist-box__item");
            for(Element element:content){
                String price = element.getElementsByClass("jobinfo__salary").text();
                String company = element.getElementsByClass("companyinfo__name").text();
                System.out.println(price+" " + company);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

但是扒下来的数据是假数据,网站知道我们在爬虫,所以为组织我们,给我们假数据蒙混,怎么扒取真的?:模拟浏览器访问

JSOUP设置请求头

java 复制代码
public static final String url ="https://sou.zhaopin.com/?jl=530&kw=java";
public static void main(String[] args) throws IOException {
    Document scriptHtml = Jsoup.connect(url)
            .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
            .header("Accept-Encoding", "gzip, deflate, br, zstd")
            .header("Accept-Language", "zh-CN,zh;q=0.9")
            .header("Cache-Control","max-age=0")
            .header("Connection","keep-alive")
            .header("Upgrade-Insecure-Requests","1")
            .header("User-Agent","Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36")// "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
            .header("Cookie", "x-zp-client-id=ef9626f5-a52b-4a15-8a12-b0a85e7c218d; sts_deviceid=19035834b09cac-08a587ad82e264-26001f51-2073600-19035834b0a15e9; LastCity=%E5%8C%97%E4%BA%AC; LastCity%5Fid=530; FSSBBIl1UgzbN7NO=5dPSkp4s02hfTOHNsDqa0XyPFtDtb6nPFiDZH.or1HT9mqeNJTekRD2P9yNyU_R6irRJn347wEzUdpkKbtsY84a; _uab_collina=171888479751435972092526; selectCity_search=530; campusOperateJobUserInfo=c32dd24c-0bda-4124-9245-7bd215dc412c; sensorsdata2015jssdkchannel=%7B%22prop%22%3A%7B%22_sa_channel_landing_url%22%3A%22https%3A%2F%2Flanding.zhaopin.com%2Fregister%3Fidentity%3Dc%26channel_name%3Dbaidu_sem_track%26callback_id%3DIQJ0tYqm%26_data_version%3D0.6.1%26channel_utm_content%3Dpp%26project%3Dzlclient%26channel_utm_medium%3Dcpt%26tid%3DxTB%26channel_link_type%3Dweb%26channel_utm_source%3Dbaidupcpz_x%26hash_key%3DuaKIYNjV6PvgIWUUAJ9n%26sat_cf%3D2%26channel_utm_campaign%3Dzbt%26channel_utm_term%3D01%26_channel_track_key%3DM9kzlBZB%26link_version%3D1%26channel_keyword_id%3D%7Bkeywordid%7D%26channel_ad_id%3D%7Bcreative%7D%26channel_account_id%3D%7Buserid%7D%26channel_keyword%3D%7Bkw_enc_utf8%7D%26channel_adgroup_id%3D%7Bunitid%7D%26channel_campaign_id%3D%7Bplanid%7D%22%7D%7D; locationInfo_search={%22code%22:%22570%22%2C%22name%22:%22%E4%BF%9D%E5%AE%9A%22%2C%22message%22:%22%E5%8C%B9%E9%85%8D%E5%88%B0%E5%B8%82%E7%BA%A7%E7%BC%96%E7%A0%81%22}; Hm_lvt_7fa4effa4233f03d11c7e2c710749600=1719221305,1719382515,1719456967,1719570373; at=47d39fc439d34b6281b7bc14e69b6679; rt=c31cb4d7b5fa4ed59efea7b21489e342; acw_tc=276077d617196447797961224e0bea96b10da8c367bedcc0f99eb890b6caeb; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%221148276223%22%2C%22first_id%22%3A%22190356c8cbd25a-0e9855f7268eda8-26001f51-2073600-190356c8cbec48%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkwMzU2YzhjYmQyNWEtMGU5ODU1ZjcyNjhlZGE4LTI2MDAxZjUxLTIwNzM2MDAtMTkwMzU2YzhjYmVjNDgiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIxMTQ4Mjc2MjIzIn0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%221148276223%22%7D%2C%22%24device_id%22%3A%22190356c8cbd25a-0e9855f7268eda8-26001f51-2073600-190356c8cbec48%22%7D; Hm_lvt_21a348fada873bdc2f7f75015beeefeb=1719456972,1719464039,1719570379,1719644781; Hm_lpvt_21a348fada873bdc2f7f75015beeefeb=1719644781; FSSBBIl1UgzbN7NP=5Rv5nMDRd7baqqqDA3zDd9a283B0wU93MkS9KJPnFe6CBe_CJZEvMnOO47V8d3kebx.uYMb4zQoh0ciC6Wtf3jx.6HCyx3mShkYcMrJ5A8R6gsymjegmlAsFWm3b1zqR5X_18Ha46KDRQonyHOxMNQ0AomzJ3uHrBPyDM9F_gP4S7ft.OCaZZ8txRjYlslaSbvyLEv0.Kk4c2e5GUNyHQdreCDpgY7bfSULLhDCfmwAMLYllGPLAjdzSKY.9ONJ3BKryunUV69QM.w9sqY8iylQ")
            .timeout(5000)
            .get();
    Elements content = scriptHtml.getElementsByClass("joblist-box__item");
    for(Element element:content){
        String price = element.getElementsByClass("jobinfo__salary").text();
        String company = element.getElementsByClass("companyinfo__name").text();
        System.out.println(price+" " + company);
    }
}

2.怎么把爬取下来的数据放到数据库中

(建立数据库不再叙述,后面会提到相关数据库中表的结构)

准备工作

(1)需要使用xml文件 (2)需要一个接口 (3)需要一个java类

注意事项:

mapper标签里面写接口的路径

标签当中的id的值必须和Dao接口中的一致;

parameterType是我们的入参找到新建的java类,写该类的路径

新建一个类ZhoLianController,用于爬取数据

java 复制代码
package com.qcby.springbootdemotest.controller;

import com.qcby.springbootdemotest.dao.ZhiLianDao;
import com.qcby.springbootdemotest.entity.SalaryAndSector;
import com.qcby.springbootdemotest.entity.SalaryBySectorAndAddress;
import com.qcby.springbootdemotest.entity.SectorAndAddress;
import com.qcby.springbootdemotest.entity.ZhiLian;
import com.qcby.springbootdemotest.url.AddressMap;
import com.qcby.springbootdemotest.url.StringToNumber;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.util.List;

@Controller
@RequestMapping("/zhilian")
public class ZhiLianController {
    @Autowired
    private ZhiLianDao zhiLianDao;

    /*跳转到前端页面
    * @return
    * */
    @RequestMapping("toWorm")
    public String worm(){
        return "/Worm";
    }

    @RequestMapping("/insert")
    @ResponseBody           //加上它返回的是字符串数据,不加返回页面
    public void wormInsert(ZhiLian zhiLian) throws IOException {
        //通过前端传来的值进行url连接
//        ZhiLian zhiLian =new ZhiLian();
        String addressCode= AddressMap.addressMap.get(zhiLian.getAddress());
        String url="https://sou.zhaopin.com/?jl="+addressCode+"&kw="+zhiLian.getSector();
        System.out.println("url="+url);
//        Document document= Jsoup.connect(url).get();
        Document document= Jsoup.connect(url)
                .header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
                .header("accept-encoding", "gzip, deflate, br")
                .header("accept-language", "zh-CN,zh;q=0.9")
                .header("cache-control","max-age=0")
                //1.
                .header("Connection","keep-alive")

                .header("cookie", "x-zp-client-id=4eb47c56-083c-437f-9954-be4bd7c64c1d; LastCity=%E7%9F%B3%E5%AE%B6%E5%BA%84; LastCity%5Fid=565; sts_deviceid=190778561d42c8-067ba08b8cf3eb-610a5850-1327104-190778561d5843; FSSBBIl1UgzbN7NO=5N456oUt6XIZ0dS52PldahGoIHM3r5pNAmUYSSxYRnhyO5d.ji9ME12NMfDxmQ9taNtvAI_p4F82_B7IWHmMMsG; _uab_collina=171999335910624941661227; locationInfo_search={%22code%22:%22570%22%2C%22name%22:%22%E4%BF%9D%E5%AE%9A%22%2C%22message%22:%22%E5%8C%B9%E9%85%8D%E5%88%B0%E5%B8%82%E7%BA%A7%E7%BC%96%E7%A0%81%22}; sensorsdata2015jssdkchannel=%7B%22prop%22%3A%7B%22_sa_channel_landing_url%22%3A%22https%3A%2F%2Flanding.zhaopin.com%2Fregister%3Fidentity%3Dc%26channel_name%3Dbaidu_sem_track%26callback_id%3DIQJ0tYqm%26_data_version%3D0.6.1%26channel_utm_content%3Dpp%26project%3Dzlclient%26channel_utm_medium%3Dcpt%26tid%3DxTB%26channel_link_type%3Dweb%26channel_utm_source%3Dbaidupcpz_x%26hash_key%3DuaKIYNjV6PvgIWUUAJ9n%26sat_cf%3D2%26channel_utm_campaign%3Dzbt%26channel_utm_term%3D01%26_channel_track_key%3DM9kzlBZB%26link_version%3D1%26channel_keyword_id%3D%257Bkeywordid%257D%26channel_ad_id%3D%257Bcreative%257D%26channel_account_id%3D%257Buserid%257D%26channel_keyword%3D%257Bkw_enc_utf8%257D%26channel_adgroup_id%3D%257Bunitid%257D%26channel_campaign_id%3D%257Bplanid%257D%22%7D%7D; zp_passport_deepknow_sessionId=6badb2ecs6aefc42fdb2e87e80cfe24b6c04; at=42d86c88e30d4c45accda450280d83a2; rt=db0d505e13b64d9085e8f6d39d530c9c; sts_sg=1; sts_chnlsid=Unknown; zp_src_url=https%3A%2F%2Flanding.zhaopin.com%2F; ZP_OLD_FLAG=false; Hm_lvt_21a348fada873bdc2f7f75015beeefeb=1719993359,1720077082,1720098938,1720253347; HMACCOUNT=69522ADBD7E7EA74; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%221204364767%22%2C%22first_id%22%3A%22190778516f7103-036bced636145e2-610a5850-1327104-190778516f88a9%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkwNzc4NTE2ZjcxMDMtMDM2YmNlZDYzNjE0NWUyLTYxMGE1ODUwLTEzMjcxMDQtMTkwNzc4NTE2Zjg4YTkiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIxMjA0MzY0NzY3In0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%221204364767%22%7D%2C%22%24device_id%22%3A%22190778516f7103-036bced636145e2-610a5850-1327104-190778516f88a9%22%7D; ZL_REPORT_GLOBAL={%22/resume/new%22:{%22actionid%22:%2262436bd1-2cf0-4071-87b5-7743c850cb12%22%2C%22funczone%22:%22addrsm_ok_rcm%22}}; Hm_lvt_7fa4effa4233f03d11c7e2c710749600=1719992130,1720254877; HMACCOUNT=69522ADBD7E7EA74; sts_sid=19087ba37ceb92-0e16a87c97a01c-610a5850-1327104-19087ba37cf94e; sts_evtseq=1; acw_tc=276077ca17202640461465087eba9d8b843d450716e18401fd52993b0e0351; Hm_lpvt_7fa4effa4233f03d11c7e2c710749600=1720264257; selectCity_search=570; Hm_lpvt_21a348fada873bdc2f7f75015beeefeb=1720264296; FSSBBIl1UgzbN7NP=5RL9bRCFxxbQqqqDAf4gtoGTGD4GGh5aQBg2fxUIAohkXIwpeZxRk8XvUBUqvlstbzPJL_Pp4I2oMXD_lTYRY2ymhM2dHag3QDlPYmAuTTqgj7Bq7o_H4PrkKMshDVR70P15rquGifog2KjQT7keH6NoCg8orbbrPvjnrqcbJgu7vULmpaEtXVAXbfY5KQsZjasG2wsE3tdDnSL8rI9K9DFmiMLEPfsQAsPVHrcZHE4ayFNtSA8w.h2Qk7.2ZeGCYtw9I883bIHvqbLHa5s1aC2vXhdPp2dXT7VQyaeFZWKYq")
//                .header("referer","https://landing.zhaopin.com/")
                //2.
                .header("sec-ch-ua","\";Not A Brand\";v=\"99\", \"Chromium\";v=\"94\"")

                .header("sec-ch-ua-mobile","?0")
                //3.
                .header("sec-ch-ua-platform","\"Windows\"")

                .header("sec-fetch-dest","document")
                .header("sec-fetch-mode","navigate")
                .header("sec-fetch-site","same-site")
                .header("sec-fetch-user","?1")
                .header("upgrade-insecure-requests","1")

                .header("user-agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Core/1.94.225.400 QQBrowser/12.2.5544.400")

                .timeout(50000)
                .get();
//        System.out.println(document);
        Elements elements=document.getElementsByClass("joblist-box__item");//clearfix
        for(Element element:elements){
            String salary = element.getElementsByClass("jobinfo__salary").text();
            String company = element.getElementsByClass("companyinfo__name").text();
            System.out.println(salary+"--"+ company);
            //判断金额当中是否包含天
            if(salary.contains("天")){
                break; //包含天这个数据不能使用
            }else {
                String s1 = StringToNumber.getValue(salary); // 获取最小的金额
                Double money = StringToNumber.StrToNum(s1) * 12;
                zhiLian.setSalary(String.valueOf(money));
            }
            zhiLian.setCompany(company);
            zhiLianDao.insert(zhiLian);
        }
    }

    @RequestMapping("/findBySalaryAndSector")
    @ResponseBody
    public List<SalaryAndSector> findBySalaryAndSector(){
        return zhiLianDao.fundBySalaryAndSector();
    }

    @RequestMapping("/findCountBySectorAndAddress")
    @ResponseBody
    public List<SectorAndAddress> findCountBySectorAndAddress(String address){
        return zhiLianDao.findCountBySectorAndAddress(address);
    }

    @RequestMapping("/findSalaryBySectorAndAddress")
    @ResponseBody
    public List<SalaryBySectorAndAddress> findSalaryBySectorAndAddress(String address){
        return zhiLianDao.findSalaryBySectorAndAddress(address);
    }
}

最后一步,我们需要设置对应的数据库

主要修改数据库路径,选择哪个数据库,以及账号密码即可。

然后运行整个项目,在浏览器输入访问的网址 localhost:8080/zhilian/insert

8080是端口号 访问之后就会进行爬取数据然后放入数据库。

3.设置一个简单的页面,进行某些属性的选择,然后点击爬取之后进行爬取,而不是我们去输入上面提到的网址进行爬取。

输入爬取的城市和爬取的职业,就能进行爬取了,但是智联招聘它的地址都是使用代号进行代替,比如北京你就需要输入530

我们可以使用一个Hashmap进行转换,更加方便我们爬取,不需要爬取城市还得不停地查询对应的代号

我们将addressMap设置为static final,它是属于类的(并且不可更改),目的是为了方便我们无需创建对象即可使用

对应Controller也要进行修改,只需要修改url的拼接部分即可。

此时我们输入北京,会自动转换为530,输入上海会自动转换为538等。这样就更加方便我们操作了。

还有一个小问题就是,由于后期需要进行数据的比较,比如工资,但是扒取下来的工资的值是一个字符串范围,比如"5千-1.2万"

我们再定义一个工具类,想办法把字符串转换为Double。

我们选取一个最低值进行转换,然后将'-'前面的字符串保留,然后进行转换,千为单位就乘以1000,其余类推。工具类StringToNumber不再赘述

同时需要Controller中进行修改,我们将工资最后设置为年薪(*12)。3. 到此完成。

*4.数据库增删改查操作(这部分属于扩展内容,可简单看)

和之前提到的插入类似,只不过多了查询和修改与删除

新建数据库Student

接口StudentDao

类Student

Mapper.xml

resultType 是返回的数据类型,返回的是我们上面提到的类

上面都形成一种套路,别忘了修改数据库(前面提到的数据库路径)

当我们输入网址进行操作时,如何有形参应该这么填:

5.前台页面展示数据

后台返回给前端的是Json数据,前端拿到数据后我们可以借助Echarts图进行展示

Apache EChartsApache ECharts,一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。https://echarts.apache.org/zh/index.html

从网站上找到我们想要的图,复制代码,然后只需要填入我们的Json数据进去即可展示,很方便。

如何去使用Echarts图帮助我们展示呢,这部分知识我在之前的"++前端学习系列++"中已经总结好了,该系列包含了前端学习的必要重点知识,很适合学习。

大家可以点击直接跳转学习如何使用Echarts图,系列中的其他文章大家也可以去我的主页进行学习,欢迎评论以及私信交流。

前端学习7------自学习梳理-CSDN博客

前端学习7续-CSDN博客

现在要解决的问题变成了后台怎么把Json数据给前端。举个栗子

在前端Html页面写下get方法:

url是Servlet的路径

success:function(data) 当中的data就是后端返回的Json数据。我们可以打印(consloe.log(data) )在浏览器控制台上,可以查看数据

request是前端给的数据,通常在前端会使用data:{} 里面是Json数据传送给后端,我们这里没有写。

我们执行完sql语句将查询到的数据存到data,最后通过response.getWriter.append(data)返回给前端(上面提到的success:function(data)当中的data)。

6.项目整合

后台已经爬取数据并且放到我们的数据库中

接下来就是前台怎么具体获得后台放到数据库中的数据,详细代码和注释都在文档中,并且详细的解释呢我以截图的方式放在下面,大家对照着去学习理解代码会更加容易理解以及印象深刻。

注:其实除了小的改动,大致内容都和上面提到的我的博客 前端学习7 的代码内容差不多,只是修改了如何将前端写死的数据(前端学习7)换成我们真正的数据,这些数据是通过后台调用方法完成的。具体看代码,看的过程中大家就会发现代码很熟悉的,因为就是和前面的代码大差不差。

.html文件

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <!-- 每一款手机有不同的分辨率,不同屏幕大小,如何使我们开发出来的应用或页面大小能适合各种高端手机使用呢?学习html5 viewport的使用能帮你做到这一点. -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- width - viewport的宽度
    height - viewport的高度
    initial-scale - 初始的缩放比例
    minimum-scale - 允许用户缩放到的最小比例
    maximum-scale - 允许用户缩放到的最大比例
    user-scalable - 用户是否可以手动缩放 -->
    <title>Document</title>
    <script src="js/echarts.min.js"></script>
    <script src="js/china.js"></script>
    <script src="js/index.js" defer></script>
    <link rel="stylesheet" href="css/index.css">
    <script>
        // 岗位数量
        fetch('http://localhost:8080/zhilian/zhilianCount')
            .then(response => response.json())
            .then(data => {
                if (data) {
                    document.getElementById('jobCount').innerText = data;
                } else {
                    console.error('No data returned from API');
                }
            })
            .catch(error => console.error('Error fetching data:', error));
    </script>
    <!-- 使用fetch函数:fetch('http://localhost:8080/zhilian/zhilianCount')
    这行代码向指定的URL发起了一个GET请求。fetch函数返回一个Promise对象,
    这个对象代表了异步操作的结果(即HTTP请求的响应)。 -->

    <script>
        // 平均薪资最高的地区
        fetch('http://localhost:8080/zhilian/maxSalary')
            .then(response => response.text())
            .then(data => {
                console.log(data)
                document.getElementById('topSector').innerText = data;
            })
            .catch(error => console.error('Error fetching data:', error));
    </script>

    <script>
        // 岗位数量最多的地区
        fetch('http://localhost:8080/zhilian/maxWork') // 修改为你的接口地址
            .then(response => response.json())
            .then(data => {
                if (data) {
                    document.getElementById('highestSalaryCity').innerText = data.address;
                } else {
                    console.error('No data returned from API');
                }
            })
            .catch(error => console.error('Error fetching data:', error));
    </script>
</head>

<body>
    <h1>软件工程岗位招聘信息</h1>
    <div class="main">
        <div class="left">
            <div class="l1"></div>
            <div class="l2"></div>
        </div>
        <div class="middle">
            <ul class="top">
                <li>
                    <h2 id="jobCount"></h2>
                    <h3>岗位数量</h3>
                </li>
                <li>
                    <h2 id="topSector" topSector></h2>
                    <h3>平均薪资最高地区</h3>
                </li>
                <li>
                    <h2 id="highestSalaryCity"></h2>
                    <h3>岗位数量最多地区</h3>
                </li>
            </ul>
            <div class="bottom"></div>
        </div>
        <div class="right">
            <div class="r1"></div>
            <div class="r2"></div>
        </div>
    </div>
</body>

</html>

.css文件

css 复制代码
* {
    padding: 0;
    margin: 0;
}

body {
    background: rgb(16, 12, 42);
}

h1 {
    color: #fff;
    text-align: center;
    /* vh视口高度 */
    height: 10vh;
    font-size: 5.5vh;
    /* 行高 */
    line-height: 10vh;
    /* 字体粗细 */
    font-weight: 500;
}

.main {
    display: flex;
}

.main .left {
    width: 29%;
}

.main .middle {
    width: 42%;
}

.main .right {
    width: 29%;
}

.main .left>div,
.main .right>div {
    width: 100%;
    height: 43vh;
}

.main .middle .top {
    display: flex;
    margin-top: 30px;
    justify-content: space-between;
    padding: 0 20px 0 60px;
}

.main .middle .top li {
    /* 文字居中 */
    text-align: center;
    /* 列表样式去掉 */
    list-style: none;
}

.main .middle .top li h2 {
    color: rgb(173, 255, 47);
    font-size: 4.5vh;
}

.main .middle .top li h3 {
    color: #fff;
    margin-top: 30px;
}

.main .middle .bottom {
    height: 69vh;
}

.js文件

javascript 复制代码
(function() {
    // 初始化 ECharts 实例
    var myChart = echarts.init(document.querySelector('.l1'));

    // 指定图表的配置项和数据
    var option = {
        // 标题
        title: {
            text: '行业岗位数量情况',
            left: "center",
            textStyle: {
                color: "#fff",
            },

        },
        // 提示框组件
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            }
        },
        // x轴相关设置
        xAxis: {
            type: 'category',
            axisLabel: {
                color: "rgb(175,174,197)",
                interval: 0, // 强制显示所有标签
                rotate: 90, // 如果标签太长,可以旋转以避免重叠
            }
        },
        // y轴相关设置
        yAxis: {
            type: 'value',
            axisLabel: {
                color: "rgb(185,184,206)"
            },
            // 修改背景线条样式
            splitLine: {
                show: true,
                lineStyle: {
                    color: "rgb(72,71,83)"
                }
            }
        },
        series: [{
            type: 'bar',
            // 柱子宽度
            barWidth: '50%'
        }],
        // 颜色
        color: ['rgb(51,152,219)'],
        // 网格配置
        grid: {
            // 配合 left right top bottom 设置图表的大小
            left: '3%',
            right: '8%',
            bottom: '5%',
            // 网格区域是否包含坐标轴的刻度标签 true:包含 false:不包含
            containLabel: true
        },
        // 数据缩放组件
        dataZoom: [{
            type: 'slider',
            show: true,
            xAxisIndex: [0],
            start: 0,
            end: 100,
            height: 12,
            bottom: '1%',
        }]

    };

    // 使用刚指定的配置项和数据显示图表
    myChart.setOption(option);

    // 从 API 获取数据并更新图表
    fetch('http://localhost:8080/zhilian/findBySalaryAndCount') // 修改为你的接口地址
        .then(response => response.json())
        .then(data => {


            // 提取数据
            const sectors = data.map(item => item.sector);
            const counts = data.map(item => item.count);


            // 更新 ECharts 的 option 数据
            option.xAxis.data = sectors;
            option.series[0].data = counts;

            // 设置 option 并渲染图表
            myChart.setOption(option);
        })
        .catch(error => console.error('Error fetching data:', error));

    // 窗口大小变化时,重置图表大小
    window.addEventListener("resize", function() {
        myChart.resize();
    });
})();


//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------//
(function() {
    // 初始化 ECharts 实例
    var myChart = echarts.init(document.querySelector('.l2'));

    // 指定图表的配置项和数据
    var option = {
        // 标题
        title: {
            text: '各地区岗位数量',
            left: 'center', // 标题居中排列
            textStyle: {
                color: "#fff",
            },
        },
        tooltip: {
            trigger: 'item'
        },
        legend: {
            type: 'scroll',
            top: 'center',
            left: 'right',
            orient: 'vertical',
            textStyle: {
                color: 'rgb(172,171,194)'
            }
        },
        series: [{
            name: '岗位数量',
            type: 'pie',
            radius: ['40%', '70%'],
            avoidLabelOverlap: false,
            label: {
                show: false,
                position: 'center'
            },
            emphasis: {
                label: {
                    show: true,
                    fontSize: 40,
                    fontWeight: 'bold'
                }
            },
            labelLine: {
                show: false
            },
            data: []
        }],
        // 颜色
        color: ['rgb(73,146,255)', 'rgb(136,255,195)', 'rgb(253,221,96)', 'rgb(255,110,118)']
    };

    // 使用刚指定的配置项和数据显示图表
    myChart.setOption(option);

    // 从 API 获取数据并更新图表
    fetch('http://localhost:8080/zhilian/findBySalaryAndCount') // 修改为你的接口地址
        .then(response => response.json())
        .then(data => {
            console.log('Data fetched:', data); // 调试输出数据

            // 提取数据
            const chartData = data.map(item => ({
                value: item.count,
                name: item.sector
            }));
            console.log('Chart Data:', chartData); // 调试输出处理后的数据

            // 更新 ECharts 的 option 数据
            option.series[0].data = chartData;

            // 设置 option 并渲染图表
            myChart.setOption(option);
        })
        .catch(error => console.error('Error fetching data:', error));

    // 窗口大小变化时,重置图表大小
    window.addEventListener("resize", function() {
        myChart.resize();
    });
})();




//平均薪资右一
(function() {
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.querySelector('.r1'));

    // 初始化配置项
    var option = {
        title: {
            text: '平均薪资最多的前5种职业',
            left: "center",
            textStyle: {
                color: "#fff",
            },
        },
        xAxis: {
            type: 'category',
            axisLabel: {
                color: "rgb(175,174,197)",
                interval: 0, // 强制显示所有标签
                rotate: 30, // 如果标签太长,可以旋转以避免重叠
            }
        },
        yAxis: {
            type: 'value',
            axisLabel: {
                color: "rgb(175,174,197)"
            },
            splitLine: {
                show: true,
                lineStyle: {
                    color: "rgb(72,71,83)"
                }
            }
        },
        grid: {
            left: '3%',
            right: '8%',
            bottom: '5%',
            containLabel: true
        },
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            }
        },
        series: [{
            data: [],
            type: 'line',
            areaStyle: {
                color: {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 0,
                    y2: 1,
                    colorStops: [{
                        offset: 0,
                        color: 'blue' // 0% 处的颜色
                    }, {
                        offset: 1,
                        color: 'transparent' // 100% 处的颜色
                    }],
                    global: false // 缺省为 false
                }
            }
        }]
    };

    fetch('http://localhost:8080/zhilian/findBySalaryAndSector') // 修改为你的接口地址
        .then(response => response.json())
        .then(data => {
            // 排序数据,按平均薪资从高到低排序
            data.sort((a, b) => b.a_salary - a.a_salary);

            // 只保留前五个城市的数据
            const topFiveData = data.slice(0, 5);

            // 提取数据
            const cities = topFiveData.map(item => item.sector);
            const avgSalaries = topFiveData.map(item => item.a_salary);
            console.log('Top Five Cities:', cities); // 调试输出前五个城市数据
            console.log('Top Five Avg Salaries:', avgSalaries); // 调试输出前五个薪资数据

            // 更新ECharts的option数据
            option.xAxis.data = cities;
            option.series[0].data = avgSalaries;

            // 设置option并渲染图表
            myChart.setOption(option);

        })
        .catch(error => console.error('Error fetching data:', error));

    // 窗口大小变化时,重置图表大小
    window.addEventListener("resize", function() {
        myChart.resize();
    });
})();


//右2
(function() {
    // 初始化 ECharts 实例
    var myChart = echarts.init(document.querySelector('.r2'));

    // 指定图表的配置项和数据
    var option = {
        // 标题
        title: {
            text: '各行业平均薪资',
            left: "center",
            textStyle: {
                color: "#fff",
            },
        },
        // X 轴配置
        xAxis: {
            type: 'category',
            axisLabel: {
                color: "rgb(175,174,197)",
                interval: 0, // 强制显示所有标签
                rotate: 90, // 如果标签太长,可以旋转以避免重叠
            }
        },
        // Y 轴配置
        yAxis: {
            type: 'value',
            axisLabel: {
                color: "rgb(175,174,197)"
            },
            splitLine: {
                show: true,
                lineStyle: {
                    color: "rgb(72,71,83)"
                }
            },
            axisLine: {
                show: true
            }
        },
        // 系列配置
        series: [{
            data: [],
            type: 'line',
            smooth: true
        }],
        // 网格配置
        grid: {
            left: '3%',
            right: '8%',
            bottom: '5%',
            containLabel: true
        },
        // 提示框组件
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            }
        },
        // 数据缩放组件
        dataZoom: [{
            type: 'slider',
            show: true,
            xAxisIndex: [0],
            start: 0,
            end: 100,
            height: 12,
            bottom: '1%',
        }]
    };

    // 从 API 获取数据并更新图表
    fetch('http://localhost:8080/zhilian/findBySalaryAndSector') // 修改为你的接口地址
        .then(response => response.json())
        .then(data => {


            const topFiveData = data;

            // 提取数据
            const sectors = topFiveData.map(item => item.sector);
            const avgSalaries = topFiveData.map(item => item.a_salary);
            console.log('Top Five Sectors:', sectors); // 调试输出前五个行业数据
            console.log('Top Five Avg Salaries:', avgSalaries); // 调试输出前五个薪资数据

            // 更新 ECharts 的 option 数据
            option.xAxis.data = sectors;
            option.series[0].data = avgSalaries;

            // 设置 option 并渲染图表
            myChart.setOption(option);

        })
        .catch(error => console.error('Error fetching data:', error));

    // 窗口大小变化时,重置图表大小
    window.addEventListener("resize", function() {
        myChart.resize();
    });
})();



(function() {
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.querySelector('.bottom'));

    // 请求数据
    fetch('http://localhost:8080/zhilian/provinceworkCount')
        .then(response => response.json())
        .then(data => {
            // 处理数据,将数据转换为 ECharts 所需格式
            const chartData = data.map(item => ({ name: item.name, value: item.value }));

            // 指定图表的配置项和数据
            const option = {
                // 标题样式
                title: {
                    text: "全国岗位分布图",
                    textStyle: {
                        color: 'rgb(255,215,0)',
                    },
                    left: 'center',
                    top: '18%'
                },
                tooltip: {
                    trigger: 'item'
                },
                visualMap: { // 左侧小导航图标
                    show: true,
                    x: 'left',
                    y: 'bottom',
                    textStyle: {
                        fontSize: 9,
                        color: 'rgb(185,184,206)'
                    },
                    splitList: [
                        { start: 1, end: 9 },
                        { start: 10, end: 99 },
                        { start: 100, end: 999 },
                        { start: 1000, end: 9999 },
                        { start: 10000 }
                    ],
                    color: ['#334271', '#4d619d', '#6e8adf', '#94d7f1', '#cdeaf6']
                },
                series: [{
                    name: '岗位人数',
                    type: 'map',
                    mapType: 'china',
                    roam: false, // 禁用拖动和缩放
                    itemStyle: { // 图形样式
                        normal: {
                            borderWidth: .5, // 区域边框宽度
                            borderColor: '#009fe8', // 区域边框颜色
                            areaColor: "#ffefd5", // 区域颜色
                        },
                        emphasis: { // 鼠标滑过地图高亮的相关设置
                            borderWidth: .5,
                            borderColor: '#4b0082',
                            areaColor: "#fff",
                        }
                    },
                    label: { // 图形上的文本标签
                        normal: {
                            show: true, // 省份名称
                            fontSize: 8,
                        },
                        emphasis: { // 鼠标滑过地图高亮的相关设置
                            show: true,
                            fontSize: 8,
                        }
                    },
                    data: chartData
                }]
            };

            // 使用刚指定的配置项和数据显示图表
            myChart.setOption(option);
        })
        .catch(error => {
            console.error('Error fetching data:', error);
        });

    window.addEventListener("resize", function() {
        myChart.resize();
    });
})();

项目最终前端截图

附录

Json爬虫(参考代码) 原理其实和Jsoup爬虫差不多,仅供参考

java 复制代码
package com.qcby.springbootdemotest.url;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import java.io.IOException;

public class XXDemo {
    public static void main(String[] args) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        try {
            HttpGet httpGet = new HttpGet("http://localhost:8080/stu/findAll");
            // 设置请求头
            httpGet.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
            httpGet.setHeader("Accept-Encoding", "gzip, deflate, br, zstd");
            httpGet.setHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
            httpGet.setHeader("Cache-Control", "max-age=0");
            httpGet.setHeader("Connection", "keep-alive");
            httpGet.setHeader("Cookie", "Idea-d0f2e52d=4cac9bc5-e85c-4d0c-a38a-1455770b689b");
            httpGet.setHeader("Host", "localhost:8080");
            httpGet.setHeader("Upgrade-Insecure-Requests", "1");
            httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0");

            // 执行请求
            CloseableHttpResponse response = httpClient.execute(httpGet);

            // 获取响应实体
            String responseBody = EntityUtils.toString(response.getEntity(),"UTF-8");

//             输出响应内容
            System.out.println(responseBody);

            //JSON解析
            JSONArray json =  JSON.parseArray(responseBody);
            System.out.println(json.size());
            System.out.println(json.get(0));
            // 关闭响应对象
            response.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

具体代码以及注释详解我已经打包

由于文章限制,无法上传压缩包,如果有需要压缩包可以私信,同时有问题可以评论或者私信都可以,无偿~ 仅仅作为交流学习
相关推荐
佚先森2 分钟前
2024ARM网络验证 支持一键云注入引流弹窗注册机 一键脱壳APP加固搭建程序源码及教程
java·html
古月居GYH16 分钟前
在C++上实现反射用法
java·开发语言·c++
儿时可乖了1 小时前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol1 小时前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-13141 小时前
jdk各个版本介绍
java
天天扭码2 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端