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
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
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
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
- Indeed is one of the most heavily defended job boards on the open web. Plain HTTP clients and datacenter IPs get a Cloudflare 403 in milliseconds, so you need real browser rendering plus a managed anti-bot layer, which Extracto includes on every request with no proxy setup on your side.
- The job view page is JavaScript-rendered. Fetching raw HTML returns a skeleton without the salary, job type or posted-date nodes populated, so a static fetch yields mostly null fields even when the request succeeds. A headless browser that waits for hydration is required.
- Salary is often an Indeed estimate rather than an employer-stated figure, and is missing on many listings. Treat it as optional and let it come back as null instead of forcing a guessed number, which is exactly how Extracto's schema validation behaves.
- Posted date arrives as relative phrasing ('Posted 30+ days ago', 'Just posted') that you must normalize to an absolute date yourself if you want to sort or filter, because the page never exposes a clean timestamp in the visible markup.
- Extracto resolves one URL per call and is not a crawler, so to collect a search results page of jobs you extract the individual viewjob URLs first, then call the API once per posting rather than expecting it to walk pagination for you.
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.