Start free

How to scrape Indeed

Extract Indeed job title, company, location, salary range, job type and posted date as schema-validated JSON, no CSS selectors.

Site:
indeed.com
Difficulty:
Hard
Rendering:
JavaScript-rendered
Anti-bot:
Cloudflare

What you can extract from Indeed

These are the fields teams most often pull from indeed.com, and why a hand-written selector for each one tends to break over time.

Field Type Why selectors break
job title string Indeed renders the title inside an h1/h2 whose data-testid and class hashes (like jobsearch-JobInfoHeader-title) are rebuilt by their build pipeline, so a hardcoded selector that worked last week silently returns null after a deploy with no warning.
company string The employer name sometimes sits in an anchor linking to the company page and sometimes in a plain span when the company has no Indeed profile, so a single CSS selector tuned to the linked variant misses every listing from companies without a profile.
location string Location is merged into a company-and-location block with no stable separator, mixing city, state, postal code and 'Remote' or 'Hybrid' badges, so a selector grabs a blob of text that still needs parsing rather than a clean location value.
salary range string Salary is frequently an estimated range Indeed injects in a different DOM node than employer-stated pay, is often absent entirely, and uses varying units (per hour, a year), so a selector that finds it on one listing returns stale or wrong data on the next.
job type string Job type lives among unlabeled pill/chip elements alongside shift and benefit tags, with no attribute marking which chip is the employment type, so an index-based or class-based selector grabs 'Health insurance' instead of 'Full-time' when the order shifts.
posted date string Indeed shows relative text such as 'Posted 3 days ago', 'Today' or 'Just posted' rather than an ISO date, and the wording rotates by A/B test, so a selector returns human phrasing that breaks any downstream date filter without extra normalization.

The schema-first approach

Instead of maintaining selectors, you hand Extracto a JSON schema describing the shape you want. Here is a schema that works for Indeed:

{
  "job_title": "string",
  "company": "string",
  "location": "string",
  "salary_range": "string",
  "job_type": "string",
  "posted_date": "string"
}
  1. 1

    Describe the Indeed fields you want

    Write a small JSON schema listing the fields to pull from indeed.com (for example job title, company, location). No CSS selectors or XPath needed.

  2. 2

    Send the URL and schema to Extracto

    POST the Indeed page URL and your schema to the Extracto API. Extracto renders the page in a real browser, handling JavaScript and dynamic content automatically.

  3. 3

    Get schema-validated JSON back

    Extracto returns JSON that matches your schema exactly, validated before it leaves the API, so your pipeline never receives broken or partial data.

Common pitfalls when scraping Indeed

Is it legal to scrape Indeed?

Indeed's Terms of Service explicitly prohibit automated access, scraping and data harvesting of its job content, and its robots.txt disallows large parts of the search and job-view paths while Cloudflare actively filters automated traffic (even sitemap fetches commonly return 403 as of 2026). Job postings themselves are typically the property of the posting employer, and aggregated salary figures and the listing layout are Indeed's own work product, so treat extracted data as covered by both copyright and contractual terms. Stay on the right side of this by keeping volume modest, requesting only public pages you have a legitimate business reason to read, never reselling raw Indeed datasets, honoring takedown requests, and consulting your own counsel before any production use. For recruiter-facing or high-volume needs, Indeed offers official employer APIs and partner feeds that are the compliant path, and you should prefer those over scraping wherever they cover your use case. Extracto handles rendering infrastructure for you, but it does not grant any license to a site's content, so responsibility for ToS and robots compliance remains with you.

Extracto follows robots.txt by default and rate-limits politely. You are responsible for compliance with Indeed's terms and applicable law (GDPR, CCPA) in your use case.

Scraping Indeed: FAQ

Can Extracto get past Indeed's Cloudflare protection?
Every Extracto request runs through a real headless browser with a managed anti-bot rendering layer that handles Cloudflare-protected sites, with proxies managed for you and no setup. Anti-bot pages are billed at 5 credits. Keep your volume modest and respect Indeed's Terms of Service and robots.txt, since the managed infrastructure does not grant you any license to the content.
Why do I get null for the salary range on some Indeed jobs?
Many Indeed listings have no employer-stated pay, and Indeed's own salary estimate is not always present either. Extracto returns null for fields that are genuinely absent rather than inventing a number, so a null salary_range means the posting did not publish one. This is intentional and keeps your dataset honest instead of silently filling guessed values.
Do I need to write CSS selectors for Indeed's changing layout?
No. You send the viewjob URL plus a JSON schema describing the fields you want, and Extracto returns typed JSON validated against that schema. Because there are no CSS selectors to maintain, Indeed's frequent class-hash and data-testid changes do not break your extraction the way a selector-based scraper would after every deploy.
Can I scrape a whole Indeed search results page at once?
Extracto extracts one URL per call and is not a crawler, so it does not walk pagination on its own. The pattern is to extract the list of individual job URLs from a search results page, then call Extracto once per viewjob URL. This keeps each extraction focused and your request volume deliberate and modest, which is the responsible way to read a protected site.
What format does the posted date come back in?
Indeed displays relative text such as 'Posted 3 days ago' or 'Just posted', and Extracto returns the string it finds on the page. If you need an absolute, sortable date you should normalize that phrasing against the time of your request in your own pipeline, because the listing does not expose a clean machine-readable timestamp in its visible markup.