Glassdoor Jobs Scraper - Pay and Ratings avatar

Glassdoor Jobs Scraper - Pay and Ratings

Pricing

from $0.25 / 1,000 results

Go to Apify Store
Glassdoor Jobs Scraper - Pay and Ratings

Glassdoor Jobs Scraper - Pay and Ratings

Scrape Glassdoor job listings by keyword and location. Each row carries the title, company, employer rating, pay as numbers with its period and currency next to the range Glassdoor displays, easy-apply and sponsored flags, a description snippet, days since posting and the apply URL.

Pricing

from $0.25 / 1,000 results

Rating

0.0

(0)

Developer

String

String

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

14 days ago

Last modified

Categories

Share

This Actor collects job listings from Glassdoor search. You give it job titles or keywords and one location; it returns each listing with the employer's Glassdoor rating, the pay range as numbers rather than a display string, how many days ago the job went up and a link to apply.

What does the Glassdoor Jobs Scraper do?

Glassdoor's job search is a Next.js App Router page, so its state arrives as React flight chunks pushed onto self.__next_f in pieces. The Actor concatenates those chunks back into the stream the page was serialised from and reads searchResultsData.jobListings out of it, which is the same object the site renders from.

One field is not in that payload. Glassdoor keeps pay as p10 and p90 integers and builds the displayed range in the browser from a translation template, so the string a human sees exists only in the markup. The Actor picks it up from the card's own id="job-salary-<listingId>" element and joins it back by listing ID, never by card order, so no listing can inherit its neighbour's pay.

  • Pay arrives twice on purpose: salaryMin and salaryMax as numbers, and salaryEstimate as the string Glassdoor shows.
  • salarySource keeps Glassdoor's own label, so an employer-stated range and a Glassdoor estimate stay distinguishable instead of being flattened together.
  • employerRating comes from the same record as the job, so pay and reputation land on one row.
  • Free-text locations work. The Actor resolves "New York, NY" through Glassdoor's own location autocomplete before searching, so you never have to know Glassdoor's internal location IDs.
  • sponsored marks paid placements, which is what you filter on when a market count has to be honest.

What data does it extract?

FieldTypeDescription
jobIdstringGlassdoor's listing ID. Stable, so use it as the de-duplication key
titlestringJob title as listed
companystringEmployer named on the search row
employerIdnumberGlassdoor's employer ID, the same across that employer's listings
employerRatingnumberOverall Glassdoor rating for the employer, out of 5
salaryEstimatestringThe range as Glassdoor prints it on the card, e.g. "$136K - $184K"
salaryMinnumberLow end of the range as a number, e.g. 136000
salaryMaxnumberHigh end of the range as a number
salaryPeriodstringWhat the pay is quoted over, e.g. "ANNUAL"
salaryCurrencystringCurrency of salaryMin and salaryMax, e.g. "USD"
salarySourcestringWhere the figure came from, e.g. "EMPLOYER_PROVIDED"
locationstringWhere Glassdoor places the job
ageInDaysnumberWhole days since posting, exactly as Glassdoor states it
postedAtstringISO 8601 timestamp derived from ageInDays. Accurate to the day
easyApplybooleanWhether the listing takes Glassdoor's one-click apply
sponsoredbooleanWhether the listing is a paid placement
descriptionSnippetstringThe first description fragment on the card, not the full text
jobUrlstringDirect link to the Glassdoor posting
querystringThe search term that produced the row
searchLocationstringThe location Glassdoor resolved your input to
sourceUrlstringThe exact search URL the row came from
collectedAtstringISO 8601 timestamp of the run

Anything Glassdoor did not supply is null. Nothing is filled in by inference.

Why scrape Glassdoor?

  • Compensation benchmarking that skips the cleanup. salaryMin, salaryMax, salaryPeriod and salaryCurrency go straight into a spreadsheet or a model. No regex over "$136K - $184K".
  • Employer-brand work. employerRating next to what a company is currently hiring for shows which teams are growing at companies whose staff rate them badly, which is where candidates are reachable.
  • Separating advertised pay from estimated pay. Filter on salarySource to build a dataset of ranges employers actually committed to.
  • Competitor hiring signals by city. One location per run, several roles per run, on a schedule.
  • Honest market counts. Drop rows where sponsored is true before you count openings.

How to use it

  1. Open the Actor on Apify Store and click Try for free.
  2. Enter your Search terms, one per line, up to 50: "software engineer", "product manager", "nurse practitioner".
  3. Set Location to a city, state or country, for example "New York, NY" or "United Kingdom". It defaults to "United States". Every term in the run uses this one location.
  4. Click Start. The run resolves the location first, then runs the searches in parallel.
  5. Export from the Dataset tab as JSON, CSV or Excel, or read it through the API.
  6. For a time series, save the input as a task, schedule it, and join runs on jobId.

Input

FieldTypeDefaultDescription
queriesstring[]requiredJob titles or keywords, 1 to 50. Duplicates collapse to one fetch
locationstring"United States"City, state or country. One location per run
maxItemsinteger1000Cap on dataset rows, 1 to 50000. Free plans stop earlier, see below
concurrencyinteger5Searches fetched in parallel, 1 to 10
{
"queries": ["software engineer", "product manager"],
"location": "New York, NY",
"maxItems": 500,
"concurrency": 5
}

Resolving the location costs one extra request at the start of the run. If Glassdoor cannot match what you typed, the run fails immediately instead of quietly searching the whole country.

Output

One row per listing. This is the shape, with illustrative values rather than a captured run.

{
"jobId": "1009472283810",
"title": "Senior Data Engineer",
"company": "Halden Health",
"employerId": 918274,
"employerRating": 3.9,
"salaryEstimate": "$136K - $184K",
"salaryMin": 136000,
"salaryMax": 184000,
"salaryPeriod": "ANNUAL",
"salaryCurrency": "USD",
"salarySource": "EMPLOYER_PROVIDED",
"location": "New York, NY",
"ageInDays": 6,
"postedAt": "2026-08-14T09:12:44.301Z",
"easyApply": false,
"sponsored": false,
"descriptionSnippet": "You will own the ingestion pipelines behind our clinical reporting stack...",
"jobUrl": "https://www.glassdoor.com/job-listing/senior-data-engineer-halden-health-JV_IC1132348_KO0,20.htm",
"query": "data engineer",
"searchLocation": "New York, NY",
"sourceUrl": "https://www.glassdoor.com/Job/jobs.htm?sc.keyword=data+engineer&locT=C&locId=1132348",
"collectedAt": "2026-08-20T09:31:02.118Z"
}

Reliability

Rows come from the search page's own data payload, so a value is either what Glassdoor holds or null. No model interprets the HTML, which is what keeps a job title from being paraphrased and a rating from being rounded.

Glassdoor dates a listing only as a whole-day age. ageInDays is carried through as the source's own value and postedAt is derived from it, so the timestamp is honest to the day rather than presenting an invented hour.

A search that cannot be read is recorded in the run's SUMMARY under failures with its URL and error, and the remaining searches still return. A run where every search failed exits with an error. A location Glassdoor cannot resolve fails the run outright, because continuing would silently widen every query to nationwide results and you would not see it in the data.

Limitations

One results page per search, around 22 listings, and no pagination input. Ten terms return roughly 220 listings. Split a broad query into narrower ones to widen coverage.

One location per run. Company reviews, interview reports, salary-report pages and full job descriptions are out of scope; descriptionSnippet is the card fragment, and jobUrl leads to the rest.

How much does it cost?

Pay per event: you are charged per result row written to the dataset, plus Apify's platform usage for the run. The current per-result price is on this Actor's Store page under Pricing.

The location lookup at the start of a run writes no rows and costs you nothing. Neither does a search that fails.

Runs started from an Apify free plan stop at 250 requests and 250 results, and the run says so in its status message. Any paid plan runs the full input and whatever maxItems you set. The limit exists because this Actor fetches through our own anti-bot infrastructure, which Apify does not cover for free-plan runs. It binds on requests as well as rows so that a long input list cannot spend those fetches on rows the run will never return.

Using it with the Apify API

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('usestring/glassdoor-jobs').call({
queries: ['software engineer', 'product manager'],
location: 'New York, NY',
maxItems: 500
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const stated = items.filter((job) => job.salarySource === 'EMPLOYER_PROVIDED' && job.salaryMax);
const midpoints = stated.map((job) => (job.salaryMin + job.salaryMax) / 2);
console.log(`${stated.length} listings with employer-stated pay`, midpoints);

The Python client takes the same input, as does the REST API. The SUMMARY record with the item count and any failed searches sits in the run's default key-value store.

The Actor reads public Glassdoor search pages as a logged-out visitor. No account, no cookie, no API key, nothing behind a login. What comes back is job advertisements employers published to attract applicants, together with the aggregate employer rating Glassdoor displays publicly beside them.

No personal data is collected. There are no reviewer identities, no individual reviews, no recruiter names and no contact details in the output.

Collecting public data is generally lawful in the US and the EU, but your own use of it is governed by your jurisdiction and your agreements. This is not legal advice.

Running the Indeed scraper over the same titles gives you employer-stated salary strings to check these numeric ranges against.

FAQ

Do I need a Glassdoor account or API key? No. Only public search pages are read, with no login, cookie or key.

Is the salary the employer's or Glassdoor's estimate? Both occur in the data and salarySource tells you which. Employer-stated ranges carry "EMPLOYER_PROVIDED". The label is passed through unchanged rather than flattened away, so you can filter to one kind before you benchmark anything.

How many listings does one search return, and can I page further? Around 22, which is one Glassdoor results page. There is no pagination input. For more coverage, run more specific terms, or the same term against several locations in separate runs.

Can I search more than one location in a run? No. All of a run's queries share one location, because the search URL is built from Glassdoor's resolved location ID. Start one run per city.

How current is postedAt? It is derived from ageInDays, which Glassdoor reports in whole days, so it is accurate to the day of posting and not to the hour. collectedAt records when the run read the page.

What happens when a search fails or Glassdoor blocks? Fetches go through our own anti-bot infrastructure. A search that still cannot be read lands in failures in the run's SUMMARY and the rest of the run continues. If all of them fail, the run errors out so a schedule notices.

Feedback

If a field is wrong, or you need something Glassdoor shows that is not in the table above, open an issue from this Actor's Store page. Include the search term and location so we can reproduce it.