Jsoup爬虫——自学习梳理

news2024/11/18 12:18:03
       ——项目已完结(源码在文末)

        一个较大的项目,通过后台进行网站爬虫,选择的是一个招聘类型的网站,爬取数据后会选择一部分放入到我们的数据库中,前台通过后台返回的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。

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设置请求头

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,用于爬取数据

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的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。icon-default.png?t=N7T8https://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文件

<!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文件

* {
    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文件

(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爬虫差不多,仅供参考

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

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

由于文章限制,无法上传压缩包,如果有需要压缩包可以私信,同时有问题可以评论或者私信都可以,无偿~ 仅仅作为交流学习

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS(五)——CSS Fonts(字体)

CSS 字体 CSS字体属性定义字体&#xff0c;加粗&#xff0c;大小&#xff0c;文字样式。 CSS字型 在CSS中&#xff0c;有两种类型的字体系列名称&#xff1a; 通用字体系列 - 拥有相似外观的字体系统组合&#xff08;如 "Serif" 或 "Monospace"&#x…

mysql特殊字符、生僻字存储设置

mysql utf-8模式下&#xff0c;分为ut8mb3,utf8mb4&#xff0c;mb4是支持特殊字符、emoji表情的&#xff0c;mb3是不支持的。 报错信息&#xff1a; 1### Error updating database. Cause: java.sql.SQLException: Incorrect string value: \xF0\xA8\x92\x82\xE6\x95... fo…

vue3+ts+vite+electron+electron-packager打包成exe文件

目录 1、创建vite项目 2、添加需求文件 3、根据package.json文件安装依赖 4、打包 5、electron命令运行 6、electron-packager打包成exe文件 Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron 1、创建vite项目 npm create vitelatest 2、添…

hipBLAS示例程序

GPT-4o (OpenAI) 当然&#xff01;以下是一个简单示例&#xff0c;展示了如何使用hipBLAS库进行矩阵-向量乘法 (GEMV) 的操作。该示例包括初始化 hipBLAS 环境&#xff0c;设置矩阵和向量数据并调用hipBLAS API来执行操作。 首先&#xff0c;确保你已经安装了 ROCm&#xff08…

PYTHON学习笔记(八、字符串及的使用)

目录 1、字符串 1.1、字符串的常用操作 1.2、格式化字符串 1.2.1、占位符格式化字符串 1.2.2、f-string格式化字符串 1.2.3、str.format( )格式化字符串 1.3、数据的验证 1.4、正则表达式 1.5.1元字符 1.5.2限定符 1.5.3其他字符 1.5.4re模块 1、字符串 1.1、字符…

如何安全的申请SSL证书

随着数字化时代的快速发展&#xff0c;互联网政务应用已成为政府服务民众、提升治理效能的重要途径。在这个网络日益复杂的时代&#xff0c;政务网站的安全问题显得尤为重要。2024年&#xff0c;国家出台并从2024年7月1日开始实施执行《互联网政务应用安全管理规定》&#xff0…

系统移植(一)u-boot相关命令

文章目录 一、概念&#xff08;一&#xff09;GNU相关命令的扩展(二进制工具集)&#xff08;二&#xff09;1. ld命令&#xff1a;将.o文件链接生成.elf格式文件2. objcopy命令&#xff1a;格式化拷贝命令&#xff0c;将.elf格式文件格式化拷贝生成.bin文件3. objdump命令&…

idea中项目目录,文件显示不全问题

问题&#xff1a;idea中项目目录显示不全问题 解决办法1&#xff1a; 删除目录中的.idea文件 用idea重新打开文件就行了 办法2&#xff1a;手动导入为maven项目 1. 2. 3. 4.选择要导入的项目&#xff0c;导入为maven

vue3前端开发-小兔鲜项目-登录组件的开发表单验证

vue3前端开发-小兔鲜项目-登录组件的开发表单验证&#xff01;现在开始写登录页面的内容。首先这一次完成基础的首页按钮点击跳转&#xff0c;以及初始化一些简单的表单的输入验证。后期还会继续完善内容。 1&#xff1a;首先还是准备好login页面的组件代码内容。 <script …

四、GD32 MCU 常见外设介绍(8)SPI 模块介绍

串行外设接口&#xff08;Serial Peripheral Interface&#xff0c;缩写为 SPI&#xff09; 提供了基于SPI 协议的数据发送和接收功能&#xff0c; 可以工作于主机或从机模式。 SPI 接口支持具有硬件 CRC 计算和校验的全双工和单工模式。 8.1.SPI 基础知识 SPI 物理层 SPI接…

Pytorch使用教学7-张量的广播

PyTorch中的张量具有和NumPy相同的广播特性&#xff0c;允许不同形状的张量之间进行计算。 广播的实质特性&#xff0c;其实是低维向量映射到高维之后&#xff0c;相同位置再进行相加。我们重点要学会的就是低维向量如何向高维向量进行映射。 相同形状的张量计算 虽然我们觉…

自动驾驶(八十八)---------通讯之SOMEIP

1. 什么是SOME/IP 服务导向架构&#xff08;SOA&#xff0c;Service-Oriented Architecture&#xff09;是一种设计软件系统的方法&#xff0c;强调通过可重用的服务来实现系统的松散耦合。每个服务是独立的功能单元&#xff0c;可以被不同的应用程序使用。这些服务通过标准化的…

新版海螺影视主题模板M3.1全解密版本多功能苹果CMSv10后台自适应主题

苹果CMS2022新版海螺影视主题M3.1版本&#xff0c;这个主题我挺喜欢的&#xff0c;之前也有朋友给我提供过原版主题&#xff0c;一直想要破解但是后来找了几个SG11解密的大哥都表示解密需要大几百大洋&#xff0c;所以一直被搁置了。这个版本是完全解密的&#xff0c;无需SG11加…

ADG901介绍

目录 一、特性二、增强产品特性三、应用四、一般描述五、极低功耗六、引脚描述七、尺寸参数八、电路连接 一、特性 宽带开关&#xff1a;-3 dB 在 4.5 GHz吸收型开关高关断隔离度&#xff1a;在 1 GHz 时为 38 dB低插入损耗&#xff1a;在 1 GHz 时为 0.8 dB单一 1.65 V 至 2.…

QT5:嵌入式linux开发板调用键盘

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录​​​​​​​ 前言 一、Buildroot构建QT环境 1.1 构建环境 1.2 检查qtvirtualkeyboard库 二、测试过程 2.1 直接调用qtvirtualkeyboard 1.测试代码 2.测试效果 2.2 运行…

【Unity国产化信创平台】麒麟银河V10系统虚拟机创建

目录 一、麒麟V10系统镜像下载 二、虚拟机创建流程 三、麒麟银河系统安装流程 一、麒麟V10系统镜像下载 https://www.kylinos.cn/# 官方访问还是会有问题&#xff0c;如果有需要麒麟银河Kylin系统V10的镜像文件&#xff0c;可以留下邮箱或者私信博主获取。 二、虚拟机创…

时间序列分析方法之 -- 移动平均(Moving Average)

目录 原理 适用情况 Python 示例代码 结论 原理 移动平均&#xff08;Moving Average, MA&#xff09;是一种常用的时间序列分析和数据平滑方法。其基本思想是通过取时间序列中某个时间窗口内数据的平均值来消除短期波动&#xff0c;从而更好地揭示数据的长期趋势。根据取平…

【Tomcat】Mac M3 Pro安装Tomcat7

文章目录 下载配置环境变量修改权限启动和关闭 下载 官网&#xff1a;https://tomcat.apache.org/ cd ~/Library tar -zxvf /Users/用户名/Downloads/apache-tomcat-7.0.99.tar.gz mv apache-tomcat-7.0.99 ~/Library/tomcat配置环境变量 vi ~/.bash_profileexport TOMCAT…

UWA Gears正式上线,助力移动平台性能优化

亲爱的开发者朋友们&#xff0c; 我们非常激动地向大家宣布&#xff0c;UWA最新的无SDK性能分析工具 - UWA Gears&#xff0c;现已正式发布&#xff01;无论您使用的是哪种开发引擎&#xff0c;这款工具都能轻松应对&#xff0c;为您的项目保驾护航。更令人心动的是&#xff0c…

vue3【实战】可编辑的脱敏信息

<script lang"ts" setup> import { ref, onMounted } from "vue"; let real_name ref("朝阳");let name ref("");onMounted(() > {name.value des_name(real_name.value); });function focusing() {name.value real_name…