How to Scrape Glassdoor: A Python Guide (Tested)
- Glassdoor's public jobs search returns real listings inside a JSON-LD block. I pulled 33 jobs per page with plain requests and pasted the output below.
- Salary and company Overview pages also load server-side and parse the same way. Reviews and deep pagination sit behind robots.txt rules and signup walls.
- Scraping public Glassdoor pages likely does not violate the CFAA after hiQ v. LinkedIn (9th Cir., 2022), but it breaches Glassdoor's Terms of Use. Those are separate risks.
- At volume Glassdoor throttles a single IP and serves CAPTCHAs. A scraper API like ChocoData handles the IP rotation and rendering that plain requests cannot.
Glassdoor holds three datasets people actually want to scrape: job postings, salary ranges, and company reviews. It also has a reputation for being hard to scrape, so I spent June 2026 testing what a plain Python client can really pull from a logged-out session. The short version: the public jobs search returns real listings embedded as JSON-LD, and I have the genuine output below. Salary and company pages parse the same way. Reviews and deep pagination are gated. This guide shows exactly where the line sits, with tested code, and where a scraper API takes over.
Can you legally scrape Glassdoor?
Scraping public Glassdoor data sits in two separate legal buckets you weigh independently: US computer-crime law, where the risk is now low, and Glassdoor’s own Terms of Use, which it breaches outright. The governing case is hiQ Labs v. LinkedIn. On April 18, 2022, the Ninth Circuit ruled that scraping publicly available data likely does not violate the Computer Fraud and Abuse Act, because the CFAA’s “without authorization” bar does not apply to data that is open to the public without a login. The case was terminated in December 2022 after a settlement.
That ruling concerns a criminal statute. It says nothing about your contract with Glassdoor. Glassdoor’s Terms of Use prohibit automated collection in plain language. The clauses that matter:
| Glassdoor Terms of Use | What it prohibits (paraphrased) |
|---|---|
| Restrictions on use | Using any robot, spider, scraper, or other automated means to access the site for any purpose without express written permission |
| Restrictions on use | Collecting or harvesting any personally identifiable information, including account names, from the Services |
| Account integrity | Creating accounts by automated means or under false pretenses |
So the practical picture has three layers. CFAA risk on public data is low after hiQ. Contract risk is real: scraping breaches the Terms of Use, and Glassdoor can ban accounts or sue for breach. Data-protection risk is separate again: reviews and salary submissions tie to people, so GDPR and similar laws apply the moment you store EU residents’ data. I cover the general framing in my is web scraping legal guide. For Glassdoor specifically, the safest posture is to scrape only public, aggregate data such as job listings and salary medians, and never log in a bot account to reach gated reviews.
One more primary-source check before any scrape: robots.txt. Glassdoor’s robots.txt disallows several paths for all crawlers. The pagination-heavy review and salary URLs are blocked, while the first salary pages and the jobs search are reachable:
| Path pattern | robots.txt status |
|---|---|
/Reviews/*_P*.htm* and /Reviews/*_IP*.htm* | Disallow |
/Interview/*_P*.htm* and /Interview/*_IP*.htm* | Disallow |
/Salaries/*_IP*.htm* (beyond IP5) | Disallow |
/member/ and /partner/ | Disallow |
/Salaries/*_IP2.htm* through *_IP5.htm* | Allow (explicit) |
robots.txt is a crawling directive rather than a law. Honoring it is still the cheapest way to stay on the right side of “authorized access” arguments and to avoid the paths Glassdoor most aggressively defends.
What data can you actually get from Glassdoor?
You can reliably get public job listings, salary aggregates, and company overview data; reviews come back gated. I tested each surface from a logged-out session in June 2026. Here is what each one returns:
| Surface | Logged-out access | What you get | Notes |
|---|---|---|---|
| Jobs search (SRCH page) | Yes, returns 200 | Title, listing URL, company, location | Embedded as JSON-LD ItemList, ~30 per page |
| Salary page | Yes, returns 200 | Role, average pay, pay range, trends | Server-rendered, JSON-LD present |
| Company Overview | Yes, returns 200 | Name, rating, size, industry, HQ | Server-rendered, multiple JSON-LD blocks |
| Reviews | Teaser only | A few reviews, then a wall | Deep pagination disallowed in robots.txt |
| Interviews | Teaser only | Limited | Pagination disallowed in robots.txt |
The takeaway is to build around jobs and salaries, not reviews. Job postings and salary medians are public by design because Glassdoor wants them indexed. Reviews are the opposite: Glassdoor shows a sample then asks you to contribute your own before you see more, which is its “give to get” model. The next sections show the jobs scraper I tested, then the scraper-API approach for volume.
How do you scrape Glassdoor jobs with Python? (Method 1: DIY)
Fetch the jobs search page, then parse the JSON-LD block Glassdoor embeds, because the listings live in a <script type="application/ld+json"> tag as a schema.org ItemList. You do not need to target individual card divs or run a headless browser. Each ListItem carries the job title in name and the posting URL in url. This is the surface I tested. Here is the exact code I ran:
import json
import requests
from bs4 import BeautifulSoup
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
url = (
"https://www.glassdoor.com/Job/"
"united-states-software-engineer-jobs-SRCH_IL.0,13_IN1_KO14,31.htm"
)
r = requests.get(url, headers=HEADERS, timeout=30)
print("STATUS:", r.status_code)
soup = BeautifulSoup(r.text, "html.parser")
# Glassdoor embeds the result set as JSON-LD (schema.org ItemList).
jobs = []
for tag in soup.find_all("script", type="application/ld+json"):
try:
data = json.loads(tag.string or "")
except (json.JSONDecodeError, TypeError):
continue
for item in data.get("itemListElement", []):
jobs.append({"title": item.get("name"), "url": item.get("url")})
print("jobs in JSON-LD:", len(jobs))
print()
for j in jobs[:5]:
print(j["title"])
The real output, run June 2026:
STATUS: 200
jobs in JSON-LD: 33
Software Developer
Software Engineer (Backend Developer)
Software Engineer III
Software Engineer Staff
Software Engineer
That is genuine data with no login and no proxy. A few things to know. The JSON-LD block held 33 listings in this run; Glassdoor rotates and sorts the result set, so the exact count and titles shift between requests, which is one way you can tell live data from a cached shell. The url field on each item is the canonical /job-listing/... page for that posting, so you can follow it to scrape the full description. The page title itself carries the total match count: in my fetch it read “49,279 software engineer Jobs in United States, June 2026 | Glassdoor”.
The URL encodes the search. The SRCH_IL.0,13 and KO14,31 segments are Glassdoor’s offset markers for the location and keyword strings baked into the slug. The cleanest way to build new searches is to run the query in a browser, copy the resulting SRCH URL, and feed it to the scraper. If you are new to this stack, my BeautifulSoup guide covers parsing in depth, and the CSS selectors guide explains targeting elements when you need to fall back from JSON-LD to raw card divs.
I also confirmed the same pattern on two other surfaces. The salary page (/Salaries/software-engineer-salary-SRCH_KO0,17.htm) returned 200 with the title “Software Engineer: Average Salary & Pay Trends 2026 | Glassdoor” and its own JSON-LD. The company Overview page (/Overview/Working-at-Google-EI_IE9079...) returned 200 with the title “Working at Google | Glassdoor” and four JSON-LD blocks. Both parse with the same json.loads approach.
Why can’t you scrape Glassdoor reviews the same way?
Reviews return a sample then a wall, so a plain GET gives you the first few and a prompt to contribute, not the full set. Glassdoor runs a “give to get” model: read a handful of reviews free, then the site asks you to post your own salary or review before it unlocks more. On top of that, robots.txt disallows the paginated review paths (/Reviews/*_P*.htm* and /Reviews/*_IP*.htm*), so the deeper pages are explicitly off-limits to compliant crawlers.
The data also is not all sitting in the initial HTML waiting to be parsed. Glassdoor pages embed a PerimeterX/HUMAN anti-bot client, and review pagination is gated behind interaction and session signals. A single logged-out request to a review page gives you the public teaser; reaching the full review history means either logging in a bot account, which hits the Terms of Use head-on and is the fastest way to get banned, or rendering the page through an authorized session with rotating IPs. Plain requests against deep review URLs will only ever give you the sample. The same caution applies to interview pages, whose pagination robots.txt also disallows.
How do you scrape Glassdoor at scale? (Method 2: scraper API)
Route the request through a scraper API so it handles IP rotation, JavaScript rendering, and the anti-bot challenge, then you parse the JSON-LD it returns exactly as in Method 1. This is the practical answer for three jobs that defeat plain requests: pulling thousands of listings without tripping Glassdoor’s per-IP throttle, fetching full job-description pages at volume, and getting past the PerimeterX CAPTCHA that appears once you push hard from one address. A scraper API maintains the proxy pool and challenge-solving so you get a usable response instead of a 403.
ChocoData is one such service, with a universal endpoint plus a set of dedicated endpoints. Its request shape is a single GET with your target URL and an API key. Here is the pattern for sending a Glassdoor jobs URL through the universal endpoint with JavaScript rendering on:
import json
import requests
API_KEY = "YOUR_CHOCODATA_KEY"
target = (
"https://www.glassdoor.com/Job/"
"united-states-software-engineer-jobs-SRCH_IL.0,13_IN1_KO14,31.htm"
)
r = requests.get(
"https://api.chocodata.com/api/v1/universal",
params={
"api_key": API_KEY,
"url": target,
"render_js": "true",
},
timeout=60,
)
print("STATUS:", r.status_code)
# r.text holds the rendered HTML; parse the JSON-LD ItemList
# exactly as in Method 1 with json.loads.
I did not run this snippet, since it needs a paid key, so treat the field names as illustrative and confirm the exact parameters against ChocoData’s own docs before you rely on them. The principle holds across any scraper API: you hand it the URL, it returns the page from a rotating residential IP with optional JS rendering, and the throttling and CAPTCHA problems become the vendor’s problem instead of yours. ChocoData advertises residential proxy rotation and optional JavaScript rendering, which are the two features that matter for Glassdoor’s anti-bot client and its JS-hydrated pages.
The honest framing: a scraper API removes the technical block, not the legal one. Routing through proxies does not grant you consent under the Terms of Use, and it does not exempt you from GDPR if you collect reviews tied to people. Use this approach for public, aggregate data such as job listings and salary medians, and apply the same restraint you would to any scraping that needs to avoid getting blocked.
Should you use a scraper API or build your own Glassdoor scraper?
Build your own for public jobs and salaries at low volume; reach for a scraper API for reviews, full job descriptions, or listings by the thousand. The decision comes down to which surface you need and how much of it. Here is how the two approaches line up for Glassdoor specifically:
| Factor | DIY (requests + BeautifulSoup) | Scraper API |
|---|---|---|
| Public jobs, low volume | Works, tested above | Works, overkill |
| Salary and Overview pages | Works, tested above | Works, overkill |
| Jobs at scale (1000s) | Throttled, then CAPTCHA from one IP | Handled via IP rotation |
| Reviews past the teaser | Not accessible | Accessible via rendering |
| PerimeterX challenge | Trips it at volume | Vendor solves it |
| Cost | Free | Paid per request |
| Maintenance | You fix broken selectors | Vendor maintains |
| Terms of Use breach | Yes | Yes |
The pattern I would follow: start with the DIY jobs-and-salaries scraper from Method 1, because it is free and it genuinely works for public, server-rendered pages. Add a scraper API the moment you need review depth, full job descriptions, or volume that one IP cannot sustain before Glassdoor throws a CAPTCHA. Both methods breach Glassdoor’s Terms of Use, so neither is a free pass; scrape only public, aggregate data and you keep the risk where the courts have been most forgiving.
For the fundamentals under all of this, including headers, pagination, and parsing, see the web scraping guide. If you want to build the same scraper inside a larger crawl framework, the Scrapy guide shows how, and the Python scraping guide covers the requests-and-parsing stack end to end.
FAQ
Scraping publicly visible Glassdoor data likely does not violate the US Computer Fraud and Abuse Act, following the Ninth Circuit in hiQ v. LinkedIn (2022), which held that the CFAA's 'without authorization' bar does not cover data open to the public without a login. That is a criminal-statute question. Separately, Glassdoor's Terms of Use prohibit scraping, so collecting data breaches your contract with Glassdoor and can get your account banned or trigger a civil suit. Reviews and salaries are user-generated personal-ish data, so GDPR and similar laws add a third layer for EU residents.
Yes, for the public surfaces. The jobs search, salary pages, and company Overview pages all render server-side and return real data to a logged-out client. I tested each one in June 2026 and got 200s with parseable HTML. Reviews are gated more tightly: Glassdoor shows a few then prompts you to contribute or sign up, and robots.txt disallows the deep review pagination paths.
Yes, once you push volume from one IP. A single well-formed request to a public page returns 200, which is what I observed. Send many requests quickly from the same address and Glassdoor serves a CAPTCHA interstitial or a 403. Its pages also embed a PerimeterX/HUMAN client, so aggressive automated patterns trip an anti-bot challenge. Low and slow from a residential IP is tolerated; high volume needs rotating proxies.
Inside a script tag of type application/ld+json. Glassdoor embeds the search results as a schema.org ItemList, so each listing is a ListItem with a name (the job title) and a url. You do not need to scrape individual card divs or run JavaScript; parse the JSON-LD with json.loads and read itemListElement. That is the cleanest selector and it survives most front-end redesigns.