免责声明:本文仅讨论对公开可访问数据的合规采集。在抓取任何平台之前,请仔细阅读其服务条款(ToS)。在美国,《计算机欺诈和滥用法》(CFAA)可能限制未授权的自动化访问;在欧盟,GDPR对涉及欧盟雇员的个人数据处理有严格要求。ProxyHat不鼓励任何违反平台条款或适用法律的行为。
如果你正在为HR分析平台或劳动力市场报告构建数据管道,抓取Glassdoor(scrape Glassdoor)几乎是绕不开的话题。Glassdoor拥有超过1亿条公司评论和薪资数据点,但它的反爬栈——DataDome加Cloudflare Bot Management——也是业内最复杂的之一。本指南将带你从公开评论页面走到BFF GraphQL端点,用可运行的Python代码和住宅代理完成一次完整的采集流程。
一、Glassdoor上哪些数据公开、哪些需要登录
Glassdoor的数据可访问性分为三层:
- 完全公开(无需登录):公司概览页面(
/Overview/<company>-EI_IE<id>.htm)和评论列表页(/Reviews/<company>-Reviews-E<id>.htm)。评论列表会展示截断的评论节点,包含ratingOverall、pros、cons、jobTitle等字段。 - 软墙(需免费注册):完整评论正文、部分评论的详细子评分。Glassdoor会弹出“注册以继续阅读”的模态框,技术上属于登录墙后的内容。
- 硬墙(需登录):完整的薪资明细页面(
/Salary/<company>-Salaries-E<id>.htm)中按岗位、地点、年限拆分的薪资分布表。
本指南仅聚焦第一层——公开评论列表页和BFF GraphQL端点返回的结构化截断评论。不要尝试用自动化脚本登录后抓取薪资明细,这几乎必然违反ToS,且DataDome会在登录态下更激进地检测异常行为。
二、Glassdoor反爬技术栈:DataDome + Cloudflare + TLS指纹
Glassdoor的反爬体系由三层构成:
- DataDome——基于IP信誉、行为特征和设备指纹的实时反爬服务。DataDome会在首次请求时下发
datadomeCookie,后续请求需携带该Cookie并通过其JS挑战刷新。 - Cloudflare Bot Management——在边缘节点执行JS挑战和TLS指纹检测。Cloudflare会检查客户端的JA3/JA4指纹是否匹配已知浏览器。
- 自定义请求头校验——BFF GraphQL端点要求
gd-csrf-token头,该token由前端JS在页面加载时生成。
为什么普通的 requests 或 curl 会失败?因为Python的 requests 库底层使用OpenSSL,其TLS ClientHello与Chrome浏览器差异巨大,Cloudflare的JA3指纹检测会在TCP握手阶段就拒绝连接。解决方案是使用 curl_cffi——一个基于curl-impersonate的Python库,能够模拟Chrome的TLS指纹。
| 工具 | TLS指纹模拟 | JS挑战处理 | Cookie管理 | 推荐场景 |
|---|---|---|---|---|
| requests | 否 | 否 | 手动 | 不推荐 |
| curl_cffi | 是(Chrome) | 部分(需配合) | 自动 | 首选 |
| Playwright | 是 | 是 | 自动 | 高并发成本高 |
三、BFF GraphQL端点:结构化评论的捷径
Glassdoor前端使用BFF(Backend-for-Frontend)架构,页面数据通过未公开的GraphQL端点获取:
https://www.glassdoor.com/bff/graphql——主要评论查询端点https://www.glassdoor.com/graph——备用GraphQL端点
这些端点返回JSON而非HTML,省去了解析复杂DOM的步骤。关键要求:
- 必须携带
gd-csrf-token头,token值可从评论列表页的HTML中提取(通常在<script>标签内的JSON配置中)。 - 必须携带从首次页面请求获取的
datadomeCookie。 - 请求体是标准GraphQL
query+variables结构。
为什么住宅代理比数据中心代理更适合对抗DataDome?DataDome的IP信誉库会标记已知的云服务商IP段(AWS、GCP、Azure等),数据中心IP通常在3-5个请求后即被拦截。住宅IP来自真实ISP,信誉评分高,配合sticky session可在单个IP上维持50-100个请求的会话窗口。
四、Python实战:通过ProxyHat抓取一条评论
以下示例使用 curl_cffi 通过ProxyHat住宅代理抓取Glassdoor BFF GraphQL端点,解析一条截断评论节点。
import re, json, time
from curl_cffi import requests as cffi_requests
# ProxyHat 住宅代理配置(sticky session)
PROXY_URL = "http://user-session-glassdoor01:pass@gate.proxyhat.com:8080"
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",
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": "https://www.glassdoor.com",
"Referer": "https://www.glassdoor.com/",
}
# 第一步:访问评论列表页,获取 csrf token 和 datadome cookie
listings_url = "https://www.glassdoor.com/Reviews/Microsoft-Reviews-EI_IE1621.htm"
resp = cffi_requests.get(
listings_url,
headers=HEADERS,
proxies={"http": PROXY_URL, "https": PROXY_URL},
impersonate="chrome120",
timeout=30,
)
# 从 HTML 中提取 gd-csrf-token
csrf_match = re.search(r'"csrfToken":"([^"]+)"', resp.text)
csrf_token = csrf_match.group(1) if csrf_match else ""
# datadome cookie 由 curl_cffi 自动保存
cookies = resp.cookies
# 第二步:POST 到 BFF GraphQL 端点
graphql_url = "https://www.glassdoor.com/bff/graphql"
query = """
query EmployerReviews($input: ReviewsInput!) {
employerReviews(input: $input) {
reviews {
reviewId
ratingOverall
pros
cons
jobTitle
date
}
pageInfo { nextCursor }
}
}
"""
variables = {
"input": {
"employerId": 1621,
"cursor": None,
"limit": 10
}
}
headers = {
**HEADERS,
"gd-csrf-token": csrf_token,
}
graphql_resp = cffi_requests.post(
graphql_url,
headers=headers,
json={"query": query, "variables": variables},
cookies=cookies,
proxies={"http": PROXY_URL, "https": PROXY_URL},
impersonate="chrome120",
timeout=30,
)
data = graphql_resp.json()
reviews = data.get("data", {}).get("employerReviews", {}).get("reviews", [])
for r in reviews[:1]:
print(f"评分: {r['ratingOverall']}")
print(f"职位: {r['jobTitle']}")
print(f"优点: {r['pros'][:100]}...")
print(f"缺点: {r['cons'][:100]}...")
Node.js 版本(使用 undici + ProxyAgent)
import { request } from 'undici';
import { ProxyAgent } from 'undici';
const proxyAgent = new ProxyAgent('http://user-session-glassdoor01:pass@gate.proxyhat.com:8080');
const resp = await request('https://www.glassdoor.com/bff/graphql', {
method: 'POST',
dispatcher: proxyAgent,
headers: {
'Content-Type': 'application/json',
'gd-csrf-token': csrfToken,
'User-Agent': 'Mozilla/5.0 ... Chrome/120.0.0.0',
},
body: JSON.stringify({ query, variables }),
});
const data = await resp.body.json();
console.log(data.data.employerReviews.reviews[0]);
五、分页、会话管理与重试策略
分页游标(Pagination Cursors)
BFF GraphQL端点使用游标分页。响应中的 pageInfo.nextCursor 字段包含下一页的游标值。将此值传入 variables.input.cursor 即可获取下一批评论。当 nextCursor 为 null 时,表示已到末尾。
Sticky Session保持DataDome Cookie有效
DataDome的 datadome Cookie与IP绑定。如果在请求过程中切换IP,Cookie会立即失效并触发JS挑战。因此,整个分页流程必须使用同一个住宅IP。在ProxyHat中,通过用户名中的 session- 标志实现sticky session:
# 同一会话保持同一出口IP
PROXY_URL = "http://user-session-glassdoor-batch01:pass@gate.proxyhat.com:8080"
一个sticky session建议在50-80个请求后轮换,避免DataDome行为分析触发拦截。
请求间隔与重试
import random, time
def fetch_with_retry(cursor, max_retries=3):
for attempt in range(max_retries):
try:
resp = cffi_requests.post(
graphql_url,
headers=headers,
json=build_query(cursor),
cookies=cookies,
proxies={"http": PROXY_URL, "https": PROXY_URL},
impersonate="chrome120",
timeout=30,
)
if resp.status_code == 200:
return resp.json()
elif resp.status_code == 403:
# DataDome 拦截 — 轮换 session
print(f"403 拦截,轮换 session...")
time.sleep(random.uniform(5, 10))
# 重新初始化 session
return None
else:
time.sleep(random.uniform(2, 5))
except Exception as e:
print(f"请求失败: {e}")
time.sleep(random.uniform(3, 8))
return None
# 主循环:每页间隔 3-7 秒
cursor = None
while True:
data = fetch_with_retry(cursor)
if not data:
break
reviews = extract_reviews(data)
save_to_db(reviews)
cursor = get_next_cursor(data)
if not cursor:
break
time.sleep(random.uniform(3, 7)) # 人性化延迟
六、常见错误与边缘情况
- JA3指纹不匹配:即使使用curl_cffi,如果
impersonate版本与你的User-Agent声明不一致,Cloudflare仍会拦截。确保chrome120对应的UA字符串匹配。 - CSRF Token过期:
gd-csrf-token有时效性,通常在30-60分钟后失效。长时间运行的采集任务需定期重新访问列表页刷新token。 - DataDome JS挑战:当收到HTTP 403且响应体包含
_dd_前缀的JSON时,说明触发了JS挑战。此时必须轮换IP和session,重新初始化整个流程。 - 地理限制:部分公司评论仅在特定地区可见。使用ProxyHat的地理定位功能:
user-country-US-session-abc:pass@gate.proxyhat.com:8080。
七、ProxyHat配置详解
ProxyHat住宅代理通过 gate.proxyhat.com:8080(HTTP)或 gate.proxyhat.com:1080(SOCKS5)提供接入。所有控制参数通过用户名字段传递:
| 参数 | 示例 | 说明 |
|---|---|---|
| 国家定位 | user-country-US | 出口IP位于美国 |
| 城市定位 | user-country-US-city-seattle | 精确到城市 |
| Sticky Session | user-session-abc123 | 保持同一出口IP |
| 组合使用 | user-country-US-session-gd01 | 美国IP + 固定会话 |
完整接入文档请参考 ProxyHat官方文档。定价信息见 ProxyHat定价页。如需了解更多抓取场景,可查看我们的 网页抓取用例 和 SERP追踪用例。支持的国家和地区列表见 ProxyHat位置页。
八、伦理抓取与何时使用官方API
抓取Glassdoor数据时,请遵循以下原则:
- 仅采集聚合、非个人数据。评论本身由用户自愿发布在公开平台,但评论中可能包含可识别个人的信息(如特定经理姓名)。在存储和分析时应做脱敏处理。
- 不抓取登录墙后的内容。完整薪资明细、个人资料页等需要登录才能访问的内容,不应通过自动化手段获取。这不仅违反ToS,还可能触及CFAA的“超出授权访问”条款。
- GDPR合规。如果分析对象包含欧盟雇员的评论,需注意GDPR对个人数据处理的要求。评论虽然公开,但系统化收集并存储仍可能构成数据处理行为。建议仅存储聚合统计(如平均评分、评论数量),而非原始评论文本。
- 尊重robots.txt。Glassdoor的 robots.txt 明确限制了部分路径的爬取。在开始前请检查并遵守。
- 考虑官方数据许可。如果你的产品面向商业客户或需要大规模数据,Glassdoor提供 官方API和数据许可方案。对于企业级HR分析产品,这是最合规且可持续的选择。
关键要点:公开评论列表页可合规抓取,但需对抗DataDome和Cloudflare反爬栈。使用curl_cffi模拟Chrome TLS指纹,配合ProxyHat住宅代理的sticky session维持会话。BFF GraphQL端点提供结构化JSON输出,比HTML解析更可靠。始终遵守平台ToS和适用法律,优先考虑官方API。
九、总结
Glassdoor评论抓取器(Glassdoor reviews scraper)的构建核心在于三层:TLS指纹模拟(curl_cffi)、IP信誉管理(住宅代理 + sticky session)、和端点选择(BFF GraphQL优于HTML解析)。对于 Glassdoor薪资数据(Glassdoor salary data),由于完整薪资明细位于登录墙后,建议通过官方渠道获取或仅使用公开评论中的薪资提及做估算。
ProxyHat住宅代理提供195+国家覆盖、99.9%正常运行时间和灵活的sticky session控制,适合需要稳定大规模采集的数据工程团队。开始你的第一个采集任务前,请务必完成法律合规审查。






