LinkedIn 的公开职位列表是招聘数据的丰富来源:职位名称、公司、地点、发布日期,以及能告诉您需求走向的搜索维度。以编程方式获取这些数据的难点在于:LinkedIn 在客户端渲染页面,并会强力挑战自动化流量,因此普通 HTTP 请求只能得到一个空壳,而不是职位信息。本指南将展示如何用 Python 抓取 LinkedIn 公开职位列表:一个小型、可运行的脚本,通过 Crawling API 获取已渲染的页面,提取所需字段,并将结果写入磁盘。
为保持诚实,整个演练的范围仅限于公开数据:任何人无需登录即可在 LinkedIn 公开职位搜索页面看到的职位列表。它不涉及账户、需要登录才能查看的内容或个人资料。下文的法律部分对这条边界有清晰说明,并非套话,请在将此脚本应用于真实规模之前先阅读它。
抓取 LinkedIn 是否合法?
请先阅读本节,因为 LinkedIn 是网络上最敏感的目标之一,诚实的回答是"取决于情况,而且范围至关重要。"本指南特意将范围限定于公开、未经身份验证的职位列表:LinkedIn 向所有人(无论是否登录)在其公开职位搜索界面提供的页面。在此范围内,有几条规则是不可逾越的:
- 不要抓取需要身份验证的内容。如果某个页面需要登录才能查看,它超出了本指南的范围。不使用会话 Cookie、不重用凭据、不绕过任何形式的身份验证。
- 不要收集个人数据或个人资料。姓名、联系方式、关系图谱和个人资料页面都不在此列。本演练收集的是职位发布元数据,而非关于可识别个人的数据。
- 不要违反 LinkedIn 的用户协议。在开始之前,请阅读 LinkedIn 的服务条款及其 robots.txt,并遵守其中的规定。尊重所述的速率预期,保持您的请求量足够低,避免给任何人的服务器造成压力。
关于法律背景:在 hiQ Labs 诉 LinkedIn 案中,美国法院就抓取公开可用数据的问题作出裁决,高层面上认定访问公开页面本身不违反《计算机欺诈和滥用法》。该案经常被援引为抓取 LinkedIn 的依据,但它并非普遍许可。合同条款(用户协议)、隐私法以及您如何使用数据仍然适用,且法律格局持续演变。将公开数据这条线视为底线,而非漏洞。
本指南收集的全部是公开职位列表元数据:任何人无需账户即可看到的职位名称、公司、地点和发布日期。它不涵盖需要登录才能查看的数据、个人资料、关系数据、消息或任何绕过身份验证的方式。如果您的项目需要超出公开列表的内容,正确的做法是与 LinkedIn 建立官方合作或数据协议,而不是设计更聪明的爬虫。
为什么普通请求在 LinkedIn 上会失败
用裸 HTTP 客户端请求 LinkedIn 职位搜索 URL,您会得到一个几乎没有职位数据的 200 响应。有两个因素对您不利。第一,LinkedIn 使用 JavaScript 在浏览器中渲染其列表,因此初始 HTML 是一个外壳,只有在页面脚本运行后才会填充。第二,LinkedIn 会迅速标记自动化流量:数据中心 IP 和不像真实访客的请求模式,在到达已渲染内容之前就会遭到挑战或封禁。
因此,一个可运行的 LinkedIn 职位爬虫需要在单次请求中同时具备两点:能渲染页面的浏览器,以及平台识别为真实访客的 IP。您可以自行组合无头浏览器和轮换住宅代理池,但维护这套方案本身就是主要工作量。Crawling API 将两者合为一次调用:向它发送带有 JavaScript token 的 URL,它在可信 IP 背后渲染页面,并返回已完成的 HTML。
Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 先在真实浏览器中渲染页面。LinkedIn 是客户端渲染的,因此您需要 JS token。使用普通 token 会返回与普通请求相同的空壳。
了解目标:LinkedIn 的公开职位搜索 URL
LinkedIn 的公开职位搜索位于一个可预测的 URL,其查询参数直接映射到搜索表单,因此您无需驱动 UI 就可以以编程方式构建任何搜索。以下是一个具体示例:伦敦的 Python 开发者职位。
https://www.linkedin.com/jobs/search?keywords=Python%20Developer&location=London
重要的参数:
- keywords 您要搜索的职位或技能,经过 URL 编码。
- location 要搜索的城市、地区或国家。
用您关心的参数构建 URL,您就有了一个可重复使用的目标。在循环中改变关键词和地点,您就有了一个随时间追踪公开需求的招聘趋势任务。
设置您的环境
您需要 Python 3.8 或更高版本。确认您的版本,创建虚拟环境以隔离项目依赖,然后安装所需库。
python --version python -m venv linkedin_env source linkedin_env/bin/activate pip install crawlbase beautifulsoup4
在 Windows 上,请用 linkedin_env\Scripts\activate 代替 source 命令来激活环境。两个依赖完成工作:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML,让您可以从中提取字段。您还需要一个 Crawlbase 账户和 JS token,注册后从控制台获取。将其替换代码中所有出现的 YOUR_CRAWLBASE_JS_TOKEN。
获取已渲染的职位搜索页面
首先获取已完成的页面。您需要传入两个对 LinkedIn 这类网站至关重要的选项:ajax_wait 告诉 API 等待异步内容加载完成,page_wait 在加载后保持固定毫秒数,让延迟渲染的列表有时间出现。五秒是合理的起点;如果结果返回较少,可以适当提高。
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) options = {"ajax_wait": "true", "page_wait": 5000} jobs_url = "https://www.linkedin.com/jobs/search?keywords=Python%20Developer&location=London" def fetch_jobs_html(url): response = api.get(url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print("Failed to fetch the page. Status code:", response["status_code"]) return None html = fetch_jobs_html(jobs_url) print(html[:500])
运行后您应该能看到包含职位卡片的真实标记,而不是普通请求返回的空壳。在编写任何选择器之前,这一步先确认渲染是否正常工作。
LinkedIn 需要在可信 IP 背后渲染页面,一次调用完成。Crawling API 接收 JS token,在真实浏览器中运行页面,在服务器端轮换住宅 IP,将已完成的 HTML 交给您,让您无需自行运行无头浏览器集群和代理池。先在免费套餐中指向一个公开职位搜索试试。
解析职位列表
拿到 HTML 后,将其加载到 BeautifulSoup 并遍历职位卡片。公开搜索页面上的每张卡片包含您需要的字段:职位名称、公司名称、地点和发布日期。在浏览器开发者工具中检查实时页面以找到当前的选择器,然后将每个字段映射到对应选择器。
from bs4 import BeautifulSoup def extract_jobs(html): soup = BeautifulSoup(html, "html.parser") jobs = [] for card in soup.select("div.base-card"): title = card.select_one("h3.base-search-card__title") company = card.select_one("h4.base-search-card__subtitle") location = card.select_one("span.job-search-card__location") posted = card.select_one("time") jobs.append({ "title": title.get_text(strip=True) if title else "", "company": company.get_text(strip=True) if company else "", "location": location.get_text(strip=True) if location else "", "posted": posted["datetime"] if posted else "", }) return jobs
LinkedIn 的类名会不经通知地更改。将上述选择器视为起始模板,而非合同。当提取结果返回空字符串时,重新检查实时公开职位搜索页面并更新选择器。这是任何生产环境爬虫的正常维护工作,不代表代码出了问题。
将获取和解析连接到一个 main 函数中,以便得到一个可运行的脚本。
def main(): html = fetch_jobs_html(jobs_url) if not html: return jobs = extract_jobs(html) for job in jobs: print(job) if __name__ == "__main__": main()
输出示例
运行完整脚本,您将得到一份结构化职位对象列表。精简后的示例:
[ { "title": "Senior Python Developer", "company": "Monzo Bank", "location": "London, England, United Kingdom", "posted": "2026-01-14" }, { "title": "Python Backend Engineer", "company": "Deliveroo", "location": "London, England, United Kingdom", "posted": "2026-01-12" } ]
处理分页
公开职位搜索显示第一批列表,随着滚动或翻页会显示更多。遍历这些结果最简洁的方式是使用 start 查询参数来偏移结果:start=0 是第一页,start=25 是下一页,依此类推。循环偏移量,通过 Crawling API 获取每页,并收集结果行。
all_jobs = [] base = "https://www.linkedin.com/jobs/search?keywords=Python%20Developer&location=London" for start in range(0, 75, 25): page_url = f"{base}&start={start}" html = fetch_jobs_html(page_url) if not html: break all_jobs.extend(extract_jobs(html)) print(f"Collected {len(all_jobs)} listings")
保持页数适中并控制循环节奏。采样搜索结果翻三页,与在紧密循环中扫描数千个偏移量,有着本质区别,后者正是导致爬虫被限速的做法。
将结果保存为 CSV
打印到控制台适合调试阶段,但您需要将数据持久化到磁盘。Python 内置的 csv 模块将每个对象键映射到一列,用几行代码就能写入所有行,无需额外依赖。
import csv def save_to_csv(jobs, path="linkedin_jobs.csv"): fields = ["title", "company", "location", "posted"] with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(jobs) print(f"Saved {path}")
在分页循环之后调用 save_to_csv(all_jobs),每次运行都会写入一个整洁的 linkedin_jobs.csv,可以在任何电子表格中打开或加载到数据处理管道。如果您更倾向于用 SQL 查询数据,可以将相同的行写入 SQLite 表;解析逻辑完全相同。
保持不被封禁
即使处理好了渲染,LinkedIn 仍然会监视爬虫形态的流量。以下几个习惯可以让运行保持健康,适用于任何难度较大的商业目标。
- 控制请求频率。在紧密循环中重复相同搜索是最快被限速的方式。分散请求,并改变关键词和地点。
- 依赖轮换。住宅代理池将请求分散到众多真实用户 IP,避免任何单一地址触发速率限制。Crawling API 会为您处理这些;如果您倾向于自己路由流量,Smart AI Proxy以即插即用代理端点的形式提供相同的住宅 IP 轮换功能。
- 关注状态码。当运行开始返回挑战或错误时,说明当前频率或 IP 层级已不再足够。将代理状态错误码视为信号而非噪音,出现时回退。
关于更广泛的指南,请参阅如何在不被封禁的情况下抓取网站。
核心要点
- 坚守公开职位数据。本指南仅收集公开职位列表元数据。不涉及需要登录的页面、个人资料或绕过身份验证;尊重 LinkedIn 的用户协议和 robots.txt。
- LinkedIn 是客户端渲染的。普通请求返回空壳,因此解析之前必须先渲染页面。
-
您需要渲染和可信 IP 的组合。带有 JS token 的 Crawling API 在一次调用中完成两件事;
ajax_wait和page_wait控制等待内容加载的时长。 -
分页是偏移量。以 25 为增量步进
start参数,以遍历更多页面的公开结果。 - 轮换和控频以保持不被封禁。Crawling API 为您轮换 IP;如果您自己路由流量,Smart AI Proxy 以即插即用端点的形式提供该轮换功能。
常见问题
抓取 LinkedIn 是否合法?
取决于您抓取的内容以及如何使用。本指南严格限定在公开、未经身份验证的职位列表,避免个人资料、需要登录的内容和绕过身份验证的方式。即便如此,LinkedIn 的用户协议、robots.txt 以及适用的隐私法仍然有效。hiQ 诉 LinkedIn 案高层面涉及公开数据访问,但它并非普遍许可。请先阅读 LinkedIn 的条款,并将范围限定在公开数据。
为什么普通请求从 LinkedIn 返回不到职位数据?
因为 LinkedIn 使用 JavaScript 在客户端渲染其列表。初始 HTML 是一个外壳,只有在浏览器中运行页面脚本后才会填充,因此原始 HTTP 请求返回状态 200,但职位字段为空。要获取真实数据,您必须先渲染页面,这正是 Crawling API 的 JS token 为您处理的事情。
LinkedIn 需要普通 token 还是 JS token?
需要 JS token。普通 token 获取静态 HTML,在 LinkedIn 上这与普通请求返回的空壳相同。JS token 在返回 HTML 之前先在真实浏览器中渲染页面,因此当 BeautifulSoup 解析时,列表是存在的。
我的选择器返回空字符串。什么发生了变化?
几乎可以肯定是 LinkedIn 的标记发生了变化。其类名会不经通知地更改,因此上个月有效的选择器可能突然失效。在浏览器开发者工具中重新检查实时公开职位搜索页面并更新选择器。定期维护选择器是任何生产环境爬虫的正常工作。
如何避免在抓取 LinkedIn 时被封禁?
保持较低的每 IP 请求频率,改变关键词和地点而不是循环相同的 URL,并通过轮换住宅 IP 路由,避免任何单一地址触发速率限制。Crawling API 为您管理轮换和可信 IP 池;如果您自建方案,Smart AI Proxy 以即插即用端点的形式提供该轮换功能。留意状态码,出现挑战时回退。
我可以用这个抓取 LinkedIn 个人资料或消息吗?
不行,您也不应该尝试。本指南特意将范围限定在公开职位列表。个人资料、关系数据和消息位于身份验证之后,涉及个人数据,超出了本文的范围。如果您的项目需要超出公开职位列表的内容,请通过官方 LinkedIn 合作或数据协议来获取,而不是抓取需要身份验证的页面。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
