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();
            }
        }
    }
}

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

由于文章限制,无法上传压缩包,如果有需要压缩包可以私信,同时有问题可以评论或者私信都可以,无偿~ 仅仅作为交流学习
相关推荐
李少兄38 分钟前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝44 分钟前
【设计模式】原型模式
java·设计模式·原型模式
ONE_Gua1 小时前
chromium魔改——navigator.webdriver 检测
前端·后端·爬虫
可乐加.糖1 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
s9123601011 小时前
rust 同时处理多个异步任务
java·数据库·rust
9号达人1 小时前
java9新特性详解与实践
java·后端·面试
cg50171 小时前
Spring Boot 的配置文件
java·linux·spring boot
啊喜拔牙1 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
anlogic2 小时前
Java基础 4.3
java·开发语言
非ban必选2 小时前
spring-ai-alibaba第七章阿里dashscope集成RedisChatMemory实现对话记忆
java·后端·spring