恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Python爬取Boss直聘大数据岗位并进行数据清洗与可视化分析

  • 首页
  • 资讯中心
  • /
  • Python爬取Boss直聘大数据岗位并进行数据清洗与可视化分析

相关资讯

OpenProject 8.3.2 安全更新:CVE-2019-11600 Activities API SQL 注入漏洞分析与升级指南 2026/9/17 6:09:09
IntelliJ Platform 模块拆分实战:将可选依赖抽取为独立 Content Module 的完整指南 2026/9/17 6:09:09
MongoDB WiredTiger 并行代码覆盖率测量:gcov/Gcovr 多构建目录并行测试与报告合并实践 2026/9/17 6:09:09

最新资讯

WPF+腾讯云OCR实现精准区域识别与结构化重命名
PVE 7.2-1 安装全流程:硬件、文件系统、网络与首台虚拟机
MATLAB口罩检测GUI系统:CNN分类+人脸ROI实现双击运行
无人机载激光雷达地形测绘:点云生成、滤波与DEM精度评定
硅半导体掺杂:施主受主、N/P型、反掺杂与离子注入
VSCode + SAPUI5 + Fiori Tools:SAP Fiori本地开发环境搭建与Hello World实战

今日推荐

每日热评|13% 的 Agent 技能带严重漏洞,这个注册表想用“验证+签名”解决信任危机
即梦AI保姆级教程:从生图到数字人,一站式搞定AI视频创作
BERT+LLM混合架构:突破NER长尾实体抽取瓶颈的工程实践

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

Python爬取Boss直聘大数据岗位并进行数据清洗与可视化分析

发布时间:2026/9/17 6:09:10
Python爬取Boss直聘大数据岗位并进行数据清洗与可视化分析 简介这是一个面向爬虫与数据分析入门学习者的综合性实战项目以北京市大数据岗位招聘数据为具体对象串联起从网络数据采集、网页解析、数据清洗、预处理、统计聚合到可视化图表展示的完整分析链路。项目中的爬虫部分演示了如何请求网页并处理动态内容数据分析部分从平均薪资、学历要求、经验年限、职位供需关系等维度展开统计与解读前端部分则通过可视化页面让数据结论更直观。资源包共111个文件核心包括4个Python源程序、原始数据表格文件、数据库文件以及20余个网页版可视化页面和统计图表图片并配有页面样式与交互脚本等前端资源整体大小仅6.58兆目录结构适合按阶段参考学习。此项目已有726人学习下载对于希望构建数据分析项目经验、完成课程作业或毕业设计的读者是一份从代码到数据再到展示形态都很完整的参考资源。1. 北京大数据岗位招聘数据为什么值得用Python爬一遍也许你在Boss直聘上搜“大数据”岗位时会看到一个几十页的列表每页都有不同的薪资、经验、技能要求。想弄清“北京哪个区职位最多”“3年经验能拿多少钱”这类问题手动翻页根本做不到。这个项目把整条链路打通用Scrapy爬取Boss直聘北京站的大数据相关职位requests和BeautifulSoup做辅助解析pandas清洗出结构化表再用matplotlib与seaborn把结果画成图最终打包成一套可部署的HTML可视化页面。无论你是正在准备大数据方向毕业设计的学生还是想跳槽前摸清市场行情的开发者都能直接复用这份代码。它给出的不是一张静态报表而是一份随时可以重跑、调整口径的岗位数据分析工具。整个项目涉及爬虫、数据清洗、特征提取、可视化和数据库存储正是目前大数据岗位最常要求的技能组合。2. Scrapy requests 双通道采集从HTML到结构化的第一公里招聘网站不像开放API一样提供干净数据爬虫的职责是把网页变成结构化字段。这个项目里其实有两条取数路径一条是Scrapy框架适合跑批量任务另一条是requestsBeautifulSoup用于快速分析单个页面的DOM结构。我的建议是主爬虫走Scrapy因为它的调度器自带URL去重和并发控制而requests适合在调试阶段用。2.1 为什么用Scrapy而不是自己写requests循环自己写requests循环并不难但要处理的问题很多重复请求怎么去重请求失败重试几次并发量怎么控制日志怎么记录。Scrapy把这些基础设施都做好了。它的Request对象会进入调度器相同指纹的URL默认丢弃DownloaderMiddleware统一给每个请求附加UA和CookieItem Pipeline把解析结果按顺序清洗、去重、入库。对于Boss直聘这种有反爬策略的站点Scrapy的RetryMiddleware还能在遇到503或验证码时自动延迟重试省去自己写异常处理的代码。从实际效果看用Scrapy写一个Spider代码量大约是自己写requests循环的一半。而且项目里需要抓多个搜索词“数据挖掘”“数据分析”“大数据开发”Scrapy可以通过start_urls或start_requests轻松生成多任务。这里有一个最小Spider示例# spider.py import scrapy from jobs.items import JobItem class JobSpider(scrapy.Spider): name jobs start_urls [ https://www.zhipin.com/web/geek/job?query大数据city101010100, https://www.zhipin.com/web/geek/job?query数据挖掘city101010100, ] def parse(self, response): for card in response.css(.job-card-wrapper): item JobItem() item[job_title] card.css(.job-name::text).get() item[salary] card.css(.salary::text).get() item[company] card.css(.company-name::text).get() item[info] card.css(.job-info span::text).getall() yield item说明job-card-wrapper是职位卡片容器的class不同时期Boss直聘的页面结构会变需要先用浏览器开发者工具确认。info字段用getall()返回的是学历、经验、标签的列表后面在Pipeline里用,.join()合并。如果想单独调试某个页面可以用requests获取HTML后用BeautifulSoup跑选择器调试通过再写回Scrapy。字段CSS选择器处理方式job_title.job-name::text直接提取文本salary.salary::text原始字符串后处理company.company-name::text去空格experience.job-info span::textgetall()后拼接district.job-area::text可能为空需回退2.2 Item Pipeline与字段预处理Scrapy把“解析”和“存储”分开。Spider只负责提取原始文本Pipeline负责清洗。比如info字段可能是[3-5年,本科,北京朝阳区]在Pipeline中按位置拆开。示例# pipelines.py class CleanPipeline: def process_item(self, item, spider): info item.get(info, []) if len(info) 3: item[experience] info[0] item[education] info[1] item[district] info[2] else: item[experience] item[education] item[district] # 技能标签需要从职位描述中提取这里先留空 item[skills] return item这个Pipeline把列表字段拆分成了结构化字段同时处理了解析不到数据的异常情况。为什么不直接在Spider里拆因为Pipeline更便于做数据校验和日志记录而且如果未来改用爬虫API逻辑可以复用。2.3 反爬参数请求头、下载延迟与Cookie策略Boss直聘对无头爬虫的识别比较敏感。我一般会在settings.py里做三件事设置DOWNLOAD_DELAY 1.5让请求间隔至少1.5秒启用Scrapy自带的RandomUserAgentMiddleware并准备一个几十组UA的列表在请求头中携带从浏览器复制的Cookie避免触发登录跳转。代码# settings.py DOWNLOAD_DELAY 1.5 RANDOM_UA_TYPES [chrome, firefox] COOKIES_ENABLED False # 改为False手动在headers放Cookie DEFAULT_REQUEST_HEADERS { User-Agent: Mozilla/5.0 ..., Cookie: your_cookie_here, Referer: https://www.zhipin.com/, }注意COOKIES_ENABLED False可以避免Scrapy的Cookie处理器干扰手动Cookie。如果还遇到验证码就要检查是不是DOWNLOAD_DELAY太小或者需要代理IP。项目里附带的数据是已经爬完的结果所以实际运行时可以先拿少量页面测试确认稳定后再全量抓取。3. pandas 数据清洗与薪资解析把“15-30K·14薪”变成数字爬下来的是原始文本要做分析必须先清洗。这一章用pandas完成三个核心任务去重、缺失值处理、把“15-30K·14薪”拆成数字字段。这三个操作占整个数据预处理工作量的70%。3.1 重复数据与缺失值先看再决定读入CSV后不要急着删行先用df.info()和df.describe()了解数据概况。重复行往往来自分页爬取时的重复记录例如同一职位出现在“大数据”和“数据分析”两个搜索词下。可以用subset指定精确去重import pandas as pd df pd.read_csv(jobs_raw.csv) print(df.shape) df df.drop_duplicates(subset[job_title, company, salary]) print(f去掉重复后剩余 {df.shape[0]} 行)为什么用这三个字段做subset因为岗位名称、公司、薪资组合起来几乎能唯一标识一条招聘广告比单用job_title更严格又比全行列去重更宽容能保留不同职位的相似文本。缺失值看df.isnull().sum()。字段district如果缺失可能是Boss直聘的职位卡片没有展示区域信息可以暂时填“未知”salary缺失必须处理因为后续分析依赖薪资。3.2 薪资区间正则解析从字符串到数值薪资文本有多种变体“15-30K”“20-40K·14薪”“面议”。下面这段代码提取区间下限和上限并计算中位数对“面议”直接记为Noneimport re def split_salary(text): if not isinstance(text, str): return pd.Series([None, None, None]) m re.search(r(\d)-(\d)K, text) if m: low int(m.group(1)) high int(m.group(2)) mid (low high) / 2 return pd.Series([low, high, mid]) return pd.Series([None, None, None]) df[[salary_low, salary_high, salary_mid]] df[salary].apply(split_salary)apply(split_salary)在这里逐行处理返回的Series会自动对齐到新列。也可以改用zip(*df[salary].apply(split_salary))但可读性不如直接赋值。注意salary_mid用于画箱线图因为它比“最低薪资”“最高薪资”更稳定能代表招聘方的平均报价。如果还想计算“14薪”的影响可以再加一个函数re.search(r(\d)薪, text)提取薪期然后salary_mid * months / 12折算成月均。不过多数岗位描述里的薪资已经是月薪所以这一步可选。3.3 学历与经验编码把文本变成可排序的数值学历文本一般是“大专”“本科”“硕士”“博士”用map做有序编码。经验文本有“1-3年”“3-5年”“5-10年”提取最低年限作为特征。edu_map {大专: 1, 本科: 2, 硕士: 3, 博士: 4} df[edu_code] df[education].map(edu_map) def extract_exp(text): m re.search(r(\d), text) if isinstance(text, str) else None return int(m.group(1)) if m else None df[exp_year] df[experience].apply(extract_exp)这里把学历从定类变量转成定序变量便于后面做相关性分析经验年限直接用数字表示能跟薪资做散点图。需要留意map遇到未识别值会返回NaN比如“学历不限”这时可以根据分析目标填0或删除。清洗后的数据表格如下原始salarysalary_lowsalary_highsalary_midedu_codeexp_year15-30K·14薪153022.52320-40K·15薪204030.035面议NaNNaNNaNNaNNaN经过这步原始数据已经从“能看”变成“能算”。下一步所有图表都基于salary_mid、edu_code、exp_year这几个数字列。4. matplotlib seaborn 可视化用图表回答“北京需要什么样的大数据人才”数据清洗完下一步是可视化。项目里用matplotlib和seaborn生成静态图再嵌入到HTML模板中展示。可视化不是随便画几个柱状图而是要回答具体问题哪个区岗位最多薪资和年限怎么变技能要求集中在哪些词上图表数据字段回答的问题区域分布柱状图district job_title岗位集中在哪个区经验薪资箱线图exp_year salary_mid经验对薪资的影响技能词频图skills 计数哪些技能最热门4.1 区域岗位数量分布直接对district做value_counts画水平条形图便于区名阅读。注意要先设置中文字体否则标题和坐标轴会变成方块。代码import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False district_counts df[district].value_counts().head(10) district_counts.sort_values().plot(kindbarh, figsize(10, 6)) plt.xlabel(岗位数量) plt.ylabel(区域) plt.title(北京大数据岗位区域分布 Top10) plt.tight_layout() plt.savefig(district.png)head(10)只取前10避免海淀区单个大柱子盖住其他区。sort_values()让条形图从低到高排列长条在下方视觉上更顺。保存成PNG而不是直接show是为了后续嵌入到HTML报告中。4.2 经验要求与薪资的箱线图箱线图能同时展示分布、中位数和异常值。这里用seaborn按exp_year分组0年、1年、3年、5年、10年画salary_mid的分布import seaborn as sns sns.boxplot(datadf, xexp_year, ysalary_mid) plt.xlabel(工作经验年) plt.ylabel(月薪中位数K) plt.title(不同经验要求下的薪资分布) plt.xticks(rotation45) plt.savefig(exp_salary_box.png)注意exp_year如果是连续的最好先离散化df[exp_group] pd.cut(df[exp_year], bins[0,1,3,5,10], labels[0-1,1-3,3-5,5-10])否则箱线图会把每个唯一的年数值都画一箱。离散化后能看到明显的阶梯效应1-3年到3-5年有一个大的薪资跃升这是分析报告中值得强调的发现。4.3 技能关键词词频提取职位描述里会写“熟悉Python、Spark、Hadoop”技能字段可能是从标签里抓的。简单的做法是用字符串计数先定义一个技能字典再对skills列做str.contains统计skills [Python, Spark, Hadoop, SQL, Java, Scala, Flink, Hive, Kafka] counts {s: df[skills].str.contains(s, naFalse).sum() for s in skills} counts pd.Series(counts).sort_values()这里naFalse让缺失值不参与匹配.sum()统计包含该技能的行数。把结果画成柱状图就能直观看到Python是绝对第一Spark与Hive紧随其后。如果想更精细可以用jieba分词后统计词频但对于这种预定义的技能清单直接匹配既快又不易出噪声。可视化部分的输出直接对接项目里的HTML页面bootstrap.css和aos.css只是给页面加样式和滚动动效真正承载数据的是这些PNG图和后面要讲的MySQL表。5. 数据导出与入库从CSV到MySQL的工程化细节分析结果要给别人用CSV文件不够友好。项目里把清洗后的数据导入MySQL这样可以用SQL做临时查询也可以供可视化后端调用。这一章记录三个关键点连接方式、类型映射、增量更新。5.1 用SQLAlchemy建立连接pandas的to_sql不直接接受原生连接需要通过SQLAlchemy的create_engine。示例from sqlalchemy import create_engine engine create_engine( mysqlpymysql://root:passwordlocalhost:3306/jobs_db?charsetutf8mb4 ) df.to_sql(bigdata_jobs, engine, if_existsreplace, indexFalse)charsetutf8mb4是关键否则职位描述里的中文表情或特殊字符会报错。if_existsreplace适合全量重建表如果只是追加改成append但前提是表结构一致。5.2 字段类型与索引设计pandas自动推断类型可能不准to_sql可以用dtype参数指定SQL类型尤其是字符串字段要显式设成VARCHAR或TEXT避免默认的VARCHAR(255)截断长文本。字段SQL类型说明job_titleVARCHAR(128)职位名salary_midFLOAT月薪中位数educationVARCHAR(16)学历原文exp_yearINT最低经验年限skillsTEXT技能标签同时在建表SQL里给job_titlecompany建立联合唯一索引配合INSERT ... ON DUPLICATE KEY UPDATE实现更新。5.3 增量更新用jobid做分批抓取爬虫每天跑一次不能每次都全量重抓。常见做法是每次抓取时带上当天日期或职位IDSQL中先SELECT已有的ID集合再对新增的插入。具体到Scrapy可以在Pipeline里维护一个已见ID的set如果item.get(jobid)已存在直接drop_item。这里有一个简单的去重Pipelinefrom scrapy.exceptions import DropItem class DuplicatePipeline: def __init__(self): self.seen set() def process_item(self, item, spider): jobid item.get(jobid) if jobid in self.seen: raise DropItem(f重复职位: {jobid}) self.seen.add(jobid) return item但这个seen只在单次爬取内有效重启爬虫后会丢失。更可靠的做法是先查MySQL里已有的jobid集合初始化到self.seen中实现“断点续爬”。在下一章我会专门讲怎么校验这套去重逻辑不会漏数。6. 让爬虫和数据管线可维护去重、断点续爬与质量校验项目交付不能只跑一次要能重跑、能检查数据有没有问题。这一章给三个具体技巧都是我在类似项目中会直接落地的。6.1 用jobid实现断点续爬与增量合并Boss直聘每一条职位详情页的URL里有唯一ID例如/job_detail/123456789.html。在Spider里把这个ID提取出来放到item中然后在Pipeline里用它做全局去重# pipelines.py import pymysql from scrapy.exceptions import DropItem class UniquePipeline: def __init__(self): self.conn pymysql.connect(hostlocalhost, userroot, password123456, dbjobs_db) self.seen self._load_seen() def _load_seen(self): with self.conn.cursor() as cur: cur.execute(SELECT jobid FROM bigdata_jobs) return {row[0] for row in cur.fetchall()} def process_item(self, item, spider): if item[jobid] in self.seen: raise DropItem(f已存在: {item[jobid]}) self.seen.add(item[jobid]) # 继续保存到MySQL... return item_load_seen在初始化时把已有ID装进集合内存占用不大每次新jobid插入后内存集合和数据库保持同步。这样即使爬到一半中断下次启动也会自动跳过已完成的部分。6.2 数据质量校验断言与阈值清洗完数据后我习惯写一段校验脚本不达标直接抛异常防止脏数据流入报表。这里用一个简单表格列出校验项和阈值检查项阈值说明salary_mid 缺失数 5%缺失过多则爬取或解析有问题重复比例0去重不彻底需重跑district 覆盖率 90%区域缺失影响地图可视化exp_year 最小值 0负值来自异常解析校验代码def validate(df): assert df[salary_mid].isna().mean() 0.05, 薪资缺失过多 assert df.duplicated(subset[jobid]).sum() 0, 存在重复jobid assert df[district].notna().mean() 0.9, 区域覆盖不足 assert (df[exp_year] 0).all(), 经验年限有负值 return True validate(df) print(f数据校验通过共 {df.shape[0]} 条记录)这些断言跑完之后再生成图表和HTML报告。项目里的bootstrap.css、aos.css这些文件都是报告页面的皮肤你完全可以用静态站点把这些图表和表单一页展示出来交给业务方或面试官当作作品集。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号