------项目已完结(源码在文末)
一个较大的项目,通过后台进行网站爬虫,选择的是一个招聘类型的网站,爬取数据后会选择一部分放入到我们的数据库中,前台通过后台返回的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图进行展示
从网站上找到我们想要的图,复制代码,然后只需要填入我们的Json数据进去即可展示,很方便。
如何去使用Echarts图帮助我们展示呢,这部分知识我在之前的"++前端学习系列++"中已经总结好了,该系列包含了前端学习的必要重点知识,很适合学习。
大家可以点击直接跳转学习如何使用Echarts图,系列中的其他文章大家也可以去我的主页进行学习,欢迎评论以及私信交流。
现在要解决的问题变成了后台怎么把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();
}
}
}
}
具体代码以及注释详解我已经打包