大二下学期,广州的南风天总是潮湿得让人有些胸闷。宿舍阳台的衣服晾了几天依然带着水汽,就像那段时间弥漫在楼道里的焦虑一样,挥之不去。身边的部分大三学长开始陆续修改简历,四处投递寻找暑期实习。在走廊和食堂里,经常能听到他们讨论面试的挫败和对未来的迷茫。
我在广州商学院,一所民办三本。客观来说,这里的招聘会通常不会有互联网大厂的身影,学校的课程体系也往往落后于业界最新的技术栈。当学长们还在期末大作业里为了 JSP 和老旧的 SSH 框架挣扎时,我心里产生了一个很务实的疑问:现在的真实市场上,到底在招什么样的人。
猜想没有意义,去问老师也未必能得到准确的答案。老师们的视野往往停留在学术界或是多年前的工程经验里。既然我是学软件工程的,不如直接去拿数据说话。于是我决定写一个爬虫,目标是当时招聘信息最活跃的 BOSS 直聘。我在 GitHub 上建了一个仓库,敲下 git init 的时候,给项目起名叫 bosszhipin_spider。
技术栈选得很稳妥,都是当时最成熟的基础工具。Python 作为主语言,网络请求交给 requests,页面解析用 BeautifulSoup,后续的数据处理用 pandas,最后的可视化结合了 matplotlib 和 FineBI。对于一个大二学生来说,这套组合足够轻量,也足够解决问题。
BOSS 直聘的反爬机制在当时不算坚不可摧,但对于刚接触爬虫和网络协议的我来说,依然像是一片布满暗礁的浅滩。代码写完第一次运行,看着终端里快速滚动的职位信息,我还没来得及高兴,屏幕上的输出突然停滞了。紧接着,一连串红色的 Traceback 打印出来,最后一行停留在 403 Forbidden。IP 被封了。
这是我踩的第一个坑,也让我学到了爬虫开发的第一课:拟人化。机器的请求是匀速且不知疲倦的,而正常人不会在一秒钟内翻阅十页招聘列表。我需要在代码里引入人类的随机性和疲惫感。
import time
import random

def crawl_with_delay(url):
# 模拟人类浏览的停顿,避免触发高频风控
time.sleep(random.uniform(1.5, 3.0))
resp = session.get(url, headers=headers)
return resp.json()
加上随机延迟后,爬虫活得久了一些,但很快遇到了第二个问题。爬着爬着,请求返回的不再是预期的 JSON 数据,而是重定向到登录页面的 HTML 源码。打开浏览器的开发者工具排查后发现,BOSS 直聘的 Cookie 会在一段时间后过期,而且服务器端对 session 的校验比较严格。为了解决这个问题,我不能再单纯地用一个无状态的 GET 请求去遍历页面,而是写了一个专门的模块来维护 session 状态。我用 requests.Session() 包装了请求,并定期模拟一些正常的页面跳转行为,比如随机访问一下首页或者企业详情页,以此来保持 Cookie 的活性。
最隐蔽的坑是数据加密。在抓取薪资和部分关键要求时,我发现 BeautifulSoup 从 HTML 源码里提取出来的字符,和我在浏览器上看到的不一样。浏览器上明明写着“15-20K”,抓下来的却是一些类似  的乱码或者毫无意义的特殊符号。
查阅了很多资料后,我才知道这是一种叫字体反爬的技术。服务器会动态生成一个 woff 字体文件,网页上显示的数字其实是自定义字体映射后的结果。为了拿到真实的薪资,我不得不偏离原定的开发进度,去研究前端的字体渲染机制。我增加了一个步骤,先通过正则匹配下载网页对应的 woff 字体文件,然后引入 fontTools 库解析出字符的 XML 映射表,建立一个本地的解密字典,最后再对抓取到的乱码进行还原解码。这个过程极其繁琐,光是找映射规律就耗费了我一个周末的深夜,但看着终端里终于打印出干净的阿拉伯数字时,我似乎对前端安全和攻防有了更底层的理解。
几天断断续续的运行后,我爬到了几万条真实的招聘数据。它们安静地躺在本地的 CSV 文件里,足足有十几兆。双击打开文件的那一刻是有成就感的,但这种感觉很快就被现实泼了冷水。
真实世界的数据是极其肮脏的。这是学校课本和那些经过精心修饰的实验课数据集里不会教的事情。
在 Jupyter Notebook 里做数据清洗,占据了这个项目大半的时间。单单是薪资字段就五花八门,有的是标准的“10-15K”,有的是“200/天”,有的是“15-20K·14薪”,甚至还有面议。我写了几十行复杂的正则表达式,配合 pandas 的 apply 和 lambda 函数,把这些非标准化的文本全部拆解,统一换算成了平均月薪的浮点数。技能标签更乱,同样是前端框架,HR 们的写法有“Vue”、“vue.js”、“VUE”、“熟悉vue”、“精通Vue3”等等。我不得不建了一个同义词映射字典,遍历整个 DataFrame,把这些噪音数据一一归一化。
当数据终于在 pandas 的 df.head() 里变得干净整洁,我把它们导进了 FineBI 和 matplotlib 里。图表渲染出来的那一刻,我看到了一些和学校认知不太一样的东西。
在前端岗位的分析图中,我发现了一个有趣的交叉点。学校里大家都在学 Vue,因为中文文档友好,容易上手,这也是很多小外包公司和接单团队的首选。但数据告诉我,在一线城市的中高级前端岗位,尤其是薪资处于前 30% 的区间里,React 的需求量已经悄悄超过了 Vue。企业愿意为 React 的生态、大型项目工程化能力以及背后的函数式编程思想支付更高的溢价。
在后端语言的对比中,Java 依然是岗位数量的绝对王者,占据了压倒性的饼图份额。但如果把视角切换到薪资分布的箱线图上,Python 相关岗位的薪资中位数却略微高出 Java 一截。仔细下钻数据后发现,纯写 Python Web 后端的岗位其实并不多,这些高薪岗位往往带有“数据分析”、“爬虫工程师”、“早期 AI 算法”等前缀。这或许是市场供需关系的一种体现,也印证了数据时代的风向。语言只是工具,解决特定领域的业务问题才是溢价的来源。
还有一个细节让我印象深刻。在词云图里,“熟悉 Docker”几乎成了所有后端岗位的标配,字体大小仅次于数据库和框架名称。这意味着现在的开发不再只是在本地 IDE 里写完代码,点一下 Run 就算结束了。企业要求候选人懂一点运维,懂容器化,懂持续集成。代码要能在服务器上跑起来,才算真正交付。
这些结论在今天看来或许已经是老生常谈的常识,但在当时,它们像是一盏灯,照亮了我后续学习的路线。我不再去钻研那些快要被淘汰的旧技术,而是把精力放在了更贴近市场需求的地方,开始折腾 Linux,学习容器化部署,看更现代的框架源码。
这个爬虫项目最后在 GitHub 上拿了 57 个 Star。在浩瀚的开源世界里,这点星星微不足道。没人会因为这 57 个 Star 给我发 offer,但对我个人而言,它是一个闭环的起点。它让我第一次完整走通了从需求定义、数据采集、清洗、分析到可视化的全流程。
后来我的大学生活过得还算充实。现在的我大四,GPA 保持在 4.02,拿了两次国家奖学金,也参加了不少比赛,拿了几个国二的奖项。偶尔会有学弟学妹在微信上问我,在一个三本院校里怎么摆脱学历焦虑,怎么面对未来找工作的压力。我其实没有什么宏大的建议。
焦虑来源于未知。当你不知道外面的世界是什么样,不知道自己的斤两时,你会恐慌。消除这种恐慌的方法,就是去折腾。就像写那个爬虫一样,遇到风控就去加延迟,遇到脏数据就去写正则清洗,不知道市场要什么就去爬真实的数据看。把那些抽象的、庞大的焦虑,拆解成一行行具体的代码和一个接一个可以被复现、被解决的 bug。
学历标签是既定事实,但技术能力和认知是可以通过持续的折腾去重塑的。代码不会骗人,你敲下的每一个按键,最终都会在终端端诚实地返回属于你的结果。