LinkedIn员工数据爬虫实战:从登录态到反爬虫的完整技术拆解
2026/9/7 8:19:58 网站建设 项目流程

简介:基于公司名称抓取LinkedIn员工公开信息的开源爬虫项目,面向需要批量获取企业人才结构的研究者、数据分析师及招聘人员,也适合初步接触网络爬虫的开发者学习参考。项目以Python实现,核心脚本linkedinSpider.py演示了从目标设定、网络请求、HTML解析到深度抓取与数据存储的完整流程,涵盖搜索页与个人详情页两轮抓取,可学习requests、BeautifulSoup、selenium等库的搭配使用,以及应对验证码、IP限制、User-Agent检测等反爬机制的常见策略,代码结构便于按需调整抓取字段与存储格式。压缩包共3个文件,包含Python源码、README说明和.gitignore配置,整体仅5KB,结构精简,适合快速阅读并二次开发。目前已有1593人学习下载,适用于市场研究、招聘寻源、客户分析或学术数据采集等场景;使用时应遵循LinkedIn服务条款,平衡爬取频率并尊重用户隐私,避免对目标网站造成异常压力。

1. 项目概述与需求拆解

1.1 这个爬虫到底是干什么的

拿到这个LinkedinSpider项目的时候,其实第一反应就是——这是一个典型的“公司维度”人才采集工具。输入一个公司名字,程序自动去LinkedIn上搜索这个公司的员工列表,然后把每个员工的档案信息(姓名、职位、所在地、个人简介、工作经历等)抓取下来,保存成结构化数据。

这套逻辑听起来简单,但实际应用场景相当广。做招聘的猎头拿它做目标公司人才地图;做B2B销售的人用它挖掘潜在客户联系人;做投研的人用它看创业公司的团队背景;做竞品分析的HR会用它来拆解对手的组织架构。哪怕是我自己,也经常用类似思路去验证某个赛道里有哪些核心人物在职。

和传统爬虫不太一样的是,LinkedInSpider的核心输入不是一个个URL链接,而是一个可能模糊甚至带歧义的公司名称。所以它的第一步其实是个搜索动作——先在LinkedIn上找到目标公司主页,再顺着公司主页的员工列表去逐层翻页,最后把员工数据落库。这个“输入名字直接出人”的交互模式,决定了它在工程实现上比普通定向爬虫多一层“搜索-消歧-对齐”的逻辑。

1.2 做这类项目前必须想清楚的四个问题

在真正动手写代码前,有几个核心决策点会直接影响项目天花板:

数据字段的取舍。你要拿到什么粒度?只拿姓名和职位,那非常简单;但如果你想拿到邮箱、手机号这些非公开字段,技术上就是另一条路——这条路通常既违法又违背LinkedIn的用户协议,而且基本只能靠漏洞或者诱导手段来实现,不建议碰。这个项目能合理抓取的边界,就是LinkedIn公开页面上展示的信息。

登录态的处理方式。LinkedIn绝大多数功能都要求登录,完全匿名的爬虫在搜索环节就会被挡下来。登录态用Cookie维持还是用账号密码自动登录?要不要做Cookie池?这是整个项目里最容易翻车的环节。

请求频率和流量控制。LinkedIn的风控在业界属于中上水平,封IP、弹验证码、限制搜索次数都是家常便饭。单机跑还是分布式跑、多久请求一次、要不要设置代理池,这些不做前置规划,代码写得再优雅也活不过两个小时。

输出数据的组织和去重逻辑。同名员工、同一员工多段工作经历、不同公司用同一邮箱前缀等情况,在存储字段设计上不提前考虑,后面做数据分析的时候会非常痛苦。

这四个问题想清楚了,项目的大体框架也就出来了。

2. 技术选型与整体架构设计

2.1 请求框架选哪个,为什么

LinkedInSpider这类项目在请求框架上的选择,个人经验是分成两派。

第一派是用纯请求库(requestshttpx)模拟接口调用。这种方式性能高、单位时间能跑出更多数据,但缺点是极其脆弱——LinkedIn的前端是React架构,接口返回的JSON结构变更频繁,而且很多数据接口有加密参数(csrf-token、匿名token等),请求头漏一个字段就会被弹回登录页。每次LinkedIn更新前端代码,你的解析逻辑就要跟着改一遍,维护成本很高。

第二派是用浏览器自动化框架,比如PlaywrightSelenium。这套方案模拟真实用户浏览器行为,能自动执行JavaScript、绕过大部分基础的指纹检测,稳定性比纯HTTP请求高不少。代价是并发量上不去,跑1000个公司可能要挂机一整天。

我自己的建议是:按需组合。如果只是临时跑几十个公司,直接用Playwright + 无头浏览器就够了,稳定压倒一切;如果要长期、大规模地跑,那就得走“浏览器模拟登录 + 抓取关键Cookie + 纯requests调接口”的混合模式,用浏览器的能力帮纯HTTP请求铺路。

这个项目从标题上看是LinkedinSpider这样的轻量封包,大概率走的是第一种思路:用Python的requests或者类似库直接请求搜索页和数据接口,配合手动维护的Cookie完成鉴权。实测下来,对中小企业数量级的任务,这种方案的性价比确实最高。

2.2 数据落盘与字段设计

员工数据的最终存储格式,我经历过三个阶段的迭代:先是一股脑全塞CSV,后来发现同一公司成百上千人、每人十几段工作经历,一张CSV表格很难表达;改成JSONL(每行一个JSON对象)后,单条档案的完整层级才能保留下来;最后是SQLite存结构化字段,方便后续做筛选和统计。

实际项目里推荐保存的核心字段我整理成了对照表:

字段示例值来源
员工姓名张三搜索结果页/档案页
职位标题Senior Engineer搜索结果页/档案页
所在地区California, US搜索结果页
个人简介转型中的全栈工程师档案页
当前公司Acme Corp搜索条件
工作经历列表Acme Corp → Alibaba → Tencent档案页(可选深挖)
个人主页URLlinkedin.com/in/xxx搜索结果页

这里有个细节容易被忽略:搜索列表页上已经能拿到姓名、职位、地区这些核心信息,但个人简介和工作经历必须点进每个员工的详情页才能拿到。如果项目只抓列表页,速度很快但信息单薄;如果抓了详情页,数据质量高但请求量成倍增加——每多抓一个字段,对应的反爬风险系数也涨一截。需要根据实际需求来做平衡。

2.3 任务队列的设计思路

虽然标题里没写调度模块,但跑过的人都明白,这种按公司名批量跑的任务必须有一个任务队列来接单。最朴素的实现是维护一个company_list.txt,每行一个公司名,跑完一个删一个;稍微进阶一点用Redis的有序集合做任务队列,支持崩溃恢复、爬虫worker的横向扩展。

分布式爬虫在LinkedIn这个场景其实被严重依赖。原因很简单:单IP的请求频率上限就摆在那,想提高吞吐量,要么等IP冷却,要么上代理池和多台机器。Twitter上曾有团队分享过他们用Scrapy+Scrapyd+Redis分布式跑LinkedIn数据的架构,但在国内的网络环境下,启不启用分布式爬虫更取决于你的代理资源是否够用,而不只是代码层面能不能跑通。

3. 核心实现细节与实操步骤

3.1 登录状态获取与Keep-Alive

LinkedIn登录态是整个爬虫项目的生命线。最稳妥的方式还是在浏览器里手动登录一次,然后用浏览器开发者工具(F12)从网络面板里复制出关键的Cookie信息。具体来说,需要关注以下几个请求头的一整套组合:

headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", "cookie": "li_at=...; JSESSIONID=...; lang=v=2&lang=zh-cn", "csrf-token": "ajax:...", "x-li-track": "..." }

其中li_at是LinkedIn登录后的长效会话凭证,有效期通常是一到两周;JSESSIONID是短期会话,配合csrf-token做接口鉴权。

我踩过的坑是:直接用浏览器复制Cookie虽然方便,但是请求头里如果少了accept-language或者x-restli-protocol-version这类标志头,LinkedIn的接口偶尔会返回一个403,看起来像IP被封,其实是请求头不完整。所以建议把整个请求头模板固定下来,之后所有的请求都复用同一套配置。

Cookie过期是必然的,解决方案通常是两个方向:一是写一个浏览器自动登录脚本,到点后用Playwright重新登录并刷新li_at;二是维护多个账号的Cookie池,轮询使用。个人建议小规模任务直接手动更新Cookie,把这个过程做成一键脚本,不值得为它写一套账号风控规避的复杂工具。

3.2 搜索“公司员工”的两种路径

拿到登录态之后,接下来要解决的是“输入公司名,怎么找到员工列表”。常见的有两种路径:

路径一:目标公司搜索页,通过公司主页的员工Tab翻页。

搜索框输入公司名字,在结果页筛出官方公司主页,点进主页后访问/company/{company_id}/people/这个URL。不同页面的翻页参数通常是page或者start,比如翻第2页就是people/?page=2

路径二:直接通过员工搜索页过滤公司。

直接打开/search/results/people/,在过滤器中把Current Company设为目标公司。这种方式的URL构造灵活,能结合关键词(比如只找工程师),但容易触发LinkedIn的搜索限制弹窗,频率控制不好会被风控很快盯上。

从稳定性的角度出发,我推荐优先用路径一。公司主页的员工列表页是入口,结构相对固定,反爬措施也宽松一点;员工搜索页适合做精准定向补采,不适合作为主流程。核心原则是:尽量走在LinkedIn认为“正常用户”会走的路上

3.3 员工列表解析与详情页深挖

有了员工列表HTML/JSON之后,解析阶段的工作量反而被低估了。如果你是用requests直接请求LinkedIn的API端点,返回的JSON里通常有一套类似下面的嵌套结构:

# 伪代码:员工卡片解析 def parse_employee(card): return { "name": card.get("fullName", ""), "title": card.get("headline", ""), "location": card.get("location", ""), "linkedin_url": card.get("publicIdentifier", ""), }

注意LinkedIn的字段名经常随版本变化,比如headline有时候是position或者occupation,解析前建议先用一次真实返回值做结构探查,不要对着旧文档硬写。

如果你愿意多花一点请求量提升数据质量,可以再对每个publicIdentifier构造/in/{username}/详情页,从中提取个人简介、教育经历、完整工作经历等列表页没有的信息。但这里请务必设置独立的请求队列和限速参数,普通翻页和详情页深挖的比例我建议控制在一比一以下,否则风控触发速度会明显变快。

3.4 限速与风控意识

说到风控,LinkedInSpider这类项目最核心的反爬策略其实就两个字:慢、变。

“慢”是控制请求间隔。实测下来,在没有任何代理的情况下,单IP的请求间隔低于5秒,半小时内就会被限制搜索;低于2秒,十分钟内大概率弹验证码。我现在习惯的做法是普通列表页请求间隔8到12秒,详情页间隔10到15秒,深夜跑数据时偶尔把间隔缩短到4秒,但不会持续太久。

“变”是让请求节奏看起来像真人。每跑完一个公司,停40到60秒;请求时间固定在某些时间段(比如工作日的白天、晚上八点到十一点),避开凌晨三点这种明显的机器活跃期。另外一定要给整个爬虫套上随机UA池,虽然LinkedIn对UA的检测没有Google那么严格,但总是同一个UA也会被挂上低信任标签。

如果你有条件上代理池(住宅代理),那稳定性会好很多——每个IP控制在50到100次请求内就要换。如果没条件,那就老老实实把单IP的日请求量压在几百次以内,跑完一轮隔几天再跑下一轮。爬虫界的铁律是:数据没拿到顶多是效率低,IP被风控拉黑才是真正的全军覆没。

4. 常见问题与排查技巧实录

4.1 登录态校验过期的各种信号

LinkedIn的登录过期不会直接报错“登录失败”,它通常会给你一些模糊的反馈。最常见的几种“信号”是:

  • 搜索请求返回的HTML/JSON里没有任何员工数据,只有登录引导页或“Join now”按钮。
  • 接口返回401 Unauthorized403 Forbidden,但你的UA和Cookie看起来没问题。
  • 详情页请求返回正常的HTTP 200,但解析出来的字段全部为空。

遇到以上情况,我的排查顺序是:先确认Cookie里的li_at是否真的有效(打开浏览器无痕窗口,粘进Cookie手动访问一次);再检查请求头里的csrf-token是否对应最新的JSESSIONID;最后排除是不是公司名输入有误。不要一上来就怀疑IP被封,Cookie问题在LinkedIn爬虫里大概占了一半以上。

4.2 验证码触发后怎么办

验证码(captcha)是所有爬虫公敌,LinkedIn的验证码触发有几个规律可循:新登录的账号前几次操作触发概率高;短时间高频搜索触发概率高;数据中心大城市的IP段触发概率也偏高。

第一次触发验证码时,不要急着写代码绕过,先把浏览器打开手动过掉验证码。这能帮你搞清楚一个问题:到底是账号触发了安全验证,还是整个IP被标记了。如果是账号级,接下来降低请求频率、换一个时间段继续跑即可;如果是IP级,那就要换出口IP或者歇一段时间(短则半小时,长则一天)。这里的关键心得是:触发验证码不是一个点上的问题,而是一条线上的问题,只有把频率、UA、入口路径全部调温和,验证码才会消失。

4.3 同名公司与名字匹配不准

公司名搜不准是这类“以名找人”项目非常现实的痛点。比如你输入“腾讯”,LinkedIn上可能存在“Tencent”“腾讯”“Tencent Games”“Tencent Cloud”等多个主页,搜索结果甚至可能把“南京腾讯科技”这种关联公司排在前头。

解决方案分两层:第一层是在搜索结果列表做精准匹配,比对公司主页的名称标准化形式(不同语言下用官方英文名优先);第二层是把爬取结果的公司归一到你输入时的原始公司名上,避免后续数据统计时出现重复和分裂。我个人的做法是维护一张公司别名映射表:输入一个公司名,程序自动去查它对应的LinkedIn公司ID,后续所有请求都用这个ID,而不是重新搜索。这样既准确又省搜索次数。

4.4 数据量大之后的存储性能问题

当你从几十个公司扩张到几百个公司,每天新增几万条员工数据之后,CSV开始变得难用。同一个员工在不同公司下重复出现、同一公司名在不同语言下重复抓取,这些去重逻辑用SQL去重比用Python内存集合适用得多。

我是在跑到第40个公司左右时彻底搬进SQLite的。建一张employees表,用(company_id, linkedin_url)作为唯一索引,后续抓到的数据先INSERT OR IGNORE再更新,代码逻辑一下简单了许多。如果未来数据量继续增长,可以直接把SQLite换成Postgres,表结构几乎不用大改。

5. 合规边界与项目延展建议

5.1 法律与协议的红线

LinkedIn的用户协议明确禁止未经许可的自动化数据采集,而“根据公司名抓员工信息”这个动作,天然就落在个人数据处理的敏感区。国内《个人信息保护法》施行之后,抓取个人公开信息并用于自动化分析,在合规上需要更谨慎地评估使用场景和数据的用途。

这不是劝退,而是提醒:爬虫项目本身是中性的工具,但使用目的决定了它是否越界。比如用于学术研究、市场宏观分析、公开信息的聚合对比,风险相对低;但如果把抓到的员工信息用于营销骚扰、人才猎头之外的商业转售,那民事责任甚至刑事风险都可能被放大。每一个把LinkedInSpider用起来的人,都应该先想清楚自己的使用边界。

5.2 从爬虫工具到数据产品的三步延伸

跑通LinkedInSpider只是一个起点,我个人觉得它真正的价值在于后续的数据加工能力。建议做完基础爬虫后,按这三个方向逐步迭代:

  • 数据清洗增强:把原始员工信息接进大模型做结构化抽取,比如从职位头衔里提取职级、职能方向,从简介里提取技能标签,这能让数据从“可读”变成“可分析”。
  • 趋势与动态监控:定时重跑同一批公司,对比数据变化,就能得出半导体行业最近三个月的人员流动趋势。这种动态数据比静态名单值钱得多。
  • 多平台数据融合:把LinkedIn的员工数据与GitHub、技术社区的个人主页关联,形成更完整的开发者画像,这套画像用在开源社区的贡献者招募上效果好得出奇。

5.3 最后再分享两个小技巧

第一个是关于公司搜索失败的兜底逻辑:当你输入的公司名在LinkedIn上找不到直接匹配时,不要直接标记失败,可以尝试换用其他语言写法、简称甚至股票代码去搜索。我遇到过好几个公司,中文名搜不到,但输入英文缩写一下子就出来了。

第二个是关于数据字段里最容易被忽略的profile_url:务必把它保留好。这个字段不仅仅是去重用的,它还是后续做关联分析的钥匙——比如你要对某位候选人做背景调查,有了这个url,后续所有补充信息都可以围绕它展开。我见过太多人清理字段时把它删了,后来追悔莫及。

LinkedInSpider这类项目的开发过程,很像是在“获取数据的欲望”和“平台的反爬防线”之间走钢丝。技术本身不难,难的是对风控的敬畏、对数据质量的偏执,以及对合规边界的清醒认知。希望这篇拆解能帮正要踏入这个领域的你少踩几个坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询