首页/新闻资讯/正文详情

招聘网站爬虫与可视化:Requests与Playwright双轨采集实战

发布时间:2026/9/23 17:44:19 来源:云帆数科 栏目:资讯中心
招聘网站爬虫与可视化:Requests与Playwright双轨采集实战
简介基于Python的招聘网站爬虫及可视化毕业设计源码与演示PPT面向计算机相关专业学生、毕设开发者及爬虫与数据可视化初学者。项目以招聘平台为目标通过Requests库完成数据采集存入MySQL数据库再利用Echarts在前端呈现饼图、直方图、折线图、扇图等综合图表覆盖数据采集、清洗入库、接口交互、可视化展示全链路。压缩包共59个文件、10.34MB主要包含9个py源码与12个pyc编译文件、7个xml配置、2个sql数据库脚本、多张png/jpg效果图以及js/css/html前端资源、pptx演示文稿和说明文档结构清晰便于学习和二次开发。已有919人学习下载适合作为毕业设计、课程作业或项目初期立项演示参考代码全部测试通过可在此基础上扩展新功能深入理解招聘数据获取与可视化分析的实际流程。1. 招聘网站爬虫与可视化毕业设计选题里最容易出彩也最容易翻车的技术链路每年到了毕业设计季“基于Python的招聘网站爬虫及可视化”都是出镜率最高的题目之一。定题容易答辩难很多同学拿到的演示PPT精美得像产品发布会但一运行源码就卡在登录墙、IP封禁或者反爬校验上。这个题目真正考验的不是“会不会写爬虫”而是“能不能把数据完整取回来又能把取回来的数据讲清楚”它天然横跨了网络请求、页面解析、数据清洗、存储和前端展示整条技术栈也是少有的能把作品填满一个展厅的选题。接下来不按“项目介绍”的思路讲直接按一套可复现的落地路径拆数据源结构怎么读、抓取策略怎么定、可视化怎么接数据以及答辩现场怎么演示才不翻车。2. 先识别招聘网站的页面结构与反爬边界再决定用 requests 还是 playwright2.1 招聘网站的数据长在三个不同的地方抓取策略完全不同常见的招聘站点数据分散在三个层级搜索列表页、职位详情页、以及公司主页。搜索列表页通常返回大量职位摘要包括职位名称、薪资范围、城市、公司名、经验要求。职位详情页则包含完整的岗位描述、福利标签、技能要求、HR信息。公司主页展示公司规模、融资阶段、团队构成这些数据对后期的可视化分析很有价值。列表页拿到的数据适合做宏观分析比如“全国Java岗位薪资分布”“北上广深招聘数量对比”详情页适合做技能图谱分析比如“一线城市后端开发最常要求的前5项技能”。如果毕业设计只做列表页数据维度太薄直接会被答辩老师问住。2.2 常见反爬手段与应对选型requests 与 playwright 双轨并行成熟的招聘网站几乎都上了反爬较常见的手段是以下三种服务端校验User-Agent和Cookie缺失或非浏览器的请求会被直接拒绝。基于IP的访问频率控制短时间超过阈值返回验证码或封禁一段时间的请求。页面数据由JavaScript动态加载直接GET返回的空壳HTML里没有职位数据。针对以上三个问题requests可以处理第一种和部分第二种只要做好header伪装和请求频率控制。但面对动态加载的页面requests需要先找XHR接口找到接口还得跟上加密参数成本陡增。playwright可以启动完整浏览器执行JavaScript让页面自行渲染后再提取DOM很多前端加密甚至不需要逆向。所以我一般建议双轨并行先requests对公开接口请求如果发现返回内容里没有有效数据再降级到playwright走浏览器渲染。普通情况下的选型参照 | 数据场景 | 推荐方式 | 理由 | |------------------------------|--------------|----------------------------------------| | 静态列表页有接口可预测 | requests | 速度快资源占用低易控制频率 | | 动态渲染无接口或密文难解 | playwright | 浏览器直接渲染绕开大部分JS加密 | | 需要登录后可见的数据 | playwright | 可自动维护登录态处理Cookie更方便 | | 大量数据但只取结构化字段 | requests | 方便配合pandas做后续清洗 | | 验证码出现频繁 | playwright | 配合识别服务或人工介入窗口更灵活 |2.3 设置合理的robots策略与抓取边界避免因爬虫惹上法律风险这一点毕业设计尤其要重视。虽然学习性质的项目一般不会被追诉但答辩时一定会问“你是否考虑过合规性”。在爬虫代码中主动配置User-Agent、设置抓取间隔、不抓取个人隐私字段如手机号、简历内容是基本素养。招聘网站的用户协议通常会注明“未经许可不得采集数据”所以演示时最好使用自己的测试数据或小众公开站点避免使用大型招聘平台的线上真实数据做并发压测。我一般会先抓几百条作为演示样本说明这个链路是通的然后把本地的SQLite或CSV作为可视化数据来源这样既保证演示顺畅也避免不必要的法律风险。3. 用 requests 拿列表数据用 playwright 补详情页双轨抓取的核心代码3.1 先实现基础请求模块给爬虫一个稳定的入口一个完整的爬虫工程至少要包含三个模块请求模块、解析模块、存储模块。请求模块负责发请求并处理失败重试解析模块从HTML或JSON中提取结构化字段存储模块负责去重和落盘。先看请求模块的实现这部分不复杂但决定了整个爬虫的稳定性。import requests import time from fake_useragent import UserAgent class JobSpiderBase: def __init__(self, base_url, max_retries3, timeout10): self.base_url base_url self.max_retries max_retries self.timeout timeout self.session requests.Session() self.ua UserAgent() def _get_headers(self): # 每次请求随机生成浏览器身份避免被统一特征识别 return { User-Agent: self.ua.random, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: self.base_url } def fetch(self, url, paramsNone): for attempt in range(self.max_retries): try: resp self.session.get( url, paramsparams, headersself._get_headers(), timeoutself.timeout ) if resp.status_code 200: return resp # 429或403表示频率受控做指数退避 if resp.status_code in (403, 429): wait_time 2 ** attempt 1 time.sleep(wait_time) except requests.RequestException as e: time.sleep(2 * (attempt 1)) return None参数说明max_retries控制每个请求的最大重试次数设置3比较合适太少遇到网络抖动就失败太多会放大异常请求量。timeout设置10秒超过即放弃避免单请求拖死整个任务。fake_useragent库生成随机的UA注意从这里取到的UA可能偶尔包含旧版本浏览器标识如果遇到被识别可以手动指定一个常用UA字符串比如Chrome 122的完整UA。3.2 解析列表页用解析器提取字段而不是正则硬抠拿到HTML后大多数人的第一反应是写正则在源码里找数据我建议直接用parsel或BeautifulSoup做提取。parsel基于lxml对复杂HTML的容错性更好XPath选择器在Python生态中与Selenium和Playwright通用学一次能用在多处。from parsel import Selector def parse_job_list(html: str) - list[dict]: selector Selector(texthtml) job_items selector.xpath(//div[contains(class, job-item)]) jobs [] for item in job_items[:50]: title item.xpath(.//span[contains(class, job-name)]/text()).get() salary item.xpath(.//span[contains(class, salary)]/text()).get() company item.xpath(.//a[contains(class, company-name)]/text()).get() location item.xpath(.//span[contains(class, location)]/text()).get() # 清洗薪资字符串方便后续做数值分析 salary_range parse_salary(salary) jobs.append({ title: title.strip() if title else , salary_min: salary_range[0], salary_max: salary_range[1], company: company.strip() if company else , location: location.strip() if location else , }) return jobs def parse_salary(salary_text): # 15-25K·14薪 先拆分主区间再按 K/M 计算数值 import re if not salary_text: return (0, 0) match re.search(r(\d)-(\d), salary_text) if not match: return (0, 0) low, high map(int, match.groups()) if K in salary_text or k in salary_text: return (low * 1000, high * 1000) if M in salary_text or m in salary_text: return (low * 10000, high * 10000) return (low, high)这段代码中salary_min和salary_max统一换算成了月薪数值后续做直方图和箱线图时非常方便。很多同学把薪资存成字符串“15-25K”到可视化阶段就卡住了还得回头重新清洗。在爬虫阶段就完成数据规格化是减少返工的关键。3.3 登录墙和动态页面用 playwright 兜底当requests拿不到数据时切换到playwright同步模式。需要说明的是playwright可以设置headlessFalse跑一遍登录流程或者直接加载已有登录态的浏览器上下文环境。下面的代码演示了如何加载本地持久化登录态并抓取动态渲染的职位详情。from playwright.sync_api import sync_playwright def scrape_detail_with_playwright(url: str, storage_state_path: str) - str: with sync_playwright() as p: browser p.chromium.launch(headlessTrue) context browser.new_context(storage_statestorage_state_path) page context.new_page() # 如果目标网站需要登录信息访问详情页这里先尝试本地已有的状态文件 page.goto(url, wait_untildomcontentloaded, timeout30000) # 等待关键内容渲染完成 page.wait_for_selector(div.job-sec-text, timeout15000) html page.content() browser.close() return htmlstorage_state_path是playwright浏览器上下文导出的登录态JSON文件可以先执行一次手动登录脚本生成。wait_untildomcontentloaded只是DOM解析完成不代表异步数据全部渲染所以还需要wait_for_selector等待具体元素出现。这个等待目标的选择很重要我一般选详情页中纯前端渲染且一定会出现的元素比按秒sleep更稳定。3.4 断点续爬让爬虫在中断后不从头开始长任务的爬虫最容易遇到的问题是跑了十页后IP被封重启又从第一页开始。解决这个问题的标准做法是维护一个已爬取URL集合持久化到本地文件或者记录当前页数。这里用最轻量的方式实现import os import json class CrawlCheckpoint: def __init__(self, checkpoint_filecrawl_state.json): self.checkpoint_file checkpoint_file self.state self._load() def _load(self): if os.path.exists(self.checkpoint_file): with open(self.checkpoint_file, r, encodingutf-8) as f: return json.load(f) return {visited_urls: [], current_page: 0} def is_visited(self, url): return url in self.state[visited_urls] def mark_visited(self, url): self.state[visited_urls].append(url) self._save() def _save(self): with open(self.checkpoint_file, w, encodingutf-8) as f: json.dump(self.state, f, ensure_asciiFalse, indent2)current_page可以在每次翻页后递增并保存visited_urls用列表存储数据量大了以后改成set再序列化或者直接用SQLite存储更高效。毕业设计的规模一般在几百到几千条数据JSON文件足够。4. 数据清洗与可视化全流程从pandas整理到ECharts大屏展示4.1 数据清洗的三个关键点去重、补全、转数值类型爬虫落盘的数据不能直接用于可视化这是很多人忽视的。职位数据中常见的问题包括同一职位在多个列表中重复出现、薪资字段解析失败返回(0, 0)、部分字段缺失导致图表出现空值。清洗阶段我一般用pandas分三步处理。import pandas as pd df pd.read_csv(jobs.csv) # 1. 按公司名职位名去重保留最新记录 df.drop_duplicates(subset[company, title], keeplast, inplaceTrue) # 2. 过滤掉薪资解析失败的数据 df df[(df[salary_min] 0) (df[salary_max] 0)] # 3. 将城市字段标准化例如 北京-朝阳区 - 北京 df[city] df[location].str.split(-).str[0] # 4. 增加平均薪资字段便于后续绘图 df[salary_avg] (df[salary_min] df[salary_max]) / 2 print(df.shape)drop_duplicates中的keeplast是保留后出现的记录在爬虫场景下后出现的记录通常更完整。str.split(-).str[0]将带区县的字符串截断为城市级避免“北京”和“北京-海淀”在后续分组统计中被当作两个类别。4.2 存储到SQLite让数据和可视化工程解耦把清洗后的数据写入SQLite是一个稳妥的选择。SQLite无需独立服务文件即可携带答辩演示时可以直接把整个项目目录拷到任何机器上运行。写入时要建立合理的索引。import sqlite3 import pandas as pd df.to_sql(jobs, sqlite:///jobs.db, if_existsreplace, indexFalse) # 创建索引加速后续的聚合查询 conn sqlite3.connect(jobs.db) conn.execute(CREATE INDEX IF NOT EXISTS idx_job_city ON jobs(city)) conn.execute(CREATE INDEX IF NOT EXISTS idx_job_salary ON jobs(salary_avg)) conn.commit() conn.close()这里使用SQLAlchemy的连接字符串sqlite:///jobs.dbpandas的to_sql方法内部可以识别。如果不想引入SQLAlchemy也可以直接传一个sqlite3连接对象。CREATE INDEX会在数据量增长后显著加快分组查询速度对演示中的交互式筛选有实际效果。4.3 Flask提供数据接口前端用ECharts渲染可视化部分不需要重逻辑。后端用Flask开一个轻量接口按城市返回聚合结果前端通过fetch拉取JSON渲染柱状图或饼图。这个方案比纯静态HTML数据文件更接近真实项目结构答辩时也更容易讲解前后端交互逻辑。from flask import Flask, jsonify import sqlite3 import pandas as pd app Flask(__name__) app.route(/api/salary_by_city) def salary_by_city(): df pd.read_sql_query( SELECT city, ROUND(AVG(salary_avg), 0) AS avg_salary, COUNT(*) AS job_count FROM jobs WHERE salary_avg 0 GROUP BY city ORDER BY job_count DESC LIMIT 20 , sqlite:///jobs.db ) data { cities: df[city].tolist(), avg_salaries: df[avg_salary].tolist(), job_counts: df[job_count].tolist() } return jsonify(data), 200, {Access-Control-Allow-Origin: *} if __name__ __main__: app.run(debugTrue, host127.0.0.1, port5000)接口返回结构固定为三个列表前端拿到后直接交给ECharts的dataset组件不用在JS里做二次字段映射。LIMIT 20限制返回城市数量避免小城市数据量太少导致图表杂乱。4.4 前端可视化大屏一个页面放下四个核心图前端页面布局分成四块左上角用柱状图展示各城市平均工资右上角用饼图展示职位学历要求占比下左侧展示技能关键词词云下右侧展示各城市招聘数量热力地图。这里以核心的柱状图和饼图为例。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title招聘数据分析看板/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script /head body div idbarChart stylewidth: 45%; height: 400px; display: inline-block;/div div idpieChart stylewidth: 45%; height: 400px; display: inline-block;/div script fetch(http://127.0.0.1:5000/api/salary_by_city) .then(res res.json()) .then(data { var barChart echarts.init(document.getElementById(barChart)); barChart.setOption({ tooltip: {}, dataset: { source: [data.cities, data.avg_salaries] }, xAxis: { type: category, data: data.cities }, yAxis: { type: value, name: 平均薪资元/月 }, series: [{ type: bar, data: data.avg_salaries, itemStyle: { color: #5470c6 } }] }); var pieChart echarts.init(document.getElementById(pieChart)); var degreeData data.cities.map((city, index) ({ name: city, value: data.job_counts[index] })); pieChart.setOption({ tooltip: { trigger: item }, series: [{ type: pie, radius: 60%, data: degreeData }] }); }); /script /body /html这段代码中ECharts的dataset组件可以接受二维数组或对象数组直接传入API的返回值即可。注意饼图的数据如果设置成城市与岗位数量维度其实是“城市岗位量占比”如果想展示学历要求需要后端再加一个/api/degree_ratio接口逻辑完全一致改一个GROUP BY字段就行。5. 答辩演示前必须做的三件事数据快照、离线运行、讲解顺序答辩环境的网络状态很不稳定尤其在学校机房外网访问可能受限CDN资源无法加载这会直接导致可视化看板白屏。因此演示前必须做本地化处理。把ECharts的JS文件下载到本地static/js/目录引用路径改为相对路径接口改成读取本地JSON文件而不是浏览器实时请求Flask。这两种模式要并存一种是“爬虫-接口-大屏”的实时联动模式另一种是“本地JSON-大屏”的离线保障模式。在答辩讲稿的编排上不要先演示爬虫因为爬虫运行需要时间且页面加载速度容易显得拖沓。先展示可视化结果让几张有冲击力的图表抓住老师的注意力然后再切到代码逐模块解释爬虫逻辑。这个顺序是面试和答辩中都被验证过比较实用的策略。可视化界面可以在最后补一个“刷新后看数据变化”的交互操作用echarts的setOption实现下钻筛选。这个细节会让整套系统显得完整度更高。本文还有配套的精品资源点击获取

相关推荐

Word页码重置:分节符与域代码实现当前页设为第一页
Word页码重置:分节符与域代码实现当前页设为第一页

1. 从“当前页面设置第一页”说起:一个被低估的高频需求“word当前页面设置第一页”这个搜索词,我在后台数据里见过太多次了。表面上看它只是问“怎么把某一页设成第一页”,但真正动手做过文档排版的人都知道,这背后藏着一整套关于… · 2026/9/23 17:44:19

HPC高性能计算架构设计:从计算节点选型到Slurm调度与InfiniBand网络调优
HPC高性能计算架构设计:从计算节点选型到Slurm调度与InfiniBand网络调优

简介:这份文档面向高性能计算领域的架构师、运维工程师及科研计算人员,系统梳理HPC集群从基础概念到落地设计的完整知识框架,帮助读者理解如何构建满足科研与工程需求的高速计算环境。资源包内含1个docx文档,约979KB,内… · 2026/9/23 17:44:12

免费下载高清视频素材的合法渠道与实用技巧
免费下载高清视频素材的合法渠道与实用技巧

1. 先别急着下载:很多人一开始就把方向搞错了刷到“免费下载高清视频”这个需求的时候,我其实挺感慨的。以前总有人私信问我“哪儿能免费下电影”“某某视频用什么工具能扒下来”——说实话,这些问题我基本不爱回答,不是装清高&am… · 2026/9/23 17:44:12

PX4 AI 辅助贡献规范:作者身份、披露与提交合规指南
PX4 AI 辅助贡献规范:作者身份、披露与提交合规指南

嵌入式物联网机器人自动驾驶智能硬件 【免费下载链接】PX4-Autopilot PX4 Autopilot Software 项目地址: https://gitcode.com/gh_mirrors/px/PX4-Autopilot 点击查看 免费下载 本指南围绕 PX4-Autopilot 仓库中的 AI 辅助贡献官方政策,系统讲解使用 AI… · 2026/9/23 18:14:28

上海专升本-家长反复问的一句话:你们机构背后到底是谁?
上海专升本-家长反复问的一句话:你们机构背后到底是谁?

一句话结论:孩子备考专升本要花两三年,家长首先要核实的一件事是机构背后是谁、有没有平台与师资保障。上海临港产业大学(指尖专升本)由临港集团与临港五校共同发起设立的平台大学承载升学服务,作为校企服务部的学历提… · 2026/9/23 18:14:28

2026上海专升本招生计划解读:7984人、14所公办+6所民办,怎么选不亏
2026上海专升本招生计划解读:7984人、14所公办+6所民办,怎么选不亏

一句话结论:2026年上海统招专升本招生7984人,比2025年的6905人扩招约15.63%,但报考人数增长更快;选校的本质是三件事——专业对不对口、考纲教材看哪一版、往年录取分数与计划数怎么变。一、先看2026年的三个数字口径2026年2025年… · 2026/9/23 18:14:28

OpenDSS与MATLAB联合仿真平台设计:输配电网协同分析实践
OpenDSS与MATLAB联合仿真平台设计:输配电网协同分析实践

简介:面向输配电系统建模与仿真需求,这份MATLAB与OpenDSS联合仿真平台资源包,适合电气工程、计算机、电子信息及数学等专业的学生用于课程设计、期末大作业或毕业设计。平台依托OpenDSS对电力系统的专业仿真能力,并结合MATLAB的数… · 2026/9/23 18:14:22

Fn键本质是硬件级键位映射切换开关
Fn键本质是硬件级键位映射切换开关

1. Fn键不是“隐藏功能”,而是被系统刻意设计的交互分层机制Fn键,全称Function Key,中文常被叫作“功能键”或“组合键开关”,但它既不是快捷键,也不是传统意义上的修饰键(Modifier Key)——它和… · 2026/9/23 18:14:03

5个坑搞定盛大网络热血传奇官网性能优化
5个坑搞定盛大网络热血传奇官网性能优化

5个坑搞定盛大网络热血传奇官网性能优化 看了一堆教程还是不会写项目?别慌,这不是你的错,是教程太“理想化”了。很多老手在 掘金技术社区… · 2026/9/23 18:13:57

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码