Glassdoor 是开放网络上最大的求职和公司研究平台之一,其公开职位列表页面承载着大量结构化信号:职位名称、招聘公司、工作地点,以及员工对这些公司的星级评分。招聘人员用它来衡量市场需求,分析师用它研究各行业的招聘趋势,求职者则并排比较职位和雇主评分。所有这些信息都以可预测的布局呈现在公开搜索结果页面上。
本指南演示如何用 JavaScript 和 Node.js 配合 Cheerio 抓取 Glassdoor。你将构建一个小型可运行的爬虫,通过 Crawling API 获取公开的 Glassdoor 职位搜索页面,解析每个职位的标题、公司、地点、公司评分、薪资估算和链接,处理分页,并将结果导出为 JSON 和 CSV。整个流程仅限于公开职位和公司列表数据,结尾处的合法性章节并非走过场,请在大量抓取前认真阅读。
你将构建什么
一个 Node.js 脚本:接受一个公开的 Glassdoor 职位搜索 URL,通过 Crawling API 获取渲染后的 HTML,并为结果页面上的每个职位卡片提取结构化记录。我们以开发者职位搜索作为示例,提取每个职位的以下字段:
- 标题 卡片上显示的职位名称,例如"React Native Developer"。
- 公司 招聘雇主的名称。
- 评分 公开的公司星级评分,当 Glassdoor 为该雇主显示时。
- 地点 职位所在的城市和地区。
- 薪资 卡片上显示的估算薪资范围(如有)。
- 发布日期 职位发布距今的时间,例如"30d+"。
- 链接 指向具体职位发布页面的 URL。
为什么普通请求在 Glassdoor 上会失败
如果用裸 HTTP 客户端请求 Glassdoor 搜索 URL,你很少能得到职位卡片。有两个原因。第一,Glassdoor 通过 JavaScript 在浏览器中渲染结果列表,因此初始 HTML 几乎是空壳,直到页面脚本运行后才会填充内容。第二,Glassdoor 积极识别自动化流量:数据中心 IP 和不像真实浏览器的请求模式会被 CAPTCHA 挑战、限速或封锁,甚至在到达渲染后的列表之前就被拦截。
因此,一个可用的 Glassdoor 爬虫在单次请求中需要两件事:一个实际渲染页面的浏览器,以及平台认为是真实访客的 IP。你可以自己搭建无头浏览器加上轮换住宅代理池,但将这两者整合并保持正常运行占了大部分工作量。Crawling API 将两者合并为一次调用:你把 URL 发给它,它在受信任的 IP 后面渲染页面,并返回供你用 Cheerio 解析的完整 HTML。
Crawling API 提供两种 token:普通 token 和 JavaScript token。Glassdoor 需要在真实浏览器中渲染页面,因此本指南中的每次请求都要使用你的 JavaScript token。普通 token 返回未渲染的 shell,你的选择器会返回空值。
前提条件
在编写任何代码之前,你需要准备好以下几项。都不会花太长时间。
基础 JavaScript 和 Node.js 知识。 你应该能够编写和运行 Node 脚本,并使用 npm 安装包。如果你是 Node.js 新手,官方文档和任何入门课程都能让你达到本教程所需的水平。更完整的演练,请参阅我们的用 Node.js 构建网络爬虫指南。
Node.js 16 或更高版本。 用 node --version 确认版本。如果没有,从 Node.js 官网安装,或通过 nvm 等版本管理器安装。
Crawlbase 账号和 token。 注册后打开控制台,从账号文档页面复制你的 JavaScript token。免费套餐最多提供 20,000 次请求,无需信用卡,你只需为成功的请求付费。请像对待密码一样保管 token:它用于验证你的请求,不要放入版本控制。
搭建项目
创建项目文件夹,初始化它,并安装爬虫所需的两个库。
node --version mkdir glassdoor-scraper && cd glassdoor-scraper npm init -y npm install crawlbase cheerio
两个依赖各司其职:crawlbase 是 Crawling API 的官方 Node 客户端,cheerio 用 jQuery 风格的 API 解析返回的 HTML,让你通过 CSS 选择器提取各个字段。在此文件夹中创建名为 scraper.js 的文件,并添加以下步骤中的代码。
第一步:获取渲染后的搜索页面
先获取完整页面。导入 CrawlingAPI 类,用你的 JavaScript token 初始化它,然后请求一个公开的 Glassdoor 搜索 URL。在解析之前检查状态码,可以让失败信息清晰可见。
const { CrawlingAPI } = require('crawlbase'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); const glassdoorPageURL = 'https://www.glassdoor.com/Job/new-york-ny-react-native-developer-jobs-SRCH_IL.0,11_IC1132348_KO12,34.htm'; api .get(glassdoorPageURL) .then((response) => { if (response.statusCode === 200) { console.log(response.body.slice(0, 500)); } }) .catch((error) => console.error('API request error:', error));
用 node scraper.js 运行脚本,你应该能在正文顶部看到真实的 Glassdoor 职位标记,而不是精简的 shell。这确认了渲染在你编写第一个选择器之前就已正常工作。Crawling API 使用你提供的 JavaScript token 在真实浏览器中渲染页面,因此你得到的 HTML 中已包含职位卡片。
刚才那次请求返回了一个完整渲染的 Glassdoor 搜索页面,而你这边不需要无头浏览器或代理。Crawling API 在真实浏览器中运行页面,在服务器端轮换住宅 IP,并处理 Glassdoor 对爬虫发出的 CAPTCHA 挑战,让你通过一次调用得到完整的 HTML。先用免费套餐指向一个公开职位搜索,然后再添加解析器。
第二步:用 Cheerio 解析每个职位卡片
拿到渲染后的 HTML,将其加载到 Cheerio 并遍历职位卡片。Glassdoor 在"Jobs List"容器中列出结果,每个职位在其自己的卡片中,因此你选取每张卡片,然后从中读取标题、公司、评分、地点、薪资、发布日期和链接。防御性地读取每个字段,避免单个缺失值导致运行崩溃。
const cheerio = require('cheerio'); function parseDataFromHTML(html) { const $ = cheerio.load(html); const searchResults = { resultInfo: '', jobs: [], }; // Result summary (for example "1,200 React Native Developer Jobs") searchResults.resultInfo = $('.SearchResultsHeader_jobCount__12dWB') .text() .trim(); // One record per job card $('ul[aria-label="Jobs List"] .jobCard').each((_, element) => { const card = $(element); const title = card.find('.JobCard_seoLink__WdqHZ').text().trim(); const company = card .find('.EmployerProfile_employerName__Xemli') .text() .trim(); const rating = card .find('.EmployerProfile_ratingContainer__N4hxE') .text() .trim(); const location = card.find('.JobCard_location__N_iYE').text().trim(); const postDate = card .find('.JobCard_listingAge__KuaxZ') .text() .trim(); const salary = card .find('.JobCard_salaryEstimate___m9kY') .text() .trim(); let link = card.find('.JobCard_seoLink__WdqHZ').attr('href'); if (link && link.startsWith('/')) { link = new URL(link, 'https://www.glassdoor.com').href; } if (title) { searchResults.jobs.push({ title, company, rating: rating || 'Rating not available', location, salary, postDate, link: link || '', }); } }); return searchResults; }
几个细节确保了这段代码的忠实性。结果摘要来自 .SearchResultsHeader_jobCount__12dWB,每个职位都在 ul[aria-label="Jobs List"] 容器内的 .jobCard 中。在一张卡片内,标题和链接都来自 .JobCard_seoLink__WdqHZ 锚点,公司来自 .EmployerProfile_employerName__Xemli,公开公司评分来自 .EmployerProfile_ratingContainer__N4hxE,地点来自 .JobCard_location__N_iYE,发布日期来自 .JobCard_listingAge__KuaxZ,薪资估算来自 .JobCard_salaryEstimate___m9kY。链接从锚点的 href 读取并解析为绝对 URL,以便在页面之外使用。
Glassdoor 的类名(上述 JobCard_* 和 EmployerProfile_* 后缀)是生成的,会不定期变化。请将这些选择器视为起始模板,而非固定合约。当某个字段返回空值时,在浏览器开发者工具中重新检查实时页面并更新选择器。定期维护选择器是任何生产爬虫的正常工作,并非哪里出错了的信号。
第三步:组装完整脚本并导出 JSON 和 CSV
现在将获取和解析整合为一个可运行的脚本,然后将记录以 JSON 和 CSV 两种格式写入磁盘。旧版指南使用 Express 端点来触发爬取,但使用简单脚本可以减少复杂度;如果需要,你可以稍后将其封装在端点中。
const fs = require('fs'); const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function crawl(pageUrl) { const response = await api.get(pageUrl); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } function toCsv(rows) { const headers = [ 'title', 'company', 'rating', 'location', 'salary', 'postDate', 'link', ]; const escape = (value) => `"${String(value).replace(/"/g, '""')}"`; const lines = [headers.join(',')]; for (const row of rows) { lines.push(headers.map((h) => escape(row[h])).join(',')); } return lines.join('\n'); } async function main() { const url = 'https://www.glassdoor.com/Job/new-york-ny-react-native-developer-jobs-SRCH_IL.0,11_IC1132348_KO12,34.htm'; const html = await crawl(url); if (!html) return; const data = parseDataFromHTML(html); fs.writeFileSync('glassdoor.json', JSON.stringify(data, null, 2)); fs.writeFileSync('glassdoor.csv', toCsv(data.jobs)); console.log(`Saved ${data.jobs.length} jobs to JSON and CSV`); } main();
将第二步中的 parseDataFromHTML 函数粘贴到同一文件,使 main 能够调用它。用 node scraper.js 运行,你会得到两个文件:glassdoor.json 包含完整的结构化记录,glassdoor.csv 可直接在电子表格中打开。toCsv 辅助函数对每个字段加引号并对嵌入的引号进行转义,这在这里很重要,因为职位标题和地点中经常包含逗号。
输出示例
JSON 文件包含结果摘要以及每个职位的对象,每个对象都有标题、公司、公开评分、地点、薪资估算、发布日期和链接。
{ "resultInfo": "React Native Developer Jobs in New York, NY", "jobs": [ { "title": "React Native Developer", "company": "Example Tech Inc", "rating": "4.1", "location": "New York, NY", "salary": "$110K - $140K (Employer est.)", "postDate": "30d+", "link": "https://www.glassdoor.com/job-listing/react-native-developer" }, { "title": "Senior Mobile Engineer", "company": "Acme Software", "rating": "3.8", "location": "Remote", "salary": "$130K - $160K (Glassdoor est.)", "postDate": "5d", "link": "https://www.glassdoor.com/job-listing/senior-mobile-engineer" } ] }
CSV 以相同的职位行加上标题行呈现,可以直接导入 Excel、Google Sheets 或任何读取分隔符文件的数据管道。
title,company,rating,location,salary,postDate,link "React Native Developer","Example Tech Inc","4.1","New York, NY","$110K - $140K (Employer est.)","30d+","https://www.glassdoor.com/job-listing/react-native-developer" "Senior Mobile Engineer","Acme Software","3.8","Remote","$130K - $160K (Glassdoor est.)","5d","https://www.glassdoor.com/job-listing/senior-mobile-engineer"
处理分页
单个搜索页面只是演示;实际任务需要提取所有结果页面。Glassdoor 通过在搜索 URL 中追加页面段来进行分页,因此你可以循环遍历页码,通过 Crawling API 获取每页,用同一函数解析,并在某页返回空卡片时停止。由于每个结果页面共享相同的卡片结构,你已经写好的解析器无需修改即可适用所有页面。
async function scrapeAllPages(baseUrl, maxPages) { const allJobs = []; for (let page = 1; page <= maxPages; page++) { // Glassdoor adds the page number before the .htm extension const pageUrl = baseUrl.replace('.htm', `_IP${page}.htm`); const html = await crawl(pageUrl); if (!html) break; const { jobs } = parseDataFromHTML(html); if (jobs.length === 0) break; // no more results allJobs.push(...jobs); console.log(`Page ${page}: ${jobs.length} jobs`); // Pace requests so you stay under the rate limit await new Promise((r) => setTimeout(r, 2000)); } return allJobs; }
URL 中的确切分页 token 可能会发生变化,所以在浏览器中检查几个真实的"下一页"链接并匹配规律。重要的习惯适用于任何目标:循环直到结果耗尽,并在请求之间添加短暂延迟,避免频繁请求站点。关于像这样渲染的、JavaScript 密集型页面的更多信息,请参阅我们的爬取 JavaScript 网站指南。
保持不被封锁
即使渲染已处理妥当,Glassdoor 仍会监测爬虫特征的流量。养成几个好习惯能让运行保持健康,这些习惯同样适用于任何强防护的商业目标。
- 控制请求频率。 在页面抓取之间引入延迟,而不是在紧密循环中频繁请求。分散请求是保持在 Glassdoor 速率限制之下的单一最重要因素。
- 依赖 IP 轮换。 住宅 IP 池将请求分散到众多真实用户地址,使单个 IP 不会触发限制或 CAPTCHA。Crawling API 为你处理这一切;如果你自己搭建,这部分是关键。
- 关注状态码。 运行期间开始返回挑战或非 200 响应,说明当前频率或 IP 层级已不够用。将其视为需要退让的信号,而不是可以忽略的噪音。
更广泛的策略请参阅如何在不被封锁的情况下抓取网站。如果你想获取另一个平台的类似职位数据,同样的获取+解析模式可以直接用于抓取 Indeed 职位和用 Python 抓取 Monster 职位。
抓取 Glassdoor 合法吗?
抓取 Glassdoor 是否被允许,取决于 Glassdoor 的服务条款、你所在的司法管辖区以及数据的用途。Glassdoor 条款限制自动化访问,因此无论你的工具有多谨慎,抓取行为都可能违反这些条款。这里的代码不改变这一点,只是让技术部分得以运行。请阅读 Glassdoor 使用条款及其 robots.txt,尊重其声明的任何速率预期,并将两者视为你采集内容的边界。
本指南有意将范围限定在公开职位和公司列表数据:任何人无需登录就能在搜索页面上看到的职位标题、招聘公司、地点、薪资估算、发布日期、链接,以及公司的公开星级评分。这与平台上的个人数据不同。个人员工评论、面试报告及其撰写者属于个人数据。将公司评分视为关于雇主的汇总信号,不要整合个人评论者的档案,也不要将某人的评论与其身份关联后再发布。任何需要登录的内容、大规模抓取,或涉及可识别个人的内容都会触发 GDPR 和 CCPA 等隐私法律,这些内容完全超出本文范围。
如果你的项目需要超出公开列表的内容,正确的途径是获得授权,而非更聪明的爬虫。Glassdoor 及其母公司运营着官方合作伙伴和 API 计划,以明确的使用条款、署名规则和商业权利提供雇主和列表数据。当你需要大量数据、有保障的结构或商业再使用权时,这些才是正确的工具。如果不确定某种用途是否被允许,请获得许可或数据协议,而不是将沉默视为默许。
核心要点
- Glassdoor 在客户端渲染列表并强力封锁。 普通请求返回空 shell 或 CAPTCHA,因此必须使用 JavaScript token 在受信任的 IP 后面渲染页面,然后才能解析。
- Crawling API 通过一次调用完成两件事。 它在真实浏览器中渲染页面,轮换住宅 IP,并处理 CAPTCHA,返回供你用 Cheerio 解析的完整 HTML。
-
Cheerio 负责提取字段。 选取 Jobs List 中的每个
.jobCard,然后读取标题、公司、评分、地点、薪资、发布日期和链接,并预期生成的类名会发生变化。 - 分页并导出。 循环遍历 Glassdoor 的页面段直到结果耗尽,控制请求节奏,并将结构化记录写入 JSON 和 CSV。
- 坚守公开数据。 只采集公开职位和公司列表,将个人评论和评论者视为个人数据,尊重服务条款和 robots.txt,对于批量或商业用途优先选择 Glassdoor 官方 API 或合作伙伴计划。
常见问题
我可以用 JavaScript 以外的语言构建 Glassdoor 爬虫吗?
可以。本指南使用 JavaScript 和 Cheerio,但同样的方法适用于任何语言。Crawling API 为多种语言提供了库和 SDK,因此你可以用同样的方式获取渲染后的 HTML,并用你的技术栈偏好的 HTML 解析器(例如 Python 中的 BeautifulSoup)进行解析。选择器和字段保持不变,只有解析语法不同。
为什么普通请求从 Glassdoor 返回不完整的数据?
因为 Glassdoor 通过 JavaScript 在客户端渲染职位列表,并用 CAPTCHA 挑战自动化流量。来自数据中心 IP 的原始 HTTP 请求通常返回空 shell 或封锁页面,而非职位卡片。要获取完整页面,你必须在受信任的 IP 后面渲染它,这正是 Crawling API 在你使用 JavaScript token 时为你处理的事情。
我的选择器返回空值。是什么变了?
几乎可以肯定是 Glassdoor 的标记发生了变化。其生成的类名(如 JobCard_seoLink__WdqHZ)会不定期变化,因此上个月有效的选择器可能会失效。在浏览器开发者工具中重新检查实时页面,在 parseDataFromHTML 中更新选择器,然后就可以继续了。定期维护选择器是任何生产爬虫的正常工作。
抓取 Glassdoor 会被封锁吗?
如果你从单个地址发出过多请求,可能会被封锁。Crawling API 通过轮换住宅 IP 并为你处理 CAPTCHA 来降低这种风险,但你仍应控制请求节奏,在页面之间添加延迟,并关注状态码,以便在挑战出现时退让。这些习惯对任何强防护的商业目标都很重要。
我可以抓取个人员工评论和评论者姓名吗?
这超出了本指南的范围,并有充分的理由。个人评论及其撰写者属于个人数据,这会触及 GDPR 和 CCPA 等隐私法律。将公开公司评分作为关于雇主的汇总信号,不要整合个人评论者的档案,也不要将某人的评论与其身份关联后再发布。超出公开列表数据的需求,请使用 Glassdoor 的官方 API 或合作伙伴计划。
Glassdoor 有官方 API 吗?
Glassdoor 及其母公司运营着官方合作伙伴和 API 计划,在明确条款下提供雇主和列表数据,具有署名规则和定义好的商业权利。如果你需要大量数据、有保障的结构或商业再使用权,这种授权途径才是正确的选择。本指南的公开数据爬虫最适合研究、原型制作和小规模分析,即不需要正式协议的场景。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

