Scrape Glassdoor Job Listings
Pull job titles, companies, locations, and salary estimates from Glassdoor search results. Glassdoor uses fingerprint checks, behavioral analysis, and CAPTCHAs to block scrapers, so the BQL and Frameworks tabs route through stealth mode and a residential proxy.
- A Browserless API token from your account dashboard
Steps
The examples below navigate to a pre-formed Glassdoor search URL and extract job listing data.
Glassdoor frequently updates its markup. If the selectors below stop returning results, inspect the page with browser DevTools and update the CSS selectors to match the current DOM structure.
- REST API
- Frameworks
- BQL
Send the BQL mutation over HTTP to the stealth endpoint. No browser library or BQL IDE required.
- cURL
- JavaScript
- Python
- Java
- C#
1. Send the request
curl -X POST \
"https://production-sfo.browserless.io/stealth/bql?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation ScrapeGlassdoor { goto(url: \"https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm\", waitUntil: networkIdle) { status } jobs: mapSelector(selector: \"[data-test='\''jobListing'\'']\") { title: mapSelector(selector: \"a[data-test='\''job-title'\'']\") { innerText } company: mapSelector(selector: \"[data-test='\''employer-name'\'']\") { innerText } location: mapSelector(selector: \"[data-test='\''emp-location'\'']\") { innerText } salary: mapSelector(selector: \"[data-test='\''detailSalary'\'']\") { innerText } } }",
"variables": {},
"operationName": "ScrapeGlassdoor"
}'
2. Check the output
{
"data": {
"goto": { "status": 200 },
"jobs": [
{
"title": [{ "innerText": "Software Engineer" }],
"company": [{ "innerText": "TechCorp" }],
"location": [{ "innerText": "New York, NY" }],
"salary": [{ "innerText": "$120K – $180K (Glassdoor est.)" }]
},
{
"title": [{ "innerText": "Senior Software Engineer" }],
"company": [{ "innerText": "BuildCo" }],
"location": [{ "innerText": "New York, NY (Remote)" }],
"salary": [{ "innerText": "$150K – $220K (Glassdoor est.)" }]
}
]
}
}
1. Send the request
const query = `mutation ScrapeGlassdoor {
goto(
url: "https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm"
waitUntil: networkIdle
) {
status
}
jobs: mapSelector(selector: "[data-test='jobListing']") {
title: mapSelector(selector: "a[data-test='job-title']") {
innerText
}
company: mapSelector(selector: "[data-test='employer-name']") {
innerText
}
location: mapSelector(selector: "[data-test='emp-location']") {
innerText
}
salary: mapSelector(selector: "[data-test='detailSalary']") {
innerText
}
}
}`;
const response = await fetch(
'https://production-sfo.browserless.io/stealth/bql?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: {}, operationName: 'ScrapeGlassdoor' }),
}
);
const { data } = await response.json();
const jobs = data.jobs.map((job) => ({
title: job.title?.[0]?.innerText ?? '',
company: job.company?.[0]?.innerText ?? '',
location: job.location?.[0]?.innerText ?? '',
salary: job.salary?.[0]?.innerText ?? '',
}));
console.log(JSON.stringify(jobs, null, 2));
2. Check the output
{
"data": {
"goto": { "status": 200 },
"jobs": [
{
"title": [{ "innerText": "Software Engineer" }],
"company": [{ "innerText": "TechCorp" }],
"location": [{ "innerText": "New York, NY" }],
"salary": [{ "innerText": "$120K – $180K (Glassdoor est.)" }]
},
{
"title": [{ "innerText": "Senior Software Engineer" }],
"company": [{ "innerText": "BuildCo" }],
"location": [{ "innerText": "New York, NY (Remote)" }],
"salary": [{ "innerText": "$150K – $220K (Glassdoor est.)" }]
}
]
}
}
1. Install dependencies
pip install requests
2. Send the request
import requests
query = """
mutation ScrapeGlassdoor {
goto(
url: "https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm"
waitUntil: networkIdle
) {
status
}
jobs: mapSelector(selector: "[data-test='jobListing']") {
title: mapSelector(selector: "a[data-test='job-title']") {
innerText
}
company: mapSelector(selector: "[data-test='employer-name']") {
innerText
}
location: mapSelector(selector: "[data-test='emp-location']") {
innerText
}
salary: mapSelector(selector: "[data-test='detailSalary']") {
innerText
}
}
}
"""
response = requests.post(
'https://production-sfo.browserless.io/stealth/bql',
params={
'token': 'YOUR_API_TOKEN_HERE',
'proxy': 'residential',
'proxyCountry': 'us',
},
json={'query': query, 'variables': {}, 'operationName': 'ScrapeGlassdoor'},
)
data = response.json()['data']
for job in data['jobs']:
title = job['title'][0]['innerText'] if job['title'] else ''
company = job['company'][0]['innerText'] if job['company'] else ''
location = job['location'][0]['innerText'] if job['location'] else ''
salary = job['salary'][0]['innerText'] if job['salary'] else ''
print(f'{title} at {company} — {location} — {salary}')
3. Check the output
{
"data": {
"goto": { "status": 200 },
"jobs": [
{
"title": [{ "innerText": "Software Engineer" }],
"company": [{ "innerText": "TechCorp" }],
"location": [{ "innerText": "New York, NY" }],
"salary": [{ "innerText": "$120K – $180K (Glassdoor est.)" }]
},
{
"title": [{ "innerText": "Senior Software Engineer" }],
"company": [{ "innerText": "BuildCo" }],
"location": [{ "innerText": "New York, NY (Remote)" }],
"salary": [{ "innerText": "$150K – $220K (Glassdoor est.)" }]
}
]
}
}
1. Send the request
import java.net.URI;
import java.net.http.*;
String token = "YOUR_API_TOKEN_HERE";
String endpoint = "https://production-sfo.browserless.io/stealth/bql?token="
+ token + "&proxy=residential&proxyCountry=us";
String query = """
mutation ScrapeGlassdoor {
goto(url: "https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm", waitUntil: networkIdle) { status }
jobs: mapSelector(selector: "[data-test='jobListing']") {
title: mapSelector(selector: "a[data-test='job-title']") { innerText }
company: mapSelector(selector: "[data-test='employer-name']") { innerText }
location: mapSelector(selector: "[data-test='emp-location']") { innerText }
salary: mapSelector(selector: "[data-test='detailSalary']") { innerText }
}
}
""";
String payload = "{\"query\": " + com.google.gson.JsonParser.parseString(query) + ", \"variables\": {}, \"operationName\": \"ScrapeGlassdoor\"}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
2. Check the output
{
"data": {
"goto": { "status": 200 },
"jobs": [
{
"title": [{ "innerText": "Software Engineer" }],
"company": [{ "innerText": "TechCorp" }],
"location": [{ "innerText": "New York, NY" }],
"salary": [{ "innerText": "$120K – $180K (Glassdoor est.)" }]
},
{
"title": [{ "innerText": "Senior Software Engineer" }],
"company": [{ "innerText": "BuildCo" }],
"location": [{ "innerText": "New York, NY (Remote)" }],
"salary": [{ "innerText": "$150K – $220K (Glassdoor est.)" }]
}
]
}
}
1. Send the request
using System.Net.Http;
using System.Text;
using System.Text.Json;
string token = "YOUR_API_TOKEN_HERE";
string endpoint = $"https://production-sfo.browserless.io/stealth/bql?token={token}&proxy=residential&proxyCountry=us";
var payload = new
{
query = @"mutation ScrapeGlassdoor {
goto(url: ""https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm"", waitUntil: networkIdle) { status }
jobs: mapSelector(selector: ""[data-test='jobListing']"") {
title: mapSelector(selector: ""a[data-test='job-title']"") { innerText }
company: mapSelector(selector: ""[data-test='employer-name']"") { innerText }
location: mapSelector(selector: ""[data-test='emp-location']"") { innerText }
salary: mapSelector(selector: ""[data-test='detailSalary']"") { innerText }
}
}",
variables = new { },
operationName = "ScrapeGlassdoor",
};
using (HttpClient httpClient = new HttpClient())
{
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(endpoint, content);
string body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
2. Check the output
{
"data": {
"goto": { "status": 200 },
"jobs": [
{
"title": [{ "innerText": "Software Engineer" }],
"company": [{ "innerText": "TechCorp" }],
"location": [{ "innerText": "New York, NY" }],
"salary": [{ "innerText": "$120K – $180K (Glassdoor est.)" }]
},
{
"title": [{ "innerText": "Senior Software Engineer" }],
"company": [{ "innerText": "BuildCo" }],
"location": [{ "innerText": "New York, NY (Remote)" }],
"salary": [{ "innerText": "$150K – $220K (Glassdoor est.)" }]
}
]
}
}
Connect through stealth mode and a residential proxy so Glassdoor sees traffic that looks like it's coming from a real US-based browser, then extract job listing data from the fully rendered page.
- Puppeteer
- Playwright
1. Install dependencies
npm install puppeteer-core
2. Connect and scrape
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint:
'wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us',
});
try {
const page = await browser.newPage();
await page.goto(
'https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm',
{ waitUntil: 'networkidle2' }
);
const jobs = await page.evaluate(() =>
Array.from(document.querySelectorAll('[data-test="jobListing"]')).map((card) => ({
title: card.querySelector('a[data-test="job-title"]')?.innerText?.trim() ?? '',
company: card.querySelector('[data-test="employer-name"]')?.innerText?.trim() ?? '',
location: card.querySelector('[data-test="emp-location"]')?.innerText?.trim() ?? '',
salary: card.querySelector('[data-test="detailSalary"]')?.innerText?.trim() ?? '',
}))
);
console.log(JSON.stringify(jobs, null, 2));
} finally {
await browser.close();
}
3. Check the output
Run with node scrape-glassdoor.mjs. You get one object per job card, each with title, company, location, and salary already grouped. No index-stitching required because we're selecting within each card element.
1. Install dependencies
npm install playwright-core
2. Connect and scrape
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP(
'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE&stealth&proxy=residential&proxyCountry=us'
);
try {
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto(
'https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm',
{ waitUntil: 'networkidle' }
);
const jobs = await page.evaluate(() =>
Array.from(document.querySelectorAll('[data-test="jobListing"]')).map((card) => ({
title: card.querySelector('a[data-test="job-title"]')?.innerText?.trim() ?? '',
company: card.querySelector('[data-test="employer-name"]')?.innerText?.trim() ?? '',
location: card.querySelector('[data-test="emp-location"]')?.innerText?.trim() ?? '',
salary: card.querySelector('[data-test="detailSalary"]')?.innerText?.trim() ?? '',
}))
);
console.log(JSON.stringify(jobs, null, 2));
} finally {
await browser.close();
}
3. Check the output
Run with node scrape-glassdoor.mjs. You get one object per job card, each with title, company, location, and salary already grouped. No index-stitching required because we're selecting within each card element.
1. Write the mutation
Navigate directly to the Glassdoor search results URL and use mapSelector to pull structured data from each job card. We send this to the /stealth/bql endpoint (not the default /bql) because Glassdoor's bot detection will block a plain browser session before the page even loads.
mutation ScrapeGlassdoor {
goto(
url: "https://www.glassdoor.com/Job/new-york-software-engineer-jobs-SRCH_IL.0,8_IC1132348_KO9,26.htm"
waitUntil: networkIdle
) {
status
}
jobs: mapSelector(selector: "[data-test='jobListing']") {
title: mapSelector(selector: "a[data-test='job-title']") {
innerText
}
company: mapSelector(selector: "[data-test='employer-name']") {
innerText
}
location: mapSelector(selector: "[data-test='emp-location']") {
innerText
}
salary: mapSelector(selector: "[data-test='detailSalary']") {
innerText
}
}
}
2. Run it
Paste into the BQL IDE and click Run.
3. Check the output
Each entry under jobs is already structured by card. mapSelector nests its results relative to each matched parent element, so you don't need to stitch fields together by index.
{
"data": {
"goto": { "status": 200 },
"jobs": [
{
"title": [{ "innerText": "Software Engineer" }],
"company": [{ "innerText": "TechCorp" }],
"location": [{ "innerText": "New York, NY" }],
"salary": [{ "innerText": "$120K – $180K (Glassdoor est.)" }]
},
{
"title": [{ "innerText": "Senior Software Engineer" }],
"company": [{ "innerText": "BuildCo" }],
"location": [{ "innerText": "New York, NY (Remote)" }],
"salary": [{ "innerText": "$150K – $220K (Glassdoor est.)" }]
}
]
}
}
Next steps
- Scrape Etsy Product Listings — extract product data from another bot-protected marketplace
- Solving Cloudflare Challenges — bypass Cloudflare protection on other targets
- Change Your Browser's IP Address Using Proxies — control which IP address target sites see