法律与合规声明:本文仅讨论对公开可访问数据的合规采集。在抓取Glassdoor数据之前,你必须仔细阅读并遵守Glassdoor的服务条款(Terms of Service)。在美国,未经授权访问受保护计算机系统可能违反《计算机欺诈和滥用法》(CFAA, 18 U.S.C. § 1030);在欧盟,处理与可识别自然人相关的数据时须遵守《通用数据保护条例》(GDPR)。本文不鼓励、不教授任何绕过登录墙或抓取需认证才能访问的数据的方法。
对于HR分析团队和劳动力市场数据工程师来说,Glassdoor是雇主品牌情报和薪资基准数据的宝库。平台拥有超过1亿条公司评论和薪资数据点,覆盖全球190多个国家。然而,Glassdoor部署了多层反爬系统——DataDome和Cloudflare Bot Management——使得传统的requests库几乎无法工作。本文将深入讲解2026年如何抓取Glassdoor公司评论和薪资数据,包括BFF GraphQL端点的调用方法、TLS指纹伪装、以及住宅代理轮换策略。
如何抓取Glassdoor公司评论和薪资数据:技术概览
Glassdoor的数据访问分为两个层级:公开可访问和登录墙后。理解这一边界是合规抓取的前提。
公开可访问的数据
以下内容在未登录状态下可见,URL格式为 /Reviews/<company>-Reviews-E<id>.htm:
- 公司概览页(行业、规模、总部位置)
- 截断的评论列表(每页约10条,显示pros/cons摘要)
- 总体评分(ratingOverall)和分项评分
- 职位标题和评论日期
登录墙后的数据(本文不涉及)
以下数据需要登录账户才能访问,属于Glassdoor的付费/注册内容:
- 完整的薪资明细(base + bonus + stock拆分)
- 完整评论正文(超过截断长度的部分)
- 面试问题详情
- 个人评论者的详细资料
关键原则:如果你的目标数据需要登录才能看到,你应该使用Glassdoor官方API或数据授权计划,而不是绕过认证。本文的方法仅适用于公开页面和公开API端点返回的数据。
Glassdoor的反爬技术栈:DataDome + Cloudflare
Glassdoor使用了至少两层反爬系统,这也是为什么简单的 requests.get() 会返回403或CAPTCHA页面的原因。
第一层:Cloudflare Bot Management
Cloudflare在边缘节点执行JS挑战和TLS指纹检测。它会检查客户端的JA3/JA4 TLS指纹——这是TLS握手期间ClientHello包的特征哈希。Python标准 requests 库使用的urllib3底层TLS栈产生的指纹与真实浏览器截然不同,因此在TLS握手阶段就会被拦截,响应延迟通常在50ms以内就被拒绝。了解更多关于Cloudflare Bot Management的技术细节,参见Cloudflare官方文档。
第二层:DataDome
通过Cloudflare后,请求进入DataDome的检测层。DataDome使用机器学习模型对以下信号进行评分:
- IP信誉评分(数据中心IP几乎自动标记为高风险)
- 浏览器指纹一致性(User-Agent与TLS指纹是否匹配)
- 请求模式(速率、路径序列、鼠标行为信号)
- Cookie一致性(datadome cookie是否与IP绑定)
DataDome的IP评分系统对数据中心IP段有极高的识别率。这就是为什么住宅代理是抓取Glassdoor的必要条件——住宅IP来自真实ISP分配段,在DataDome的IP信誉库中风险评分较低。详见DataDome官方说明。
解决方案:curl_cffi + 住宅代理
curl_cffi 是一个基于libcurl的Python库,能够模拟Chrome的TLS握手特征。它通过底层调用curl-impersonate项目,使JA3/JA4指纹与真实Chrome浏览器一致,从而绕过Cloudflare的TLS指纹检测。这是抓取Glassdoor的基础工具。
BFF GraphQL端点:结构化数据的来源
Glassdoor的前端使用BFF(Backend for Frontend)架构,通过GraphQL端点获取结构化数据。虽然这些端点未公开文档化,但可以通过浏览器开发者工具在网络请求中观察到。
关键端点
/graph— 主GraphQL端点,接受POST请求,返回JSON格式的评论数据/bff/— BFF代理层端点,部分请求路径不同但返回类似结构
必需请求头
调用GraphQL端点时,以下请求头是必需的:
gd-csrf-token— CSRF令牌,可从页面HTML中的<meta>标签或内联JS中提取Content-Type: application/json- 有效的
datadomecookie(通过初始页面访问获取) - 与浏览器一致的
User-Agent
典型的GraphQL查询结构如下(仅示例公开评论字段):
query GetReviews($employerId: ID!, $cursor: String) {
employerReviews(employerId: $employerId, after: $cursor) {
edges {
node {
ratingOverall
pros
cons
jobTitle {
name
}
reviewDateTime
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
实战:Python抓取Glassdoor评论的完整示例
以下示例展示如何通过ProxyHat住宅代理和BFF GraphQL端点抓取公开评论数据。代码使用curl_cffi进行TLS伪装,通过ProxyHat网关轮换IP。
步骤1:安装依赖
pip install curl_cffi
步骤2:通过ProxyHat住宅代理调用GraphQL端点
import json
import time
import re
from curl_cffi import requests
# ProxyHat 住宅代理配置
PROXY_URL = "http://user-country-US-session-glassdoor1:YOUR_PASSWORD@gate.proxyhat.com:8080"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/131.0.0.0 Safari/537.36",
"Content-Type": "application/json",
"Accept": "application/json",
}
EMPLOYER_ID = "4138" # 示例:某公司ID
# Step 1: 访问公开评论页,获取 csrf token 和 datadome cookie
review_url = f"https://www.glassdoor.com/Reviews/example-company-Reviews-E{EMPLOYER_ID}.htm"
session = requests.Session(impersonate="chrome")
resp = session.get(review_url, proxies={"https": PROXY_URL, "http": PROXY_URL}, timeout=30)
if resp.status_code != 200:
print(f"初始页面请求失败: {resp.status_code}")
exit(1)
# 从HTML中提取 gd-csrf-token
csrf_match = re.search(r'gd-csrf-token["\']?\s*[:=]\s*["\']?([a-f0-9-]+)', resp.text)
if not csrf_match:
# 备选:从meta标签提取
csrf_match = re.search(r'name="csrf"\s+content="([^"]+)"', resp.text)
csrf_token = csrf_match.group(1) if csrf_match else ""
print(f"CSRF Token: {csrf_token[:8]}...")
# Step 2: 调用 BFF GraphQL 端点获取结构化评论
graphql_url = "https://www.glassdoor.com/graph"
query = """
query GetReviews($employerId: ID!, $cursor: String) {
employerReviews(employerId: $employerId, after: $cursor, first: 10) {
edges {
node {
ratingOverall
pros
cons
jobTitle { name }
reviewDateTime
}
}
pageInfo { endCursor hasNextPage }
}
}
"""
payload = {
"query": query,
"variables": {"employerId": EMPLOYER_ID, "cursor": None},
"operationName": "GetReviews",
}
headers = {**HEADERS, "gd-csrf-token": csrf_token}
resp = session.post(
graphql_url,
json=payload,
headers=headers,
proxies={"https": PROXY_URL, "http": PROXY_URL},
timeout=30,
)
data = resp.json()
reviews = data.get("data", {}).get("employerReviews", {})
for edge in reviews.get("edges", []):
node = edge["node"]
print(f"评分: {node['ratingOverall']} | 职位: {node['jobTitle']['name']}")
print(f" 优点: {node['pros'][:80]}...")
print(f" 缺点: {node['cons'][:80]}...")
print(f" 日期: {node['reviewDateTime']}")
print("---")
page_info = reviews.get("pageInfo", {})
print(f"下一页游标: {page_info.get('endCursor')}")
print(f"是否有更多: {page_info.get('hasNextPage')}")
Node.js 示例:使用 SOCKS5 代理
const { SOCKSProxyAgent } = require('socks-proxy-agent');
const fetch = require('node-fetch');
const proxyUrl = 'socks5://user-country-US-session-glassdoor1:YOUR_PASSWORD@gate.proxyhat.com:1080';
const agent = new SOCKSProxyAgent(proxyUrl);
async function fetchReviews(employerId, cursor = null) {
const query = `
query GetReviews($employerId: ID!, $cursor: String) {
employerReviews(employerId: $employerId, after: $cursor, first: 10) {
edges { node { ratingOverall pros cons jobTitle { name } } }
pageInfo { endCursor hasNextPage }
}
}`;
const resp = await fetch('https://www.glassdoor.com/graph', {
method: 'POST',
agent,
headers: {
'Content-Type': 'application/json',
'gd-csrf-token': process.env.GD_CSRF_TOKEN,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
},
body: JSON.stringify({
query,
variables: { employerId, cursor },
operationName: 'GetReviews',
}),
});
return resp.json();
}
fetchReviews('4138').then(console.log);
分页、粘性会话与速率控制
游标分页
Glassdoor的GraphQL使用Relay风格的游标分页。每次响应中的 pageInfo.endCursor 是下一页的游标值,将其作为下一次请求的 after 参数传入即可。当 hasNextPage 为 false 时,分页结束。
粘性会话(Sticky Session)
DataDome的 datadome cookie与IP地址绑定。如果在获取cookie后切换IP,DataDome会立即失效并要求重新验证。因此,在同一个公司的完整抓取流程中,必须使用粘性会话保持同一出口IP。
在ProxyHat中,通过在用户名中添加 -session- 标志实现粘性会话:
# 同一会话保持同一IP
proxy_a = "http://user-country-US-session-companyA123:PASSWORD@gate.proxyhat.com:8080"
proxy_b = "http://user-country-US-session-companyB456:PASSWORD@gate.proxyhat.com:8080"
# companyA123 和 companyB456 是不同的会话ID,分配不同IP
请求节奏与重试逻辑
建议的请求间隔为3-5秒每次请求,以模拟人类浏览行为。以下是一个带重试逻辑的分页循环示例:
import time
import random
def scrape_all_reviews(session, proxy_url, employer_id, csrf_token, max_pages=50):
all_reviews = []
cursor = None
for page in range(max_pages):
payload = {
"query": query,
"variables": {"employerId": employer_id, "cursor": cursor, "first": 10},
"operationName": "GetReviews",
}
headers = {**HEADERS, "gd-csrf-token": csrf_token}
for attempt in range(3):
try:
resp = session.post(
"https://www.glassdoor.com/graph",
json=payload,
headers=headers,
proxies={"https": proxy_url, "http": proxy_url},
timeout=30,
)
if resp.status_code == 200:
data = resp.json()
reviews = data["data"]["employerReviews"]
all_reviews.extend([e["node"] for e in reviews["edges"]])
if not reviews["pageInfo"]["hasNextPage"]:
return all_reviews # 分页结束
cursor = reviews["pageInfo"]["endCursor"]
break # 成功,跳出重试循环
elif resp.status_code == 403:
print(f"第{page}页被拦截,尝试{attempt+1}/3")
time.sleep(10 * (attempt + 1)) # 指数退避
continue
except Exception as e:
print(f"请求异常: {e}")
time.sleep(5 * (attempt + 1))
else:
print(f"第{page}页连续3次失败,终止")
break
# 节奏控制:3-5秒随机延迟
time.sleep(random.uniform(3, 5))
return all_reviews
常见错误与边界情况
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 403 Forbidden | DataDome IP评分过高或TLS指纹不匹配 | 切换为住宅代理 + 确认curl_cffi的impersonate="chrome"生效 |
| 400 Bad Request | gd-csrf-token缺失或过期 | 重新访问评论页获取新token |
| 空响应/超时 | 出口IP被临时封锁 | 更换session ID获取新IP,等待60秒后重试 |
| CAPTCHA页面 | 请求频率过高触发人机验证 | 降低请求速率至每5秒1次,使用粘性会话减少IP切换 |
| GraphQL schema错误 | 字段名变更或端点结构调整 | 通过浏览器DevTools重新检查最新查询结构 |
ProxyHat配置与最佳实践
ProxyHat提供住宅、移动和数据中心代理。对于Glassdoor抓取,住宅代理是唯一推荐类型,因为DataDome对数据中心IP的拦截率极高。
连接参数
- HTTP网关:
gate.proxyhat.com:8080 - SOCKS5网关:
gate.proxyhat.com:1080 - 国家定位:
user-country-US:password@gate.proxyhat.com:8080 - 城市定位:
user-country-US-city-sanfrancisco:password@gate.proxyhat.com:8080 - 粘性会话:
user-session-myID123:password@gate.proxyhat.com:8080
查看完整代理位置列表请访问 ProxyHat代理位置,了解定价方案参见 ProxyHat定价。更多关于 网页抓取用例 和 SERP追踪 的技术细节可参考我们的用例文档。完整API文档见 ProxyHat官方文档。
并发与性能建议
- 单会话建议保持≤5个并发请求
- 多公司并行抓取时,为每个公司分配独立的session ID
- 监控成功率指标,低于90%时降低并发或增加延迟
- 目标延迟控制在200-500ms范围内(代理转发延迟)
伦理抓取与何时使用官方API
数据采集原则
- 仅采集聚合、非个人数据:公司评分分布、薪资范围统计、评论趋势——而非单条评论中的个人身份信息
- 不抓取登录墙后数据:完整薪资明细、面试详情等需要认证的内容应通过官方渠道获取
- 遵守robots.txt:检查
https://www.glassdoor.com/robots.txt并尊重其中的Disallow规则 - GDPR考量:评论中可能包含欧盟用户的个人信息(如评论者职位+公司可间接识别个人)。对欧盟公司数据进行匿名化处理,移除可能关联到特定员工的信息
何时应该使用官方数据授权
以下场景中,Glassdoor的官方数据授权计划是更合适的选择:
- 需要大规模、系统性地获取薪资明细数据
- 需要将数据用于商业产品或公开报告
- 需要历史数据快照或定期数据交付
- 团队没有维护反爬绕过基础设施的工程能力
官方数据授权虽然成本更高,但提供法律保障、数据质量保证和稳定的交付SLA。对于HR分析团队来说,这通常是总拥有成本更低的选择。
关键要点
- 仅抓取公开可访问的数据——公司概览、截断评论、总体评分。登录墙后的完整薪资数据不在本文方法范围内。
- Glassdoor使用DataDome + Cloudflare双重反爬。curl_cffi的Chrome TLS伪装是绕过Cloudflare指纹检测的必要手段。
- BFF GraphQL端点(
/graph)返回结构化JSON数据,需要gd-csrf-token请求头和有效的datadomecookie。- 住宅代理是抓取Glassdoor的必要条件——数据中心IP在DataDome的IP评分系统中几乎自动被拦截。
- 使用粘性会话(
-session-标志)保持DataDome cookie有效,请求间隔控制在3-5秒。- 遵守CFAA和GDPR,仅采集聚合非个人数据,在需要大规模商业数据时优先考虑官方数据授权。






