Scraping Indeed sounds simple until you try it. There is no public API anymore, the site sits behind Cloudflare, and the salary you actually want arrives as a messy string like "$120,000 - $160,000 a year". This guide covers what you can pull from an Indeed job, why the DIY route is harder than it looks, working Python, a no-code shortcut, and a straight comparison so you can pick a lane. If you want the deeper reference, there is a full guide to scraping Indeed jobs too.
What you can pull from an Indeed job
Per listing you can get:
- Title, company, and location (plus city, state, country code, and lat/long)
-
Salary, parsed into numbers:
salaryMin,salaryMax,salaryPeriod,currency, not just the raw string - Job type, remote/hybrid flag, and the full description text
- Benefits, date posted, easy-apply and urgent-hire flags
- Company data on demand: rating, review count, industry, size, revenue, founded, website, and socials
Parsed salary and company enrichment are the two things people actually come for, and the two things a naive scrape gets wrong.
Why scraping Indeed is hard
Three reasons DIY is more work than it looks:
- There is no official API. Indeed retired its public job-search API to new developers years ago, so there is no supported programmatic route. Scraping is the only option left.
-
Cloudflare and rate limits. A plain request to a search results page tends to return a
403or a challenge. Hammer it and you get blocked fast. -
Messy salary strings. Indeed shows salary as free text. Turning
"$120,000 - $160,000 a year"into cleansalaryMin/salaryMax/periodreliably, across currencies and 40 countries, is fiddly.
So the work is not the parsing of one page. It is staying unblocked, normalizing salary, and pulling company data from separate pages, on repeat.
Three ways to get Indeed job data
| DIY Python | Indeed Jobs Scraper (actor) | Indeed Official API | |
|---|---|---|---|
| Setup time | Hours to days | ~30 seconds | Not available (retired) |
| Anti-bot handled | You (Cloudflare, blocks) | Built in | n/a |
| Salary as numbers | You parse the string | Yes: salaryMin / salaryMax
|
n/a |
| Company data | Separate scrape | Free, one toggle | n/a |
| Countries | You handle localization | 40+ | n/a |
| Cost | Proxies + eng time | Pay-per-result | n/a |
| Best for | One-off search | Scheduled, at scale | Not an option |
The official-API column is the real story: since Indeed closed its API, a scraper (yours or a ready-made one) is the only way to get this data programmatically.
Option A: DIY in Python
See the wall first:
import httpx
url = "https://www.indeed.com/jobs?q=software+engineer&l=New+York"
r = httpx.get(url, headers={"User-Agent": "Mozilla/5.0"})
print(r.status_code) # often 403 / Cloudflare challenge
If you do get HTML back, the job cards parse roughly like this (selectors change often, so re-check against the live page):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
for card in soup.select("div.job_seen_beacon"):
title = card.select_one("h2.jobTitle")
comp = card.select_one("[data-testid='company-name']")
sal = card.select_one("[data-testid='attribute_snippet_testid']")
print(
title.get_text(strip=True) if title else None,
comp.get_text(strip=True) if comp else None,
sal.get_text(strip=True) if sal else None, # raw string, still needs parsing
)
Then you still have to normalize that salary string into numbers, follow each job to its full description, and scrape the company page separately. For one search that is fine. For a pipeline, the upkeep adds up.
Option B: the no-code / API shortcut
When you just want clean rows, the Indeed Jobs Scraper on Apify talks to Indeed's structured endpoints (no fragile HTML path), handles the blocking, and returns parsed JSON. No login, no proxy setup.
From Python:
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("factden/indeed-jobs-scraper").call(run_input={
"query": "software engineer",
"location": "New York, NY",
"country": "US",
"maxItems": 200,
"salaryMin": 120000, # native salary-range filter
"scrapeCompany": True, # attach company profiles for free
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
print(job["title"], job["company"], job["salaryMin"], job["salaryMax"])
Set country to any of 40+ codes (US, GB, IN, DE, SG, ...), filter by remote, datePosted, jobType, or experienceLevel, or pass startUrls / jobKeys to fetch specific listings.
What comes back: 26 structured fields per job
| Group | Fields |
|---|---|
| Core |
title, company, location, city, state, countryCode, url, jobKey
|
| Salary |
salary (raw), salaryMin, salaryMax, salaryPeriod, currency
|
| Job detail |
jobType, remote, occupations, benefits, description, datePosted, isUrgentHire, easyApply
|
| Geo |
latitude, longitude
|
Company (with scrapeCompany) |
companyRating, companyReviewCount, companyPageUrl + a full company profile (industry, size, revenue, founded, website, socials) |
A trimmed sample row:
{
"title": "Senior Software Engineer",
"company": "Plaid",
"location": "New York, NY",
"salary": "$120,000 - $160,000 a year",
"salaryMin": 120000,
"salaryMax": 160000,
"salaryPeriod": "year",
"currency": "USD",
"jobType": ["Full-time"],
"remote": "remote",
"benefits": ["Health insurance", "401(k)"],
"datePosted": "2026-06-10",
"companyRating": 4.2,
"url": "https://www.indeed.com/viewjob?jk=6d50b3ebeb3fb122",
"jobKey": "6d50b3ebeb3fb122"
}
The numeric salary fields and the on-demand company profile are what make this usable for labor-market analysis without a cleanup pass. Full field list and snippets are in the GitHub repo.
Grab a free sample dataset
Want to see the data first? There is a free Indeed jobs sample (CSV/JSON) here: factden.com/sample-indeed. Drop it into pandas and the parsed salary columns are ready to plot.
FAQ
Is scraping Indeed legal? The listings are publicly visible. As with any scraping, check Indeed's Terms of Service and your local rules (especially around personal data), and use the data responsibly.
Does Indeed have an API? Not a usable one. Indeed closed its public job-search API to new developers, which is exactly why people scrape the public pages now.
How do I stop getting blocked? Residential proxies, real headers, and slow pacing, or a tool that talks to Indeed's structured endpoints instead of scraping HTML. Plain requests to the search pages will get challenged.
Can I get salary as numbers? Yes. salaryMin, salaryMax, salaryPeriod, and currency are parsed from the raw string, so you can filter and aggregate directly.
Can I get company data too? Yes. Turn on scrapeCompany and each job carries the company's rating, size, industry, revenue, and profile links, no second scrape needed.
Which countries are supported? 40+, including US, UK, Canada, India, Germany, Singapore, Australia, and more, via the country code.
Related
- Doing competitive intelligence instead of hiring data? See how to scrape G2 reviews.
- Other FactDen scrapers: G2 software reviews and Trip.com and Ctrip hotel reviews.
Questions, or a field you wish it extracted? Drop a comment.
Top comments (0)