How to scrape Glassdoor at scale in 2026 with proxies that work
Glassdoor is one of the harder scraping targets I deal with, right up there with LinkedIn. It gates most of its useful content (reviews, salary ranges, interview questions) behind a login wall, throttles aggressively once it flags a session as automated, and rotates its markup often enough that selector-based scrapers break every few months. I’ve run Glassdoor pulls for a few clients doing comp benchmarking and employer brand monitoring, and the failure mode is always the same: someone builds a scraper on a residential IP from their laptop, it works for 200 requests, then the account and the IP both get burned.
This tutorial is for people who need Glassdoor data on a recurring basis, HR analytics teams, recruiters building comp models, market researchers tracking employer sentiment, not for a one-off pull of a few pages. I’ll walk through the proxy and browser setup that actually holds up, the login and rate-limit problems specific to Glassdoor, and what changes as you scale from a few hundred pages a day to tens of thousands.
One thing up front: this is not legal advice. Glassdoor’s terms of use prohibit automated data collection, and scraping in violation of a site’s terms can expose you to a breach-of-contract claim even where the underlying data is public. The Ninth Circuit’s ruling in hiQ Labs v. LinkedIn established that scraping publicly accessible data generally doesn’t violate the Computer Fraud and Abuse Act, but that case didn’t touch contract law, and it doesn’t cover data sitting behind a login wall, which is most of what makes Glassdoor useful. Read the terms, talk to a lawyer if you’re doing this commercially, and decide your risk tolerance before you build anything.
what you need
- A headless browser toolkit: I use Playwright for Glassdoor because its network interception and stealth patching are better maintained than Selenium’s for this kind of target.
- Rotating residential or mobile proxies. Datacenter IPs get flagged within a handful of requests on Glassdoor. Budget roughly $4-15 per GB depending on provider and geo-targeting needs; I’ve covered the pricing tradeoffs in more detail in the Decodo review.
- At least one legitimate Glassdoor account per concurrent session if you need review or salary detail beyond the free teaser (Glassdoor gates full content behind a “sign in to see more” wall).
- A queue and storage layer, Redis or SQS for the job queue, Postgres or a flat data lake for output. Anything heavier is overkill under a few hundred thousand rows.
- A proxy pool sized to your concurrency, not your total volume. 20-50 rotating residential IPs comfortably supports single-digit concurrent sessions.
- Budget for infrastructure: figure $50-150/month in proxy bandwidth for a modest run (a few thousand company pages a month), scaling from there.
step by step
1. Define exactly what data you need
Glassdoor splits into distinct data types: company overview pages, reviews, salary ranges, interview questions, and job listings. Each has a different URL pattern and a different gating level (overview pages are mostly open, reviews and salaries require login). Decide upfront which of these you actually need, because building a scraper for all five at once triples your surface area for breakage.
Expected output: a short spec, list of target URL patterns and the fields you want from each.
If it breaks: if you’re not sure which pages are public vs. gated, load a target company page in an incognito browser window first and see what renders before you sign in.
2. Check robots.txt and terms before writing a line of code
Glassdoor’s robots.txt disallows crawling of most dynamic paths, and its terms of use explicitly prohibit scraping. The Robots Exclusion Protocol (RFC 9309) isn’t legally binding on its own, but ignoring it removes any good-faith argument you might otherwise have. Log what’s disallowed so you know which paths you’re deliberately stepping outside of, and make that a documented decision, not an accident.
Expected output: a written note of which paths you’re targeting relative to what robots.txt disallows.
If it breaks: if legal exposure is a concern for your use case, this is the point to loop in counsel, not after you’ve got 50,000 rows.
3. Set up rotating residential proxies
Pick a provider with sticky sessions (same IP for the duration of a login session, since Glassdoor will log you out if your IP changes mid-session) and per-request rotation available for the anonymous pages. I’ve had good results with Decodo and Smartproxy for this kind of target; I compared the two directly in Decodo vs Smartproxy. Configure your proxy pool with geo-targeting matched to the accounts you’re using, a US account logging in from a Singapore IP is one of the fastest ways to trigger a challenge.
# example proxy config, sticky session for authenticated pages
export PROXY_HOST="gate.smartproxy.com"
export PROXY_PORT="7000"
export PROXY_USER="user-session-abc123"
export PROXY_PASS="yourpassword"
Expected output: a proxy endpoint that returns a consistent IP across repeated requests within one session.
If it breaks: if your IP is changing mid-session despite a sticky config, check that your session ID string is actually static across requests, most providers key stickiness off that string.
4. Build the browser scraper with Playwright
Use a real headless (or headed, for the login flow) browser rather than raw HTTP requests. Glassdoor’s content is heavily JS-rendered and its bot detection checks browser fingerprint signals that a plain requests-based scraper can’t fake.
from playwright.sync_api import sync_playwright
def fetch_company_page(url, proxy):
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={"server": f"http://{proxy['host']}:{proxy['port']}",
"username": proxy["user"], "password": proxy["pass"]},
headless=True,
)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
viewport={"width": 1366, "height": 768},
)
page = context.new_page()
page.goto(url, wait_until="networkidle")
html = page.content()
browser.close()
return html
Expected output: rendered HTML with the full page content, not the skeleton served to bots.
If it breaks: if you’re getting a blank or teaser-only page, check whether Glassdoor served a login redirect, print page.url after navigation to confirm.
5. Handle the login wall
For reviews and salary detail, you’ll need an authenticated session. Log in once per session using a real account (recruiter or business accounts work fine, don’t fabricate identities to open dozens of accounts, that’s a fast route to a permanent IP and account ban across your whole pool, and it’s the kind of pattern anti-fraud systems are built to catch). Save the session cookies and reuse them rather than logging in on every request.
Expected output: a cookie/session file you can inject into new Playwright contexts to skip repeated logins.
If it breaks: if sessions expire faster than expected, check for a “verify it’s you” email challenge, Glassdoor triggers these when login location or device fingerprint shifts.
6. Deal with bot-detection challenges
Glassdoor runs behavioral bot detection that looks at mouse movement, timing between actions, and browser fingerprint consistency, not just IP reputation. Randomize navigation timing (don’t goto every URL at a fixed interval), and consider running your sessions through an anti-detect browser profile manager if you’re operating more than a handful of concurrent identities. I’ve written more on fingerprint isolation tooling on antidetectreview.org, which is worth a look if this becomes your bottleneck.
Expected output: pages loading normally without a CAPTCHA or “unusual traffic” interstitial.
If it breaks: if you’re consistently hitting a challenge page, slow down your request rate first, it’s the cheapest fix before you touch fingerprinting.
7. Parse and structure the data
Once you have rendered HTML, extract fields with CSS selectors or, more reliably, by pulling the embedded JSON that Glassdoor’s React app hydrates from (search the page source for window.__INITIAL_STATE__ or similar embedded state objects, which are more stable across UI changes than CSS classes).
import re, json
def extract_embedded_state(html):
match = re.search(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});', html, re.S)
return json.loads(match.group(1)) if match else None
Expected output: a structured dict of review text, ratings, and metadata per record.
If it breaks: if the embedded state variable name has changed, view source on a fresh page load and search for large inline <script> blocks, Glassdoor still ships most of its data this way even after markup changes.
8. Throttle, rotate, and store
Cap concurrency per proxy IP (I keep it to 1-2 requests per IP per minute for authenticated pages), rotate IPs on a schedule rather than per-request for sticky sessions, and write results to your database incrementally so a crash mid-run doesn’t cost you the whole batch.
Expected output: a steady, low error-rate ingestion pipeline writing to your storage layer.
If it breaks: if your block rate climbs above roughly 5%, pause and widen your proxy pool or slow your request cadence before pushing more volume.
common pitfalls
- Using datacenter proxies to save money. They’re cheaper per GB but get flagged almost immediately on Glassdoor. It’s a false economy once you count re-scraping and account churn.
- Scraping at a constant, mechanical interval. Fixed 2-second gaps between requests are one of the easier behavioral signals to detect. Randomize.
- Ignoring cookie and session persistence. Logging in fresh on every request multiplies your login-challenge exposure for no benefit.
- Not tracking block rate as a metric. If you’re not logging 403s, CAPTCHA hits, and login challenges per proxy, you won’t notice degradation until the whole pool is burned.
- Storing scraped review text with reviewer-identifying metadata you don’t need. Keep your dataset to what you’ll actually use; unnecessary PII retention is a liability with no upside.
scaling this
At 10x (a few thousand pages a day), a single machine, a 20-30 IP rotating proxy pool, and a basic job queue is enough. Concurrency of 3-5 sessions keeps block rates low.
At 100x (tens of thousands of pages a day), you need a proper distributed queue (SQS or a self-hosted alternative), a proxy pool in the low hundreds of IPs with geo-diversity matched to your account pool, and monitoring on block rate per proxy so you can retire bad IPs automatically. This is also where sticky-session cost starts to matter, budget accordingly on your proxy contract.
At 1000x, you’re running a scraping operation, not a script. Expect to need a dedicated proxy management layer (rotation logic, health checks, automatic retirement of flagged IPs), multiple isolated account pools, and a review process for markup changes since a selector or embedded-state break now costs you a day of missed data instead of an afternoon. At this scale it’s also worth revisiting the legal question from step 2 with actual counsel, since volume and commercial use both increase exposure.
where to go next
If Glassdoor is one of several people-data or company-data sources you’re pulling from, check out my guide on scraping Crunchbase at scale, the login and rate-limit patterns are similar. For the proxy side specifically, best proxies for scraping LinkedIn covers a lot of the same rotating-residential vendor tradeoffs that apply here. And for the full archive, browse the rest of the tutorials on the blog.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-07-15.