资讯动态

招聘数据可视化系统实战:从爬虫采集到薪资预测的完整实现

发布时间:2026/9/26 17:16:33 来源:尧图企业网站定制
做这个招聘数据可视化系统前前后后折腾了大概三周时间。从最开始爬51job的岗位数据到清洗整理再到用Flask把后端接口撸出来、ECharts画大屏最后塞进去一个简单的薪资预测模型整个链路走通之后感觉对“大数据”这个词的理解确实不太一样了——它不是什么玄乎的概念就是一条从采集、清洗、存储到展示和建模的流水线。这篇文章就把这个项目的完整思路和实操细节捋一遍包括技术选型的原因、每个模块的核心代码、踩过的坑以及怎么让机器学习的部分不至于沦为摆设。不管你是正在做毕业设计还是想自己搭一套数据分析系统练手应该都能从这里找到能直接用的参考。1. 项目整体设计与技术选型解析1.1 这个系统到底解决什么问题先说项目定位。招聘数据可视化系统的核心价值是把散落在招聘网站上海量的、非结构化的岗位信息变成一张张能看懂的图表更进一步是从里面挖掘出对求职者有参考价值的规律。我当时给自己定的目标是回答这样几个问题哪个城市的互联网岗位需求量最大不同技术栈Java、Python、前端、算法的薪资中位数大概在什么水平薪资随工作年限是怎么涨的什么阶段涨幅最猛学历要求对薪资的影响到底有多大能不能根据岗位描述里的关键词预测一个新的岗位大概给多少钱明确这五个问题之后系统的功能边界就清晰了数据采集模块负责拿数据清洗模块负责把脏数据变成结构化表格存储用数据库后端用Flask提供API前端展示用ECharts大屏机器学习模块做薪资预测和岗位分类。整套系统不追求大而全但每个环节都必须能跑通。这个思路本质上就是一个迷你版的数据仓库加BI系统。对个人项目来说难度不在于某个单一技术点而在于把所有环节串起来的时候每个环节都会出现一些小问题解决这些问题的过程才是这个项目真正有价值的地方。1.2 技术选型为什么是PythonFlask而不是其他组合先说为什么不用Spring Boot或者Node.js。做数据分析类项目Python是绕不开的原因很直接pandas做数据清洗、scikit-learn做机器学习模型、requests写爬虫这些库在同类工具里基本就是事实标准。如果换成Java光是把数据清洗那套逻辑用Java重写一遍工作量就得翻倍。但Python本身不擅长做Web页面渲染。这时候Flask的价值就出来了——它足够轻量核心就一个路由加一个模板引擎不需要像Django那样强制你按照它的目录结构和ORM规则来组织代码。对于一个以数据处理为核心的演示性系统Flask意味着我可以把精力集中在数据链路本身而不是被框架约束。如果你用过FastAPI可能会问为什么不用它。说实话FastAPI的自动文档和类型提示确实香但Flask的生态更成熟网上随便一搜就是案例遇到问题好排查校园网环境里部署也顺手。对于一篇面向初学者的教程型项目来说Flask的认知门槛更低。数据库方面我选了SQLite没有上MySQL。理由很实在这个项目的数据规模撑死也就几万条SQLite单文件存储、零配置、Python标准库自带驱动对课程设计和本地演示来说绰绰有余。如果你的数据量真的到了百万级或者需要多用户并发写入再考虑换MySQL不迟——后面我会说说迁移的思路。前端可视化一开始纠结过要不要用DataV或者FineReport那种现成的BI工具后来还是选了ECharts。原因有三个第一ECharts的图表类型最全地图、词云、桑基图、雷达图都有大屏效果好第二它跟Flask后端配合最简单后端吐JSON前端fetch拿数据直接setOption就能出图完全不需要引入重型框架第三ECharts是纯前端渲染不用像pyecharts那样在服务端维护图表状态刷新页面就是最新的。1.3 机器学习在这个项目里的定位与算法选型很多类似的毕设项目把机器学习做成一个噱头放个神经网络就完事了。我自己的理解是在这个项目里机器学习的定位应该是“辅助决策”而且要跟业务能对上。我实际落地了两个方向岗位薪资预测输入特征为城市、学历、经验要求、岗位类别预测月薪中位数。这是一个典型的回归问题我用的是线性回归和随机森林回归做对比输出R²和均方误差。为什么不直接用深度学习因为样本量只有几千条深度学习容易过拟合而且解释性差写文档的时候不好说明白。岗位职能分类根据职位名称里的关键词自动判断它属于技术类、产品类、运营类、设计类还是职能类。这是一个文本分类问题我用的是朴素贝叶斯加TF-IDF特征简单高效几百条样本就能有不错的效果。选这两个模型还有一个私心它们都是scikit-learn里可以直接调用的代码量不大但能讲清楚机器学习的完整流程——特征工程、数据集划分、模型训练、评估。这就能把“大数据”“机器学习”“可视化”这些关键词全部串起来项目显得完整答辩的时候也站得住脚。2. 数据链路与核心功能拆解2.1 51job招聘数据爬虫的设计要点爬虫是整个数据链路的起点。写爬虫之前先要明确目标字段职位名称、公司名称、城市、薪资范围、学历要求、工作经验要求、发布时间、职位描述、公司规模、行业类型。这些字段覆盖了后续所有可视化图表的输入。我选的采集对象是51job的搜索列表页。它的页面结构相对规整初版代码没有登录和验证码要求对学习项目来说是最合适的。关键步骤是构造搜索URL、解析列表页、进入详情页提取完整职位描述、翻页循环。请求的时候我给自己定了几条纪律设置合理的User-Agent随机延时2到4秒每天的控制总量不要太大遵守目标站点基本的访问礼仪。爬虫写出来是练手和学习不是跟网站对抗这个边界要清楚。解析用的BeautifulSoup加lxml解析器定位元素的时候优先找稳定的class或者id比如职位列表项的class名、薪资节点的class名。遇到页面结构调整代码崩溃是家常便饭所以解析逻辑要集中封装在独立的函数里方便维护。采集完成之后原始数据会存成CSV文件备份同时导入SQLite。这里有个小技巧raw_data表里存的是完全没处理过的原始文本清洗之后的数据放另一张表两张表分开的好处是后面发现清洗逻辑有bug还能从原始表重新跑一遍不用再去爬一次。2.2 数据清洗把脏数据变成可分析的表格爬下来的数据是没法直接用的脏得五花八门。我遇到的典型问题有这些薪资字段是字符串比如“10-15千/月”“1-1.5万/月”“面议”得统一转成数值。学历字段不统一“本科”“本科及以上”“大专”“硕士”“学历不限”混着来。经验字段也是文本“3-4年经验”“无需经验”“在校生/应届生”这些说法全不一样。城市字段有带“上班”的有带“深圳”的要去括号。职位描述很长里面全是HTML标签要去掉。清洗的核心逻辑我写成了pandas的transform函数。薪资字段的处理思路是能匹配到数字区间就取中位数统一换算成“元/月”。比如“10-15千/月”解析出10和15乘以1000取平均值12500。“1-1.5万/月”则乘以10000再取平均。匹配不到的就直接标记成空值建模的时候去掉。经验字段我做了分段映射0对应“无需经验”和“在校生/应届生”1对应“1年以下”2对应“1-3年”3对应“3-5年”5对应“5-10年”10对应“10年以上”。这样做成有序的数值型特征回归模型可以直接用。学历字段也做了有序映射初中及以下0、高中1、大专2、本科3、硕士4、博士5。排序的逻辑是“门槛越高数值越大”这个特征之后在薪资预测模型里能派上用场。缺失值处理策略关键字段缺失的直接丢弃该行因为总量够用不会太心疼如果某列缺失率超过30%比如公司福利这种就直接整列不纳入分析。整个清洗流程封装成一个clean_data()函数跑完之后可以看到原始数据和清洗后数据的行数对比心里有数。2.3 数据大屏从数据到图表的翻译过程数据大屏本质上是一个“翻译器”——把数据库里的数字翻译成人类视觉能快速理解的形式。但这个翻译不是把每个字段画张图就完事得跟着业务问题走。对照前文提的那五个问题我设计了大屏的布局全国岗位分布地图用地图展示不同城市的岗位数量热力分布直接回应“哪个城市岗位最多”。岗位需求量TOP10条形图横向条形图展示需求量最高的十个城市让用户一眼看到头部城市排序。行业分布饼图展示互联网招聘岗位集中在哪些行业比如电子商务、企业服务、金融、教育等。学历要求环形图以环形图直观展示不同学历要求的岗位占比回答“我的学历能投什么岗位”。经验要求折线图展示各经验年限岗位的平均薪资曲线直观回答“经验怎么影响薪资”。技能词云从职位描述中提取Top N高频技术词Java、Python、Spring、MySQL等词云的好处是直观坏处是视觉干扰大所以在设置里控制词汇量在50个以内过滤掉噪音词。大屏的视觉细节有一处很关键——配色。跟传统的白底报表不一样大屏用的是深色底加高饱和配色的方案选深蓝背景#1D1E26加渐变色的柱条高亮用亮蓝或暖黄。因为大屏的阅读场景往往是离得比较远的投屏展示深色背景能减少光刺激高亮色系让重点信息跳出来。前端结构上没有用框架就是纯HTML加原生JavaScript。每个图表一个div容器ECharts初始化之后通过fetch调用Flask的API接口拿数据更新数据就是重新setOption。加了一个5分钟自动刷新的定时器这样大屏挂在电视上的时候数据可以持续更新而不需要人工操作。2.4 机器学习模块与数据可视化的联动机器学习的产出不能只停留在控制台里打印几个指标那用户看不到价值。我做的是把模型的预测结果也变成可视化的一部分。以大屏上的“薪资预测器”模块为例左侧是输入区城市下拉框、学历下拉框、经验滑块、岗位类别下拉框右侧实时显示预测结果。用户选完参数前端拼一个JSON请求Flask把参数转成特征向量调用训练好的模型predict返回预测值图表区画一个局部回归拟合线展示这个特征下的薪资趋势。这个交互让机器学习模块真正地“被使用”了而不只是做给评委看。每次模型预测时系统还会顺带返回该预测的置信区间和推荐指数根据样本覆盖率计算的一个简单比例让输出不只是一个干巴巴的数字。为了让结果有说服力我还做了一个“特征重要性”条形图从随机森林模型里提取feature_importances_展示薪资影响因素的排序——城市排第一经验第二学历第三岗位类别第四。这个图对外行的解释力极强一眼就能看出什么因素对工资影响最大比一堆R²、MAE指标直观得多。3. 实操过程与关键代码实现3.1 项目结构与环境准备动手前先把目录结构定下来避免后期代码堆成一座山。我的最终项目结构如下job_analysis/ ├── app.py # Flask主应用入口 ├── config.py # 全局配置数据路径、模型路径等 ├── crawler/ │ ├── job_spider.py # 51job爬虫 │ └── parser.py # 页面解析工具 ├── data/ │ ├── raw_jobs.csv # 爬取原始数据 │ └── clean_jobs.db # SQLite数据库 ├── preprocessing/ │ └── clean.py # 数据清洗模块 ├── models/ │ ├── train_salary.py # 薪资预测模型训练 │ ├── train_category.py # 岗位分类模型训练 │ └── job_classifier.pkl # 分类器模型文件 ├── static/ │ ├── css/dashboard.css │ └── js/ │ ├── charts/ │ ├── api.js │ └── dashboard.js └── templates/ ├── index.html # 数据大屏页 └── predict.html # 薪资预测页环境搭建直接用Anaconda创建虚拟环境装好pandas、scikit-learn、requests、beautifulsoup4、flask、flask-cors。Python版本用的3.9这几个库在3.9下兼容性最好不会出现什么cryptography版本报错的幺蛾子。一个小建议装包之前先更新pip不然一些依赖包会装到旧版本后面import的时候报错排查起来浪费时间。3.2 爬虫模块从网页到结构化数据爬虫的核心逻辑分三个函数build_url()负责构造搜索链接parse_list_page()负责解析列表页parse_detail_page()负责解析详情页。构造搜索URL这一步比较关键51job的搜索URL格式大致是def build_url(keyword, city深圳, page1): # 搜索关键字、城市、页码拼出目标URL params { keyword: keyword, city: city, page: f{page}, } return https://search.51job.com/list/010000,000000,0000,00,9,99, \ f{quote(keyword)},2,{page}.html注意quote()是必须的中文关键字不UrlEncode的话直接放进URL里请求很容易被拒。列表页解析的套路是先找到每一条职位记录的列表节点然后逐条提取from bs4 import BeautifulSoup def parse_list_page(html): soup BeautifulSoup(html, lxml) job_items soup.select(.j_joblist .e) results [] for item in job_items: title_tag item.select_one(.jname) company_tag item.select_one(.cname) salary_tag item.select_one(.sal) city_tag item.select_one(.area) # 提取详情页链接进入详情页解析更多字段 detail_url title_tag.get(href) if title_tag else results.append({ title: title_tag.get_text(stripTrue) if title_tag else , company: company_tag.get_text(stripTrue) if company_tag else , salary: salary_tag.get_text(stripTrue) if salary_tag else , city: city_tag.get_text(stripTrue) if city_tag else , detail_url: detail_url, }) return results详情页里的职位描述字段才是真正的金矿。拿下来之后要去掉HTML标签再拿正则做初步清洗。这里我踩过一个坑——有些职位描述里会带上公司自己的招聘广告模板比如“公司福利五险一金、弹性工作、定期团建”这些信息在建模时是噪音后来我做了个关键词黑名单把“福利”“团建”“五险一金”这类词在生成词云时过滤掉。主循环里控制节奏很重要import time from random import random def run_spider(keyword, max_pages10): all_data [] for page in range(1, max_pages1): url build_url(keyword, pagepage) html fetch_url(url) # requests.get 异常重试 if not html: break all_data.extend(parse_list_page(html)) time.sleep(2 random() * 3) # 随机延时避免请求过快 save_to_csv(all_data, data/raw_jobs.csv)max_pages不要拉太高如果你是本地练习10页够用了大概能拿几百条到一千条记录。抓太多之后清洗和建模的时间成本都会上去反而影响项目推进。注意爬取公开网页数据要把握好度。这个项目的核心目的还是学习数据分析方法以研究学习为目的、控制采集规模的爬虫演示不应该针对任何目标站点做高并发或破解式采集。3.3 Flask后端数据API设计与封装后端职责很纯粹提供数据API。路由设计上遵循“一个图表一个接口”的原则前端要什么就给什么颗粒度细一点后面调试起来很方便。主要的接口如下from flask import Flask, jsonify, render_template from flask_cors import CORS import sqlite3 app Flask(__name__) CORS(app) DATABASE data/clean_jobs.db def query_db(sql, args()): conn sqlite3.connect(DATABASE) conn.row_factory sqlite3.Row cur conn.cursor() cur.execute(sql, args) rows cur.fetchall() conn.close() return [dict(row) for row in rows] app.route(/) def index(): return render_template(index.html) app.route(/api/salary/city) def salary_by_city(): rows query_db( SELECT city, AVG(salary_mid) as avg_salary FROM cleaned_jobs GROUP BY city ORDER BY avg_salary DESC ) return jsonify(rows) app.route(/api/jobs/city) def jobs_by_city(): # 返回岗位量TOP10城市 rows query_db( SELECT city, COUNT(*) as job_count FROM cleaned_jobs GROUP BY city ORDER BY job_count DESC LIMIT 10 ) return jsonify(rows) app.route(/api/salary/experience) def salary_by_experience(): rows query_db( SELECT experience_level, AVG(salary_mid) as avg_salary FROM cleaned_jobs GROUP BY experience_level ORDER BY experience_level ) return jsonify(rows) app.route(/api/jobs/industry) def jobs_by_industry(): # 行业分布聚合 pass app.route(/api/predict/salary, methods[POST]) def predict_salary_api(): # 接收参数 - 特征向量 - 加载PKL模型 - 返回预测 pass写SQL的时候有个细节聚合查询里AVG()不会自动忽略NULL值以外的空字符串所以在清洗阶段就要把非数值的薪资字段替换成NULL而不是空字符串不然AVG的结果会偏小或者报类型错误。Flask的另一个坑是静态文件缓存尤其你在改前端JS和CSS的时候浏览器经常加载旧版本。我在开发时的处理是给静态URL加一个时间戳缓存参数比如/static/js/dashboard.js?t123456789每次更新代码就换一下时间戳能少掉不少头发。3.4 ECharts大屏前端实现要点大屏的HTML骨架很简单就是一个flex网格布局12个图表卡片按行列分布。每个卡片里放一个div初始化对应图表。这里的关键是图表配置项的写法以及数据接口的对接方式。以地图为例ECharts地图需要注册中国地图GeoJSON数据新版ECharts把地图数据拆出来了所以需要单独引script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/map/js/china.js/script图表初始化的核心套路async function loadCityJobs() { const res await fetch(/api/jobs/city); const data await res.json(); const chart echarts.init(document.getElementById(cityJobsChart)); chart.setOption({ title: { text: 岗位需求Top10城市, left: center }, tooltip: { trigger: axis }, grid: { left: 3%, right: 4%, bottom: 3%, containLabel: true }, xAxis: { type: value }, yAxis: { type: category, data: data.map(d d.city) }, series: [{ type: bar, data: data.map(d d.job_count), itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ { offset: 0, color: #83bff6 }, { offset: 1, color: #2f89fc } ]) } }] }); window.addEventListener(resize, () chart.resize()); }这里有一个易踩的坑设备像素比。如果大屏跑在4K分辨率的机器上ECharts默认的canvas渲染可能会糊。解决办法是在初始化之前设置echarts.init(dom, null, {devicePixelRatio: 2})或者干脆用SVG渲染器个人项目用SVG更稳字体也清晰大数据量时性能略差但几千条数据完全没压力。使用SVG渲染器的方法const chart echarts.init(dom, null, { renderer: svg });自动刷新大屏的机制也简单一个setInterval(() { refreshAllCharts(); }, 300000);5分钟拉一次全量数据。刷新的时候不要chart.dispose()直接setOption替换数据即可保留过渡动画视觉上会更平滑自然。3.5 机器学习模型的训练与封装薪资预测用的特征列表最终锁定为城市编码、学历等级、经验等级、行业编码、公司规模编码。这些都是数值型可以直接喂给模型。训练脚本的核心流程import pandas as pd import joblib from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score df pd.read_sql(SELECT * FROM cleaned_jobs, conn) # 特征工程对分类变量做数值编码LabelEncoder from sklearn.preprocessing import LabelEncoder le_city LabelEncoder() le_industry LabelEncoder() df[city_code] le_city.fit_transform(df[city]) df[industry_code] le_industry.fit_transform(df[industry]) features [city_code, edu_level, exp_level, industry_code] X df[features] y df[salary_mid] # 分层抽样保证城市分布均匀 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor(n_estimators200, max_depth12, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) print(R2 , r2_score(y_test, y_pred)) print(MAE , mean_absolute_error(y_test, y_pred)) # 保存模型和编码器 joblib.dump(model, models/salary_rf.pkl) joblib.dump(le_city, models/le_city.pkl)模型保存之后Flask里的调用逻辑就变成了拿请求参数 - 用同一个LabelEncoder做编码 - 拼特征向量 -model.predict()- 返回结果。这里有一个特别重要的坑训练时保存的LabelEncoder必须跟模型一起保存预测时用同一个编码器映射。如果预测的时候临时fit一个新的LabelEncoder城市编码顺序会对不上预测结果直接乱套。我的习惯是把所有处理对象打包成一个字典存到同一个pkl文件里要用的时候一把梭出来避免漏文件。joblib.dump({ model: model, le_city: le_city, le_industry: le_industry, features: features }, models/salary_bundle.pkl)岗位分类模型的做法类似只是用的特征变成了TF-IDF词向量分类目标变成岗位类别。我用的是朴素贝叶斯MultinomialNB训练快、在小数据集上稳定性好。想提升效果可以把ngram_range调到(1,2)准确率能提几个百分点但特征维度会涨不少看自己机器性能取舍。4. 常见问题与排查技巧实录4.1 爬虫采集阶段的三个大坑第一个坑是爬着爬着页面结构变了。51job这类招聘网站的页面改版频率不低今天用.j_joblist能选到列表改天就找不到了。我的经验是每个解析函数都要写自己的测试用例尝试独立跑通后再集成到主程序里。如果某个选择器失效优先去浏览器F12看实际DOM结构别凭记忆猜。第二个坑是延时不够被风控。这里说的风控未必是封IP更常见的是返回一个验证页或者空列表。排查方法很原始把每次请求的响应状态码和响应长度打日志。如果连续多次返回同样长度、内容明显不是岗位列表大概率是触发风控了这时候应该停止爬虫等一等而不是换个代理硬刚。第三个坑是编码问题。requests拿到的HTML有时候是GBK编码有时候是UTF-8直接response.text会出现中文乱码。正解是先用response.encoding检查或者直接指定response requests.get(url, headersheaders) response.encoding response.apparent_encoding不过apparent_encoding有时候会误判更稳的办法是先用requests的raw字节流decode成UTF-8解码失败再回退GBK。4.2 Flask后端调试的常见问题开发时遇到最多的Flask报错是KeyError和TypeError基本都出在接口返回的JSON字段名跟前端对不上。前端取d.city但后端返回的key是city_name这种低级错误一旦出现前端图表就是空白。我后来定了一个规则接口返回的JSON结构一律先在浏览器地址栏直接访问一遍确认数据长什么样再去写前端代码。这一步看似多花十秒钟实际上能省掉半小时的瞎猜。Flask的调试模式必须开app.run(debugTrue)这样代码改了不用手动重启服务浏览器刷新就能看到新效果。还有一个隐藏福利是debug模式下报错页面会显示完整的堆栈排查Bug效率天差地别。4.3 ECharts图表渲染不出来的排查顺序ECharts图表空白90%的情况不是ECharts的锅而是数据没取到。我的排查顺序是先看浏览器Network面板的接口请求有没有红字报错再在Console里打印fetch返回的数据确认字段名对不对然后看setOption里的series.data格式是否符合图表要求比如饼图要求的是[{name: ..., value: ...}]结构你传个纯数组它就不画。还有一个容易忽略的问题中文乱码导致图表一片黑。ECharts的title如果出现中文时字体加载失败尤其是CDN被墙的字体文件图表会显示豆腐块一样的方块。解决方法有两个一是把页面meta标签加上charsetUTF-8二是把title里的中文直接通过echarts.graphic.Text渲染或者干脆避免在title里放中文改用图例显示。如果你遇到的是图表重叠或者卡片溢出那多半是flex布局没设置min-width: 0。ECharts容器在flex子项里会默认保持内容宽度导致它撑破父容器解决方法是在CSS里给图表容器加上min-width: 0; height: 100%;。4.4 机器学习效果不好的调优思路如果你的薪资预测模型R²跑出来只有零点几甚至负数不要慌先按这个顺序排查第一检查标签有没有清洗干净。salary_mid如果混入了“面议”转换后的0值或者极端值模型会被带偏先做分位数截断比如去掉最高和最低的1%。第二检查特征是否都是有效数值。如果某个特征全是同一个值比如所有数据都是“本科”那这个特征对模型没有贡献R²自然上不去。第三考虑特征交叉。薪资不是简单的线性叠加城市和经验的交互作用很重要。可以增加一列特征为city_code * exp_level作为交互项加入模型R²通常有提升。第四换模型。线性回归搞不定非线性关系试试随机森林或者梯度提升树后者对类别的稠密编码更友好。我之前做版本对比LightGBM比随机森林在薪资预测上MAE能低8%左右不过安装和调参成本高一点看你是否需要。如果你的样本量特别小比如只有200条那效果不好也许不是模型的锅而是样本压根不够支撑高维特征空间。这时候可以退而求其次用简单的分组统计做“基准模型”再用机器学习模型做对比至少能讲清楚差异在哪。5. 从开发到演示几个提升体验的细节技巧项目基本跑通之后想让答辩或演示效果更好有几个小细节值得花时间打磨。大屏的加载体验要做“分步加载”。初始页面打开时先完成整体布局再用loading遮罩挡住各个图表区域数据来了再逐个消失这个过渡会显得项目很有质感。ECharts自带showLoading方法每个图表数据加载完成之后hideLoading即可。另一个细节是给关键数字做“数字滚动”效果。大屏顶部可以放几个核心KPI卡采集岗位总量、平均月薪、覆盖城市数、岗位类别数。数字变化时做一个滚动动画视觉冲击力比静态数字大很多实现上网上搜一段纯JS数字滚动的代码贴进去就行十几行的事。大屏数据跟随筛选条件联动也是一个加分项。顶部加一个“城市”下拉框选择某个城市后所有图表数据同步更新这比一张静态大屏更能体现系统的交互性。实现上就是所有fetch请求都带上?cityxxx参数后端SQL加一个where条件没有复杂度。部署的时候可以用gunicorn替代Flask自带的开发服务器命令很简单gunicorn -w 4 -b 0.0.0.0:8000 app:appsocket绑定到0.0.0.0的意思是让局域网内的其他设备也能访问演示的时候手机或者另一台电脑直接访问大屏地址不用都挤在一台机器前看。最后再分享一个我在演示前踩过的坑在教室投影上大屏的深色背景和暗色文字很容易看不清楚。投影仪的显示对比度不如显示器解决办法是把大屏的对比度参数调高一些深色背景不要用纯黑用带一点蓝灰色的深色文字颜色用纯白或者亮黄投影的时候能保证可读性。还有ECharts图表的字体大小不要小于12px大屏展示时小于12px的文字根本看不清。这个项目做完一遍你可能会发现所谓“大数据”“机器学习”并没有想象中那么神秘它们就是在一条完整的数据流水线里各自扮演一个角色。掌握了这条流水线的构建方法以后换个数据源比如电商销售数据、房产挂牌数据或者校园一卡通数据整个系统架构都是一样的换的只是爬虫的目标页面和业务图表。这大概就是这类项目最大的价值——它让你在一个真实场景里把数据分析的技能完整地走了一遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑