恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python爬虫实战:基于requests和pandas的51job招聘数据采集与增量更新分析
首页
资讯中心
/
Python爬虫实战:基于requests和pandas的51job招聘数据采集与增量更新分析
Python爬虫实战:基于requests和pandas的51job招聘数据采集与增量更新分析
发布时间:2026/9/1 17:36:32
简介本资源是一套面向Python初学者与爬虫实践者的多场景实战代码集聚焦招聘、影视、电商、婚恋及社交平台等真实网页的数据采集需求帮助学习者掌握requestsBeautifulSoup/PyQuery等主流技术栈的落地应用。压缩包共6个Python脚本文件总大小仅9KB轻量易读涵盖51job岗位信息与技能要求抓取、猫眼电影TOP100评分与票房数据采集、什么值得买商品评价分析、我主良缘用户画像采集、百度贴吧图片批量下载等典型任务每个脚本均体现请求伪装、HTML解析、数据清洗与结构化存储等关键环节。目前已有591人学习下载代码逻辑清晰、注释简明适合作为爬虫入门练习、课程设计参考或小型数据分析项目的快速启动模板尤其利于理解反爬策略应对与多源异构网页的通用解析思路。 如果你手动在51job上搜“python岗位”一页页翻下去、一遍遍复制粘贴进表格大概几十条职位就能让人烦躁到怀疑人生。而用Python写一个爬虫把搜索、翻页、解析、清洗、导出的流程全部自动化睡一觉起来就能得到一份结构化的招聘市场数据报表。这篇博文讲的就是这件事基于Python requests和pandas的51job爬虫从抓取搜索接口返回的JSON数据开始到字段清洗、数据入库、增量更新和简单分析完整给出可复现代码。适合刚学完Python基础、想用爬虫做点实际产出的读者也适合准备求职的人拿它摸摸市场行情。后面我会把抓包思路、字段清洗、反爬小心得和常见问题一并说清楚尽量少讲废话多给能直接用的东西。1. 项目整体设计与思路拆解1.1 这个项目要解决什么问题招聘信息本质上是一种高频更新的公开数据。对求职者来说最大的困扰不是“没有岗位”而是“岗位太多信息太碎”今天看到的岗位明天可能下架同样一个职位在不同渠道薪资范围差出一倍某个城市到底缺不缺人、缺什么人单靠肉眼翻页面根本没法形成整体判断。用爬虫抓取51job的数据就能把这些问题变成一张可以量化的表哪些公司在招python岗位、薪资区间集中在多少、学历门槛是什么、工作年限要求如何、岗位发布量的城市排名。如果坚持每天跑一次增量抓取还能观察到市场变化趋势。这个项目最适合的场景就是个人学习与求职决策脚本跑起来以后数据全部留在本地后续做分析、做可视化都方便。1.2 三种爬虫类型怎么选热词里常提到的“批量型爬虫、增量型爬虫、垂直型爬虫”其实正好对应这个项目能拆出的三层设计。批量型爬虫的目标是“把某个范围内能抓的数据尽量抓全”比如把51job上所有python相关职位翻完。它的特点是任务量大、请求密集对反爬策略要求高。增量型爬虫则是“只更新上次之后新增或变化的数据”比如每天定时跑一次把新发布的职位拿回来避免重复抓取浪费请求。垂直型爬虫则限定在某个特定领域或平台比如“只看51job、只看技术岗位”抓取范围窄但深度深。这个项目是三种类型的组合垂直限定在51job招聘平台对python关键词做批量翻页抓取同时通过职位ID去重实现增量更新。理解这个组合关系很重要因为写爬虫最大的忌讳就是“不知道自己在爬什么、为什么这么爬”方案选型决定了后面所有的代码结构。1.3 为什么数据源选51job而不是其他平台我选择51job不是因为它的页面好抓而是它的数据结构相对规范。51job的搜索页面虽然也有反爬但接口设计比较清晰搜索行为对应的是一条XHR请求返回的是结构化JSON字段命名相对统一对爬虫开发者非常友好。另外51job上的职位字段覆盖比较全岗位名称、公司名称、薪资、工作地点、经验要求、学历要求、发布时间、职位链接都有。这些字段对于后续的数据分析来说刚好够用不需要再去多个页面拼接。相比某些平台把数据藏在异步渲染的脚本里、或者字段忽多忽少51job省去了很多脏活累活。当然网站改版是常态接口参数可能会变我会在后面专门讲怎么抓包应对。2. 环境准备与工具选型2.1 基础环境Python版本和依赖安装这个项目对Python版本没有很苛刻的要求Python 3.8以上基本都能跑。我自己的环境是Python 3.10运行稳定。需要的第三方库只有四个requests发HTTP请求、pandas处理表格数据、beautifulsoup4和lxml备用解析方案。安装命令就一条pip install requests pandas beautifulsoup4 lxml如果你是在国内网络环境可以加清华镜像源加速pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests pandas beautifulsoup4 lxml如果你的机器上同时装了多个Python版本建议用虚拟环境避免依赖冲突。不过这个项目依赖很轻直接装到全局也问题不大。2.2 requests的地位为什么不用scrapy很多人一听到“爬虫”就想到scrapy框架觉得那才是正规军。但对于51job这种垂直场景我反而推荐requests。原因很简单scrapy是一个完整的爬虫框架有引擎、调度器、下载中间件、Item Pipeline一套体系适合大型分布式爬虫项目。但对这种“单机、单任务、数据量几千条”的小项目来说scrapy能带来多少收益呢几乎为零反而增加了学习成本和调试成本。requests的优势在于轻量和直观一个请求就是一个函数调用返回什么、解析什么全在代码里一眼能看明白。如果抓取接口后来改版了改起来也快。我个人的做法是先拿requests写一个能跑通的最小脚本确认数据能拿到、字段能解析如果后面业务量真的大到需要scrapy再迁移也不迟。爬虫的核心永远是“先把数据拿到”而不是“用什么框架”。2.3 headers和Cookie从哪里获取这是新手最容易卡住的地方。直接运行一个裸的requests.get到51job大概率拿不到数据因为服务器会验证请求头。正确的做法是模拟真实浏览器。第一步打开Chrome访问这个地址https://we.51job.com/pc/search?keywordpython这时页面上会出现python岗位列表。第二步按下F12打开开发者工具切到Network网络面板刷新页面。这时能看到一堆请求在筛选框里输入api或者search找到那个名字类似search-pc的XHR请求。第三步点击这个请求在Headers面板里找到Request Headers区块把User-Agent、Referer、Cookie这三个值复制出来。Cookie一般是一长串字符串可能包含tenantId、appId之类的参数全部复制即可。把这些信息填进Python代码的headers字典里请求就能通过服务端的校验了。需要注意Cookie是有时效的如果某天脚本突然失效大概率是Cookie过期重新复制一次就行。3. 核心细节解析与实操要点3.1 先抓包找到真正返回数据的接口从项目源码或者教程里直接复制一个URL风险很大。因为网站的接口随时可能调整你复制下来的URL可能是旧的可能已经被服务端禁用。所以必须学会自己抓包定位接口。以51job新版搜索页为例我在实际抓包中观察到的搜索接口是这样的GET https://we.51job.com/api/job/search-pc这个接口的完整参数很多但核心就这几个参数名含义示例值api_key接口标识固定为51job51jobtimestamp当前时间戳1700000000keyword搜索关键词pythonsearchType搜索类型2表示职位搜索2jobArea工作地区编码000000pageIndex页码从1开始1pageSize每页条数20keywordType关键词匹配类型0注意我这里的参数是基于当前观察到的快照。读者实际操作时应该以上面提过的F12抓包内容为准看Query String Parameters里实际传了什么参数然后一一对应到代码里。3.2 数据藏在HTML还是JSON早期51job的职位数据是直接嵌在HTML页面里的需要用requests抓到页面源码再通过BeautifulSoup或正则从一堆标签里扒数据非常痛苦。现在新版页面改成了前后端分离架构搜索列表的数据是通过XHR请求动态加载的接口返回的是JSON。JSON方案对爬虫来说是最舒服的不需要解析HTML标签直接定位字段键名就能取到数据。核心代码很简单data resp.json() job_list data[result][body] total_count data[result][total]其中body是一个列表里面每个元素就是一条职位记录字段名基本是jobName、companyName、salary、workArea、workYear、degree、issueDate这些。拿到这个结构以后后面所有的事都是围绕这个列表做数据清洗。我还想提一个细节即使接口返回JSON响应内容的编码也可能出问题。建议在调用.json()之前显式指定编码resp.encoding utf-8因为51job部分旧接口返回的是GBK编码如果requests自动检测错了就会出现乱码。指定编码是几行代码的事但能省掉很多排查时间。3.3 字段清洗薪资、城市、学历的坑不要指望原始字段能直接用招聘网站的字段有一种“看起来规范实际全是噪声”的特质。薪资字段salary常见的值有这些1.5-2.5万/月、200-300/天、3-4万/月、面议。如果要拿薪资做统计分析得先把字符串转成数字。我的做法是自定义一个解析函数统一转成“元/月”为单位的下限和上限。工作地点字段workArea一般是类似上海-浦东新区的格式我需要拆出城市名后面做城市分布统计时用。学历要求degree常见的值是本科、大专、硕士、学历不限这些可以直接做分类统计。经验要求workYear的值是3-4年经验或在校生/应届生处理方式类似。一个通用原则清洗函数只负责“把一个字段标准化”不要和抓取逻辑混合在一起。这样接口变动时只要改对应函数就行。3.4 数据存储CSV还是Excel抓下来的数据如果只放在内存里脚本一关就全没了。持久化方案我建议优先考虑CSV因为CSV大小小、编码清晰、Excel能直接打开pandas处理起来也方便。数据量不大的情况下用pandas的to_csv就能搞定df.to_csv(jobs.csv, indexFalse, encodingutf-8-sig)注意编码用utf-8-sig而不是utf-8否则用Excel打开CSV时会看到中文乱码。这是Windows环境的老坑提前说清楚。如果数据量到了几万条以上或者需要做更复杂的查询可以考虑SQLite。不过这个项目的典型数据量也就是几千到几万条CSV完全够用。4. 完整实操过程与核心环节实现4.1 单页抓取从构造请求到解析先写一个最小可用的抓取函数。假设你已经从浏览器复制好了headersimport requests import time headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://we.51job.com/pc/search?keywordpython, Cookie: 这里替换成你浏览器复制的完整Cookie } def fetch_jobs(keyword, page, page_size20): url https://we.51job.com/api/job/search-pc params { api_key: 51job, timestamp: str(int(time.time())), keyword: keyword, searchType: 2, jobArea: 000000, pageIndex: str(page), pageSize: str(page_size), keywordType: 0, } resp requests.get(url, headersheaders, paramsparams, timeout10) resp.encoding utf-8 data resp.json() if data.get(code) ! 200: print(请求失败:, data) return [] return data[result][body] jobs fetch_jobs(python, 1) print(len(jobs))运行后如果输出数字说明请求已经通了。如果返回空列表或报错优先检查三件事Cookie是否过期、pageIndex和pageSize是否传成字符串、接口路径是否因改版而变化。4.2 字段提取与保存拿到jobs列表后定义一个字段提取函数def extract(jobs): rows [] for item in jobs: row { 职位名称: item.get(jobName), 公司名称: item.get(companyName), 薪资: item.get(salary), 工作地点: item.get(workArea), 经验要求: item.get(workYear), 学历要求: item.get(degree), 发布时间: item.get(issueDate), 职位链接: item.get(jobHref), } rows.append(row) return rows然后用pandas一次性处理import pandas as pd df pd.DataFrame(extract(jobs)) df.to_csv(python_jobs_page1.csv, indexFalse, encodingutf-8-sig) print(df.head())看到Excel里出现中文标题、中文内容不再乱码单页抓取就算完成了。这一步是整个项目的“地基”后续所有功能都建立在这份最小的抓取和保存逻辑上。4.3 翻页循环与请求限速单页数据通常不够用。51job搜索接口默认每页20条python岗位至少有几百页但我不建议一口气抓完。翻页循环的写法很简单all_rows [] for page in range(1, 6): # 先抓5页测试 jobs fetch_jobs(python, page) rows extract(jobs) if not rows: break all_rows.extend(rows) print(f第{page}页累计{len(all_rows)}条) time.sleep(1 random.random() * 2) # 随机延迟1-3秒关键在这一行time.sleep(1 random.random() * 2)。这能让每次请求间隔具有随机性而不是固定的1秒。固定间隔容易被服务端识别为机器行为随机间隔更接近人工操作。我在实际测试中连续抓50页没有问题。但如果你把延时去掉连续快速请求几十次大概率会触发验证码。4.4 增量更新只抓新职位翻页抓取是“全量覆盖”每次从第一页抓到最后一页。但职位数据每天都在变如果每天都全量抓既浪费请求又会产生大量重复数据。增量更新的核心思路是每个职位都有一个唯一ID在返回数据里通常是jobId。我把所有已经见过的jobId存到一个本地JSON文件里每次请求后检查新数据里的jobId是否已存在只保留新增部分。import json import os seen_file seen_job_ids.json def load_seen(): if os.path.exists(seen_file): with open(seen_file, r, encodingutf-8) as f: return set(json.load(f)) return set() def save_seen(seen): with open(seen_file, w, encodingutf-8) as f: json.dump(list(seen), f, ensure_asciiFalse) seen load_seen() new_rows [] for page in range(1, 11): jobs fetch_jobs(python, page) for item in jobs: jid item.get(jobId) if jid and jid not in seen: seen.add(jid) new_rows.append(extract([item])[0]) time.sleep(1 random.random() * 2) save_seen(seen) print(f新增 {len(new_rows)} 条职位)这套逻辑第一次跑时会全量保存之后再跑就只输出新增内容。缺点是搜索接口返回的排序可能变化导致漏掉少量老职位但对个人学习场景完全够用。如果确实需要更严谨的增量可以配合职位详情页比对发布时间不过那是另一个项目的事了。4.5 薪资解析与简单分析增量数据积累几天后就可以做统计了。我经常做的是薪资分布分析。先把薪资字符串标准化import re def parse_salary(salary): if not salary or salary 面议: return None, None # 匹配 1.5-2.5万/月 / 200-300/天 / 3千-4千/月 m re.search(r([\d.])-([\d.])(万|千)?/月, salary) if m: low float(m.group(1)) high float(m.group(2)) unit m.group(3) if unit 万: low * 10000 high * 10000 elif unit 千: low * 1000 high * 1000 return low, high m2 re.search(r([\d.])-([\d.])/天, salary) if m2: # 按每月22个工作日估算 return float(m2.group(1)) * 22, float(m2.group(2)) * 22 return None, None然后把这个函数应用到DataFrame的一列生成薪资下限和薪资上限两列df[[薪资下限, 薪资上限]] df[薪资].apply(lambda x: pd.Series(parse_salary(x))) print(df.groupby(学历要求)[薪资下限].mean())这样就能得到“本科岗位平均薪资下限”“大专岗位平均薪资下限”之类的结论。虽然只是最简陋的统计但对求职参考来说已经比肉眼刷页面强太多了。4.6 定时任务让脚本自己跑抓取和分析都完善以后可以让脚本每天自动执行。Windows用“任务计划程序”macOS/Linux用crontab。crontab的写法很简单每天凌晨2点跑一次0 2 * * * cd /path/to/project /usr/bin/python3 main.py我在实际使用中还会让脚本在跑完后发送一个通知比如把新增职位数量发到邮箱或钉钉机器人。这里不展开具体代码但思路是main.py执行完后读取当次新增的new_rows长度再通过requests的POST请求调用到IM机器人的webhook即可。定时任务加通知爬虫才真正变成一个可用的信息助手。5. 常见问题与排查技巧实录5.1 请求返回空数据或者状态码429如果resp.json()返回的是{code: 429}说明请求频率太高触发了服务端的限流。429是HTTP中“Too Many Requests”的标准状态码服务器在明确告诉你你访问太多了请慢一点。应对方法按优先级排序第一把time.sleep的间隔拉长建议至少2到5秒第二检查是不是有多个循环在并行请求确保是串行第三如果确实需要更高频率考虑使用IP代理池但这对于个人项目通常不太值得。另一个常见情况是返回空列表但请求成功。这种情况要先看Cookie是不是过期了然后看keyword参数是否被URL编码比如中文“python”倒是没事但如果你搜“人工智能”需要在params里直接传中文requests会帮你编码这个没问题。真正可能出问题的是你自己拼接URL时忘记编码。5.2 数据乱码CSV用Excel打开出现乱码原因几乎总是编码问题。记住一个原则写入CSV时用utf-8-sig。如果你已经写入了utf-8编码的文件且不想重新爬可以用Notepad或VS Code打开文件另存为时选择“带BOM的UTF-8”也能解决。代码里的处理就一行df.to_csv(jobs.csv, indexFalse, encodingutf-8-sig)5.3 翻页只抓到第一页代码循环看起来没问题但翻到第2页返回的还是第1页的数据。这个问题通常是参数类型不对。服务端可能要求pageIndex作为字符串传入而你在params里传了整数有些接口会忽略掉或转成默认值1。把参数值统一转成字符串比如str(page)基本能解决。还有一种情况是接口的页码从0开始而不是从1开始比如第1页的pageIndex是0。这需要抓包确认不能想当然。5.4 触发验证码怎么办51job的验证码通常会以页面跳转或JSON错误的形式出现。遇到验证码第一反应不应该是想怎么破解而是要意识到访问频率太高了。我的处理顺序停掉脚本等待至少几分钟到半小时冷却后再跑然后大幅增加请求间隔如果是长期项目考虑把pageSize调大比如从20改成50用更少的请求获取相同的数据量。这样能从源头上减少请求次数。强调一个原则爬虫的目标是“优雅地获取公开数据”不是和网站对抗。降低频率、模拟人工操作、尊重服务端规则才是长期稳定的玩法。5.5 实操心得清单最后整理几条自己踩坑换来的经验每一条都是真金白银换来的。第一Cookie是爬虫的生命线但大多数人一开始不知道怎么拿。拿Cookie的方法上面讲了我这里只提醒一句千万不能把别人博客里抄来的Cookie硬塞进自己的代码那是别人的会话随时会失效而且也不安全。第二永远不要拼URL参数。requests的params参数会自动处理编码和特殊字符自己拼字符串很容易在中文关键词或特殊字符上出错。代码里多用params{keyword: keyword}少用url ?keyword keyword。第三数据清洗要尽早不要留到最后。我见过很多爬虫写到最后卡在清洗上就是因为早期把所有字段都当字符串存了没有在写入前做标准化。宁可多写几个清洗函数也不要囤积一堆脏数据。第四日志很重要。至少要在每个页面请求后把状态码、当前页码、返回条数打印出来。脚本一旦跑挂了没有日志就只能从头排查。我自己的习惯是加一行print(f[{time.strftime(%Y-%m-%d %H:%M:%S)}] page {page}, status {resp.status_code}, count {len(rows)})这样排查起来效率高很多。最后说几点个人体会这个爬虫项目写完之后我自己连续跑了一周每天晚上定时抓取51job上python相关岗位的新增数据。到周末统计的时候发现几个很有意思的现象薪资在2万以上的python岗位几乎都要求本科以上学历要求里带“智能体”“大模型”字样的岗位数量明显在增加而且这个细分方向的新增职位发布频率比其他方向高不少。单纯从找工作的角度说这些数据比招聘平台默认的推荐算法更能反映市场的真实需求。另外我建议你把增量得到的职位链接字段利用起来。每天新职位列表出来以后可以针对性地打开几个感兴趣的职位详情页看看JD里的技能要求会比被动等推送高效不少。爬虫只是手段把数据转化成求职优势才是目的。如果你想让这个项目更进一步可以考虑把抓取范围从“python”扩展到“Java”“前端”“数据分析”等关键词做一个多关键词对比分析也可以在每天晚上抓完后自动生成一份Excel图表报告发到自己的邮箱。爬虫项目的终点永远不是“能跑”而是“能用、能坚持用”。希望这篇博客能帮你写出自己的第一个招聘数据观察脚本。本文还有配套的精品资源点击获取