十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

招聘数据可视化:Python爬虫到Flask图表展示的完整实现

招聘数据可视化:Python爬虫到Flask图表展示的完整实现 简介这是一份基于Python的招聘数据分析可视化系统毕业设计资料包面向计算机相关专业毕业生及需要完成数据类课题设计的同学。资源围绕招聘数据的采集、处理与可视化展示构建了从爬虫脚本、数据清洗分析到前端图表展示的完整闭环可帮助读者快速理解项目结构并作为毕业设计或课程设计直接参考。压缩包共54个文件包含14个Python源码文件、10个HTML页面、8张PNG与6张JPG图片另有配置文件、JS/CSS前端样式、CSV数据样例和说明文档等整体大小8.59MB目录分工清晰便于按模块查阅。目前已有1544人学习。资源内附爬虫主程序、分析模块、交互式可视化页面、数据样例及环境配置脚本并包含文档说明既可用于毕设演示也能帮助初学者掌握招聘数据分析可视化的常见实现路径。1. 招聘数据可视化从爬虫抓取到图表展示的三层结构这套毕业设计源码给我的第一印象是它的重点并不在 ECharts 画图本身而在于整条链路是配置驱动的。你从conf.ini/my.ini里改一个城市名或关键词spider_main.py就会重新抓取招聘页面落成test.csv接着process_data.py负责清洗analyze_data.py做聚合统计需要批量处理时还能切到analyze_spark.py走 Spark 的 local 模式最后server.py用 Flask 起服务把统计结果渲染进show.html、us.html和interaction.html三个页面。它很适合两类人一类是拿 python 数据分析与可视化做课程设计或毕业设计的在校生另一类是没完整跑过爬虫→清洗→分析→展示全链路、想看看 pandas 和 Spark 各自边界的开发者。下文我按每一层拆开讲包括参数语义、代码逻辑和常见坑位。2. 爬虫层conf.ini 配置驱动与 spider_main 任务编排2.1 conf.ini 到底在配置什么这个项目的爬虫入口并不复杂但它的配置项设计值得先看清楚。根目录下有conf.ini、my.ini和一万.ini本质上都是同一类配置文件区别只是针对不同招聘网站或不同关键词组合的预置方案。我用conf.ini举例它的典型结构如下[crawl] crawl_url https://search.xx.com/jobs?keyword{keyword}city{city} keyword Python数据分析 city 北京 max_page 5 delay_seconds 1.5 timeout 10 output_file data/test.csv [request] user_agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36这段配置的逻辑很直白crawl_url是搜索页模板{keyword}和{city}是占位符运行时被真实值替换max_page控制翻页深度delay_seconds控制每次请求之间的间隔。项目里用configparser读取这段配置代码大概是这样的import configparser config configparser.ConfigParser() config.read(conf.ini, encodingutf-8) crawl_url config.get(crawl, crawl_url) keyword config.get(crawl, keyword) city config.get(crawl, city) max_page config.getint(crawl, max_page) delay config.getfloat(crawl, delay_seconds) timeout config.getint(crawl, timeout) output_file config.get(crawl, output_file)这里我说明三个参数选择的理由第一用configparser而不是 YAML是因为它属于 Python 标准库毕设环境里不需要额外pip install出问题的概率最低第二getint/getfloat能自动做类型转换避免手写int()时因为空值抛异常第三delay_seconds不要设成 0哪怕是最基础的演示连续高频请求招聘网站也容易被限流后面我会单独讲这个坑。2.2 spider_main.py 的任务拼接与请求逻辑配置文件负责要抓什么spider_main.py负责怎么抓。这个文件的流程可以拆成四步拼接 URL、发起请求、解析页面、写 CSV。核心代码是下面这段import requests from bs4 import BeautifulSoup import csv import time headers { User-Agent: config.get(request, user_agent), Referer: https://search.xx.com/jobs, } all_rows [] for page in range(1, max_page 1): url crawl_url.format(keywordkeyword, citycity) fpage{page} try: resp requests.get(url, headersheaders, timeouttimeout) resp.raise_for_status() except requests.RequestException as e: print(f[ERROR] page {page} failed: {e}) continue soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.job-list-item): row { position: item.select_one(.job-title).text.strip(), company: item.select_one(.company-name).text.strip(), salary: item.select_one(.salary).text.strip(), city: city, experience: item.select_one(.exp).text.strip(), education: item.select_one(.edu).text.strip(), } all_rows.append(row) time.sleep(delay) with open(output_file, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesrow.keys()) writer.writeheader() writer.writerows(all_rows)这段代码有三个细节值得展开resp.raise_for_status()会在 HTTP 状态码非 200 时主动抛异常这样翻页失败不会静默地写入半截数据encodingutf-8-sig会在 CSV 头部写入 BOM保证 Excel 直接打开不乱码这是 Windows 演示环境里很实用的处理time.sleep(delay)放在每页请求之后配合配置项里的delay_seconds是控制请求频率的主要手段。初次跑通时我建议先把max_page改成 1只抓一页验证选择器对不对再放开翻页深度。选择器匹配不到数据时soup.select(.job-list-item)会返回空列表程序不会报错但 CSV 里只有表头——这种情况先检查页面结构而不是检查代码。2.3 CSV 字段设计与落盘格式爬虫层产出的test.csv字段顺序很重要因为后续process_data.py是按列名读取的。这个项目约定的是七列position、company、salary、city、experience、education再加上我建议补充的publish_time。一个容易踩的点是salary字段。爬虫层不要在这里做薪资解析比如把15-20K·14薪拆成数字这是分析层process_data.py的职责。爬虫保持原始字符串好处是数据可回溯——如果清洗逻辑写错了还能从 CSV 重新处理不用重新抓网页。同样的原则也适用于experience字段原始文本如3-5年、经验不限都原样保留。2.4 请求失败的兜底策略招聘网站的结构变化比想象中频繁这个项目里爬虫的容错设计是单页失败只打印错误日志continue进入下一页而不是中断整个流程。实际运行中你会遇到ConnectionError、Timeout或HTTP 403前两个是网络波动403基本是请求头被识别了。处理时先确认user_agent是否是真实浏览器的 UA再看有没有必填的 Cookie这两个检查能解决大多数403问题。3. 数据分析层pandas 与 Spark 双轨清洗策略3.1 为什么同一份数据要准备两条分析路径这是这套源码和普通 Flask 展示项目最大的区别——它把pandas路径和Spark路径同时放在了工程里。analyze_data.py走的是 pandas 的read_csvgroupbyanalyze_spark.py走的是spark.read.csvgroupBy。对于几百条到几千条的招聘数据pandas 完全够用单线程也能在几百毫秒内完成聚合但换个场景比如关键词扩到几十个、城市扩到十几个爬虫落盘的文件从一个变成十几个Spark 的批处理价值就出来了。毕设答辩时数据量变大怎么办是最容易被问到的问题analyze_spark.py的存在就是回答这个问题的材料它能直接读入多个 CSV 做同样的清洗和聚合而 pandas 那边需要手动pd.concat合并所有文件。Spark 在这里用local[*]模式启动不需要额外搭集群演示时只是启动慢一点。3.2 process_data.py 的字段归一化清洗层是这套系统里代码最核心的部分。process_data.py要解决三件事去重、薪资文本转数值、城市和学历字段归一化。下面这段是典型的清洗逻辑import pandas as pd df pd.read_csv(data/test.csv, encodingutf-8-sig) df df.drop_duplicates(subset[position, company, city]) def parse_salary(s): if K not in s: return None, None low, high s.replace(K, ).split(-)[:2] return float(low), float(high) df[salary_low] df[salary].apply(lambda x: parse_salary(x)[0]) df[salary_high] df[salary].apply(lambda x: parse_salary(x)[1]) df[avg_salary] (df[salary_low] df[salary_high]) / 2 df[city] df[city].str.replace(·海淀, ).replace(·朝阳, )这里drop_duplicates的去重键是职位公司城市因为同一家公司可能在不同城市发相同职位只看职位和公司会误删parse_salary用先替换K再取前两段的写法是因为部分数据会带·14薪这种后缀split(-)[:2]能安全忽略后半段。清洗前后的对比可以看下表原始字段清洗后字段处理方式北京·海淀北京正则去除区县后缀15-20K·14薪salary_low15, salary_high20, avg17.5提取数字区间并计算均值3-5年3-5年保留原文统计时做区间映射大专 / 本科 / 硕士大专 / 本科 / 硕士直接归一为标准名称清洗完成后df.to_csv(data/cleaned.csv, indexFalse)落一份中间结果这样做的好处是后面调图表参数时不用反复跑爬虫和清洗直接读这份 CSV 做调试。3.3 analyze_data.py 的聚合口径analyze_data.py的核心是三个统计口径城市平均薪资、岗位数量 Top10、学历要求分布。这三个口径正好对应show.html上的三张基础图。它的聚合代码是典型的 pandas 操作import pandas as pd import json df pd.read_csv(data/cleaned.csv, encodingutf-8-sig) city_salary ( df.groupby(city)[avg_salary] .mean() .round(1) .sort_values(ascendingFalse) .reset_index() ) top_positions ( df[position].value_counts().head(10).reset_index() ) top_positions.columns [position, count] edu_dist df[education].value_counts().reset_index() edu_dist.columns [education, count] result { city_salary: city_salary.to_dict(orientrecords), top_positions: top_positions.to_dict(orientrecords), edu_dist: edu_dist.to_dict(orientrecords), } with open(data/analysis_result.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse)注意.round(1)的位置——先求均值再保留一位小数如果先 round 再聚合精度误差会累积。to_dict(orientrecords)输出的是[{city: 北京, avg_salary: 18.2}]这种结构前端拿到后几乎不需要做二次转换可以直接塞进 ECharts 的dataset里。ensure_asciiFalse保证 JSON 里中文正常显示不转成\uXXXX转义序列。3.4 analyze_spark.py 的批处理写法analyze_spark.py的思路是把process_data.py里用 pandas 做的清洗搬到 Spark DataFrame 上重做一遍from pyspark.sql import SparkSession from pyspark.sql.functions import col, split, avg, count spark SparkSession.builder.appName(job_analysis).getOrCreate() df spark.read.option(header, True).csv(data/*.csv) df df.dropDuplicates([position, company, city]) df df.withColumn(salary_low, split(split(col(salary), -)[0], K)[0].cast(float)) df df.withColumn(salary_high, split(split(col(salary), -)[1], K)[0].cast(float)) result df.groupBy(city).agg(avg(salary_low).alias(avg_low)) result_pd result.toPandas() result_pd.to_json(data/spark_result.json, orientrecords, force_asciiFalse)这里的split嵌套比较绕拆开看是两次切割先按-切出薪资区间的前半段再按K去掉单位后缀最后cast(float)转数值。toPandas()这一步是刻意的因为可视化层读的是 JSON 文件统一由 pandas 结构输出前端不直接连 Spark。演示时如果 Spark Session 启动失败最常见的原因是本机没配JAVA_HOME这个问题我会在最后一章单独讲。4. 可视化层Flask 路由、JSON 接口与 ECharts 联动4.1 server.py 的路由设计整个可视化层由server.py一个文件承载用的是 Flask 而不是 Django理由很明确Flask 的路由到视图的映射足够直观模板渲染语法简单对没有 Web 开发经验的人来说几天就能上手改。它的路由规划如下表路由模板职责/templates/index.html系统首页项目介绍/showtemplates/show.html静态统计图表展示/ustemplates/us.html报告与说明/interactiontemplates/interaction.html交互式筛选图表/spidertemplates/spider.html爬虫运行状态展示服务端读取上一章生成的analysis_result.json并把它传给模板核心代码大概是from flask import Flask, render_template, jsonify import json app Flask(__name__) with open(data/analysis_result.json, encodingutf-8) as f: analysis_data json.load(f) app.route(/show) def show(): return render_template(show.html, dataanalysis_data) app.route(/api/city_data) def city_data(): city request.args.get(city, 北京) filtered [x for x in analysis_data[city_salary] if x[city] city] return jsonify({status: 0, data: filtered})这里有一个关键点静态图表用render_template直接传数据交互页面走/api/city_data接口重新查询。二者对比render_template适合首屏渲染数据量小、加载快接口方式适合用户切换城市时局部刷新不用重载整个页面。4.2 JSON 接口的返回结构/api/city_data返回的 JSON 结构对前端是透明的硬编码为统一格式{ status: 0, data: [ { city: 北京, avg_salary: 18.2, count: 120 } ] }status字段供前端判断请求是否成功data才是业务数据。这个项目的页面模板里有不少fetch调用JavaScript 拿到data后直接chart.setOption({...})更新图表。注意接口和页面同源不存在跨域问题如果后期把 Flask 分成前后端两个端口部署才需要额外配置CORS。前端联动部分的逻辑大致是document.getElementById(city-select).addEventListener(change, async function (e) { const city e.target.value; const resp await fetch(/api/city_data?city${encodeURIComponent(city)}); const result await resp.json(); if (result.status 0) { salaryChart.setOption({ series: [{ data: result.data.map(d d.avg_salary) }] }); } });encodeURIComponent对中文城市名做编码是这里最容易漏的一步如果直接拼 URL城市名为北京时可能在某些浏览器下请求异常。这个交互逻辑在答辩演示时很加分它让页面从静态图表变成了可以操作的分析工具。4.3 interaction.html 与 show.html 的职责边界这套源码里interaction.html是核心演示页show.html是备选方案。一个常见的误区是试图把所有图表都塞进同一个页面结果加载慢、渲染乱。这个项目的做法是静态组图放show.html交互式筛选放interaction.html职责分离。interaction.html里除了城市筛选还有按学历、经验年限两个下拉框每个下拉框变化都会重新请求接口。这样做还有一层好处如果某个图表接口报错报错范围被限制在单页内不会影响其他页面的正常展示。5. 部署验证与常见坑把毕业设计系统完整跑起来5.1 一键与环境准备压缩包里的install_package.bat做了三件事安装requirements.txt里的 Python 依赖、创建data目录、按顺序执行爬虫和分析脚本。手动执行时对应的命令是pip install -r requirements.txt python spider_main.py python process_data.py python analyze_data.py python server.py然后浏览器访问http://127.0.0.1:5000。如果只做可视化演示而跳过爬虫可以先用压缩包自带的test.csv直接跑process_data.py数据已经存在不用重复抓取。5.2 高频踩坑排查表现象原因处理ModuleNotFoundError: pandas依赖没装全对比requirements.txt逐个安装conf.ini读取乱码文件编码不是 UTF-8config.read(conf.ini, encodingutf-8)Flask 端口 5000 被占用其他服务占用了端口app.run(port5050)换端口启动图表区域空白前端拿到空数组或 key 不匹配打开浏览器控制台查看接口返回值Spark Session 启动失败没装 Java 或JAVA_HOME未配置安装 JDK 并配置环境变量或者跳过 Spark 路径只跑 pandas5.3 把这些参数改成你自己的如果想把系统复用到其他城市或其他岗位改三个地方就够conf.ini里的keyword和city、爬虫页面的 CSS 选择器、server.py里的初始城市默认值。关键词改成Java开发、城市改成上海后重新执行爬虫到分析的四条命令图表数据会自动更新不需要动任何前端代码。如果爬虫目标站结构变化太大也可以把 CSV 换成手动整理的 Excel 数据只要字段名保持一致后面的分析展示链路完全不用改。这是这套毕业设计最大的可扩展点——爬虫、分析、展示三段解耦改任何一段都不影响另外两段运行。本文还有配套的精品资源点击获取
返回列表