Indeed Scraper: Daily Job Listings from Every Indeed Site
ProxyField gets new Indeed jobs with salaries and company names into your database every day in two ways: the script on this page run on ProxyField residential proxies, from the Easy Trial (1 GB for $2.95), or the Web Scraping API, in development (contact sales), priced at $2.00 per 1,000 rendered pages, from $1.00 at volume. Per job that is about $0.20 per 1,000 jobs for the card fields (title, company, location, pay) at 10 cards a page, and $2.20 per 1,000 jobs with the full posting. Both routes return Indeed's pages, not a finished job feed: the script below is the parser, and it writes one row per job, keyed on Indeed's job key jk, with salary minimum, maximum, currency and period when the employer shows pay.
Through the sales team on our 24/7 live chat
| Fetch | Entry | Volume, from |
|---|---|---|
| HTTP | $0.50 | $0.20 |
| JS rendering | $2.00 | $1.00 |
Billed on successful results only.
Run it in 10 minutes:
pip install playwright && playwright install chromium- Buy the Easy Trial (1 GB for $2.95) and put your login and password in the script's
PROXY. - Set
COUNTRY("us","gb","de"…),QUERYandPLACE. python3 indeed_daily.py: yesterday's new jobs go intojobs.dbandnew_jobs_<date>.jsonl.- Add one cron line:
0 6 * * * cd /opt/indeed && python3 indeed_daily.py >> run.log 2>&1.
What lands in your database, as the script writes it to new_jobs_<date>.jsonl (illustrative values, not real jobs). Salary fields are null when the posting shows no pay, never estimated:
{"site": "www.indeed.com", "jk": "a1b2c3d4e5f60718", "title": "Data Analyst", "company": "Example Health Co", "location": "Chicago, IL", "salary_text": "$70,000 - $85,000 a year", "salary_min": 70000.0, "salary_max": 85000.0, "currency": "$", "period": "year", "first_seen": "2026-10-28", "last_seen": "2026-10-28"}
{"site": "www.indeed.com", "jk": "0f9e8d7c6b5a4321", "title": "Junior Data Analyst", "company": "Example Logistics", "location": "Chicago, IL 60606", "salary_text": "$28 - $32 an hour", "salary_min": 28.0, "salary_max": 32.0, "currency": "$", "period": "hour", "first_seen": "2026-10-28", "last_seen": "2026-10-28"}
{"site": "www.indeed.com", "jk": "1a2b3c4d5e6f7a8b", "title": "Senior Data Analyst", "company": "Example Bank", "location": "Remote in Chicago, IL", "salary_text": null, "salary_min": null, "salary_max": null, "currency": null, "period": null, "first_seen": "2026-10-28", "last_seen": "2026-10-28"}Indeed data on public pages
An Indeed scraper reads what a job seeker sees without signing in. Your own script fetches the pages through ProxyField residential proxies today; the Web Scraping API, in development, is designed to fetch any Indeed URL and return the page. Either way your code pulls the fields out: ProxyField sells no separate Indeed API with pre-parsed fields.
| Indeed page | What the public page shows |
|---|---|
Job search (/jobs?q=…&l=…) | Job cards: title, company, location, salary when shown, job type, how long ago it was posted, the job key |
Job page (/viewjob?jk=…) | Full description, salary, job type, benefits, company rating, apply route |
Company page (/cmp/<name>) | Overview, size, industry, open jobs |
| Company reviews and salaries | Ratings, review text, role and location of the reviewer, reported pay by job title |
Indeed asks visitors to sign in for some deeper review and salary pages; anything behind a login is out of scope under ProxyField's acceptable use policy. The API's design returns the page itself as HTML, Markdown or plain text, or a JSON envelope with the status code, the final URL, the headers, the cookies and the content; it has no parsed Indeed fields, so your parser reads them.
A parsed job feed or raw pages?
There are two ways to get Indeed jobs into a database: a hosted scraper that returns finished job records, or fetched pages plus your own parser. ProxyField sells the second: residential proxies for your script today, and the Web Scraping API in development.
| Item | A hosted parsed-feed scraper | ProxyField pages + your parser |
|---|---|---|
| What comes back | job records in the service's field list | the rendered page; your parser writes the fields (script below) |
| Who writes the parser | the service | you: the script on this page, about 85 lines |
| When Indeed changes its markup | you wait for the service to update | you fix a selector yourself |
| Which fields | the service's list | anything on the public page, in your own schema |
| Price unit | set by the service, often per job or per run | API: $2.00 per 1,000 successful results with JS rendering, from $1.00, so $0.20 per 1,000 jobs from cards; proxies: per GB, from $2.95 for 1 GB |
| Daily schedule | the service's scheduler | one cron line on your server |
| The same code for other sites | no | yes: any public URL |
| Available | the service's terms | residential proxies now; the API in development (contact sales) |
A daily Indeed job feed with salaries and company names
A daily feed is one short search per keyword and city, run every morning, written into your database with the job key as the primary key:
| Step | What to set |
|---|---|
| Only new jobs | sort=date (newest first) and fromage=1 (posted in the last day) |
| Paging | start=0, 10, 20… until a page has no cards |
| Primary key | the card's data-jk value; a job seen again only updates last_seen |
| Columns | site, jk, title, company, location, salary_text, salary_min, salary_max, currency, period, first_seen, last_seen |
| Salary | the text as shown ("$70,000 - $85,000 a year"), parsed into minimum, maximum, currency and period; all empty (NULL) when the posting shows no pay, never estimated |
| Full posting | open /viewjob?jk=<key> once per new key for the description and benefits |
| Schedule | one cron line, e.g. 0 6 * * * cd /opt/indeed && python3 indeed_daily.py >> run.log 2>&1 |
Read moreShow less
This Playwright script runs one search through one sticky ProxyField residential IP in the site's country, checks robots.txt before every page, skips images and fonts to save traffic, stops on a refusal, upserts every card into SQLite and writes the day's new jobs to a JSON Lines file. Change COUNTRY to move it to another Indeed site:
# indeed_daily.py: yesterday's new jobs for one search -> jobs.db and new_jobs_<date>.jsonl
import json, random, re, secrets, sqlite3, time
from datetime import date
from urllib.parse import quote_plus
from urllib.robotparser import RobotFileParser
from playwright.sync_api import sync_playwright
COUNTRY = "us" # one key per Indeed site: "gb", "ca", "de" ...
SITES = {"us": ("www.indeed.com", "en-US"), "gb": ("uk.indeed.com", "en-GB"),
"ca": ("ca.indeed.com", "en-CA"), "de": ("de.indeed.com", "de-DE"),
"fr": ("fr.indeed.com", "fr-FR"), "au": ("au.indeed.com", "en-AU"),
"es": ("es.indeed.com", "es-ES"), "it": ("it.indeed.com", "it-IT"),
"nl": ("nl.indeed.com", "nl-NL")}
SITE, LOCALE = SITES[COUNTRY] # the Indeed domain and the browser locale
SESSION = secrets.token_hex(4) # one sticky IP for the whole run
PROXY = {"server": "http://gate.proxyfield.io:7000", # an IP in the same country, kept 30 min:
# e.g. <login>-country-us-session-3f9a1c2e-ttl-1800 (add -city-new_york for one city)
"username": f"<login>-country-{COUNTRY}-session-{SESSION}-ttl-1800",
"password": "<password>"}
QUERY, PLACE = "data analyst", "Chicago, IL" # "London", "Berlin" ...
SEARCH = (f"https://{SITE}/jobs?q={quote_plus(QUERY)}&l={quote_plus(PLACE)}"
"&sort=date&fromage=1&start={start}")
MAX_PAGES = 10 # this script's own cap: start=0 to 90; split a search that needs more
def parse_pay(text):
"""'$70,000 - $85,000 a year' -> (70000.0, 85000.0, '$', 'year'); NULLs when no pay is shown.
Reads US and UK number formats; adapt it for sites that write 45.000 EUR or 'pro Jahr'."""
cur = re.search(r"[$£€]", text or "")
nums = [float(n.replace(",", "")) for n in re.findall(r"\d[\d,]*(?:\.\d+)?", text or "")]
if not cur or not nums: # no currency sign: a job type such as "Full-time", not pay
return None, None, None, None
per = next((w for w in ("hour", "day", "week", "month", "year") if w in text.lower()), None)
return min(nums), max(nums), cur.group(0), per
def text(card, selector):
el = card.query_selector(selector)
return el.inner_text().strip() if el else None
robots = RobotFileParser(f"https://{SITE}/robots.txt")
robots.read()
db = sqlite3.connect("jobs.db")
db.row_factory = sqlite3.Row
db.execute("""CREATE TABLE IF NOT EXISTS jobs (site TEXT, jk TEXT, title TEXT, company TEXT,
location TEXT, salary_text TEXT, salary_min REAL, salary_max REAL, currency TEXT,
period TEXT, first_seen TEXT, last_seen TEXT, PRIMARY KEY (site, jk))""")
today = date.today().isoformat()
with sync_playwright() as p:
browser = p.chromium.launch(proxy=PROXY)
page = browser.new_page(locale=LOCALE)
page.route("**/*", lambda route: route.abort() # images, media and fonts are traffic you pay for
if route.request.resource_type in ("image", "media", "font") else route.continue_())
for start in range(0, MAX_PAGES * 10, 10):
url = SEARCH.format(start=start)
if not robots.can_fetch("*", url):
print(f"robots.txt disallows {url}: stop")
break
resp = page.goto(url, wait_until="domcontentloaded", timeout=60000)
if resp is None or resp.status in (403, 429) or "Security Check" in page.title():
print(f"refused at start={start}: stop, retry this search later at a slower pace")
break
# Indeed changes its markup: check these selectors on a live page first
cards = page.query_selector_all("div.job_seen_beacon")
if not cards:
break # end of the results, or the markup changed
for card in cards:
link = card.query_selector("a[data-jk]")
if not link:
continue
pay = text(card, "[data-testid='attribute_snippet_testid']") # pay when shown; may hold the job type
db.execute("""INSERT INTO jobs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(site, jk) DO UPDATE SET last_seen = excluded.last_seen""",
(SITE, link.get_attribute("data-jk"), link.inner_text().strip(),
text(card, "[data-testid='company-name']"),
text(card, "[data-testid='text-location']"),
pay, *parse_pay(pay), today, today))
db.commit()
time.sleep(random.uniform(5, 10))
browser.close()
with open(f"new_jobs_{today}.jsonl", "w") as out: # today's new jobs, one JSON object per line
for row in db.execute("SELECT * FROM jobs WHERE site = ? AND first_seen = ?", (SITE, today)):
out.write(json.dumps(dict(row)) + "\n")Today's new jobs, with pay and company, for your database or a report:
sqlite> SELECT jk, title, company, location, salary_text, salary_min, salary_max, period
...> FROM jobs WHERE first_seen = date('now') ORDER BY salary_max DESC;The rows look like the three at the top of this page. A row without pay keeps the salary columns empty: the posting showed none. Each run, new keys are inserted and known keys only get a new last_seen, so a job that stops appearing has closed. On Windows, Task Scheduler runs it daily: schtasks /create /sc daily /st 06:00 /tn indeed-daily /tr "py C:\indeed\indeed_daily.py". More on running it every day: schedule web scrapers.
What a daily feed would cost on the Web Scraping API (in development): 50 keyword-city searches of up to 5 result pages, plus 1,000 new job pages a day, come to 37,500 rendered pages a month. That is $75 at $2.00 per 1,000, or $37.50 at the volume price.
Per job, not per page: you pay per page, and one search page holds many job cards.
| What you collect per job | Pages per 1,000 jobs | At $2.00 per 1,000 | At $1.00 (volume) |
|---|---|---|---|
| Card fields from search pages (title, company, location, pay), at 10 cards a page | 100 | $0.20 | $0.10 |
The full posting (description, benefits, pay) from /viewjob | 1,000 | $2.00 | $1.00 |
| Both | 1,100 | $2.20 | $1.10 |
Count the cards on your own result pages in the first run; the job key removes a card that appears on two pages.
The same feed on residential proxies, today: you pay per GB, so the cost depends on what one page weighs with images and fonts skipped, as the script does. Read the GB used in the dashboard's real-time usage stats after the first run, then divide by the pages fetched. A worked example, if a results page weighs 1.5 MB:
| Your own scraper on residential proxies | Pages | Traffic | Cost |
|---|---|---|---|
| 1,000 jobs from cards, 10 a page | 100 | 0.15 GB | $0.44 at $2.95/GB |
| 50 searches × 5 result pages a day, for 30 days | 7,500 | 11.25 GB | $33.48 for 12 GB at $2.79/GB |
The traffic never expires, so GB bought for this month's feed and left over carry into the next.
Indeed's country sites: one scraper, a few settings
Yes: the same scraper collects Indeed UK and the other country sites, because they share one URL pattern (/jobs?q=…&l=…&start=…, /viewjob?jk=…). Each site has its own listings, language and currency, so four things change per site: domain + IP country + browser locale + the parser's pay format. In the script above, the COUNTRY key sets the first three; you test the fourth once per site, and check that the script gets past the cookie consent banner UK and EU sites show. Indeed UK, side by side for your own scraper and for the Web Scraping API's design:
proxy username: <login>-country-gb-session-3f9a1c2e-ttl-1800
API request: {"url": "https://uk.indeed.com/jobs?q=data+analyst&l=London&sort=date&fromage=1", "render": true, "country": "gb"}Read moreShow less
On the API, country sets the country the request comes from (the exit IP), as a two-letter code; it accepts the proxy pool's 195+ countries, and every Indeed domain costs the same per 1,000 successful results.
| Indeed site | URL | IP for your own scraper |
|---|---|---|
| United States | www.indeed.com | US proxies: 61.2M+ residential IPs, 2,800+ cities |
| United Kingdom | uk.indeed.com | UK proxies: 16.6M+, 640+ cities |
| Canada | ca.indeed.com | Canadian proxies: 5.5M+ |
| Germany | de.indeed.com | German proxies: 20.2M+, 980+ cities |
| France | fr.indeed.com | French proxies: 8.3M+, 170+ cities |
| Australia | au.indeed.com | Australian proxies: 4.1M+, 140+ cities |
| Spain | es.indeed.com | Spanish proxies: 13.7M+, 690+ cities |
| Italy | it.indeed.com | Italian proxies: 10.2M+, 210+ cities |
| Netherlands | nl.indeed.com | Dutch proxies: 3.2M+, 110+ cities |
The table holds 9 of the sites. Indeed lists 63 countries, 91 country and language versions, at indeed.com/countries; take each site's exact domain from there. Other Indeed markets with ProxyField residential IPs include Ireland (310K+), Belgium (180K+), Switzerland (160K+), Austria (130K+), Poland (5M+), Mexico (26.1M+), Brazil (36.8M+), India (42.6M+), Japan (3.3M+) and Singapore (1.4M+).
What to change per country in your own scraper:
-
The domain in
SEARCH, e.g.https://uk.indeed.com/jobs?q=data+analyst&l=London&sort=date&fromage=1&start={start}. -
The IP country in the username, e.g.
<login>-country-gbfor uk.indeed.com. Countries take their two-letter code in lowercase, as in<login>-country-gb. -
The browser locale to match:
new_page(locale="en-GB"),"de-DE","fr-FR". -
The parser salaries come in the site's currency and wording (£ and "a year" on the UK site), and location formats differ. Test the selectors on one page of each site before the first daily run.
-
The
sitecolumn, already in the script's table, because a job key belongs to one country site. -
The consent banner
UK and EU visitors may see a cookie consent dialog before the results; check that your script reaches the job cards.
-
The law on UK and EU sites, UK GDPR and the GDPR apply to any personal data you collect, such as a recruiter's name in a posting; keep it out of your dataset. Each country site has its own terms page. See Is Web Scraping Legal?
Targeting covers country, region (state) and city in 195+ countries; ASN and ZIP targeting are not offered.
Indeed's official APIs or a scraper?
Indeed's developer APIs are built for employers, applicant tracking systems and job board partners: Sponsored Jobs, Indeed Apply, Job Sync. They post and sync your own jobs and receive applications; none of them is a public job search feed for collecting listings. If you are an Indeed partner, use the partner APIs; to read public listings, a scraper fetches the pages.
| Item | Indeed partner APIs | Web Scraping API |
|---|---|---|
| Who can use it | Approved employers, ATS and job board partners | ProxyField customers |
| Purpose | Posting jobs and handling applications | Reading public Indeed pages |
| Output | Your own jobs and applicants | The page, for your parser |
| Billing | Set by Indeed | Per 1,000 successful results |
What you send
The Web Scraping API is in development: to start, contact sales. In its design, you send an Indeed URL and get the page back: a POST to https://api.proxyfield.io/v1/scrape with your API key in an Authorization: Bearer <api_key> header and a JSON body: url, render (false for plain HTTP, true for JS rendering), country and output (html, markdown, text or json). Because Indeed builds its pages with scripts, wait_for takes a CSS selector the rendered page must show before it is returned, such as the job cards. Parameter reference: API documentation.
A search URL you would send looks like https://www.indeed.com/jobs?q=data+analyst&l=Chicago%2C+IL&sort=date, and a job page like https://www.indeed.com/viewjob?jk=<key>.
# The request in the Web Scraping API's design (in development: contact sales)
curl https://api.proxyfield.io/v1/scrape \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.indeed.com/jobs?q=data+analyst&l=Chicago%2C+IL&sort=date", "render": true, "country": "us", "output": "json"}'Example response in the design (shortened; content is the rendered page's HTML):
{
"metadata": {"id": "req_09a6d3f1b8e2c574", "api": "web_scrape", "status": "success", "billed": true},
"status_code": 200,
"final_url": "https://www.indeed.com/jobs?q=data+analyst&l=Chicago%2C+IL&sort=date",
"content_type": "text/html; charset=utf-8",
"content": "<!DOCTYPE html><html lang=\"en\">…</html>"
}Pricing: $2.00 per 1,000 rendered pages, from $1.00
Indeed builds and guards its pages with scripts, so budget for JS rendering: $2.00 per 1,000 successful results, from $1.00 at volume. Pages that load over plain HTTP cost $0.50 per 1,000, from $0.20. You pay only for successful results. Billed: a page Indeed answered with a 2xx status. Not billed: blocked requests, timeouts and errors on our side; a blocked request is retried on new IPs, and the retries are not billed. Every response says whether it was billed. A target's 404 or 410, and a sign-in page the site serves with a 200 status, count as billed results. The API is in development: to start, contact sales through the sales team on our 24/7 live chat. The volume price starts at 10,000 successful results in one purchase.
The same 120,000 pages over plain HTTP would cost $60, or $24 at the volume price. Payment and balance for the API: the same crypto balance as proxies, which never expires; there is no subscription or monthly fee. All prices: pricing.
Through the sales team on our 24/7 live chat
| Workload (30-day month) | Pages a month | JS rendering at $2.00 | At $1.00 (volume) |
|---|---|---|---|
| Daily feed: 50 searches × 5 result pages, plus 1,000 new job pages, daily | 37,500 | $75 | $37.50 |
| 200 keyword-city searches, first 5 result pages, daily | 30,000 | $60 | $30 |
| 3,000 new job pages, daily | 90,000 | $180 | $90 |
| The last two together | 120,000 | $240 | $120 |
Scraping Indeed with Python without getting blocked after a few pages
Refusals after the first few result pages come from pace and a changing IP, so: one sticky IP per browser session, 5 to 10 s between pages, and stop on a 403 or 429. The 10-page cap per search is the script's own choice (MAX_PAGES), not an Indeed limit; robots.txt decides which start values may be fetched, and the script checks it before every page. Indeed's Terms of Service restrict automated access without its permission, so check your use against them first (see Legal questions).
What a refusal looks like, so your code can tell it from a results page:
requests.get("https://www.indeed.com/jobs?q=data+analyst") -> status 403, <title>Security Check - Indeed.com</title>
Playwright after a burst of pages -> page.title() == "Security Check - Indeed.com"Read moreShow less
The page may also say "Additional Verification Required". The script stops on these conditions:
| Condition | What the script does |
|---|---|
| robots.txt disallows the URL | doesn't request it, and stops |
| HTTP 403 | stops this search; retry it later at a slower pace |
| HTTP 429 | stops; wait before the next run (429 Too Many Requests) |
| A page titled "Security Check" | stops |
| A page with no job cards | stops: the end of the results, or changed markup |
10 result pages read (start=90, the script's MAX_PAGES) | stops; split the search by city, or use fromage=1 |
And it keeps to these settings:
- A real browser. Use Playwright, not plain
requests, because Indeed builds its pages with scripts. - One IP per browser session. Keep the cookies and the IP together with a sticky session (30 s to 36 h) instead of a new IP per request; the script's
PROXYdoes this with-session-<id>-ttl-1800. An IP idle for 120 s gets a new IP, which a 5 to 10 s pace never reaches. Region, session and session time go in the same username:-region-<region>,-session-<id>and-ttl-<seconds>(30 to 129,600 seconds, 20 minutes by default), as in<login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600; every part after the login is optional. - The site's country. Use residential IPs from the same country as the Indeed site.
- 5 to 10 s between result pages, as in the script above, at most 10 result pages per search, and a few seconds before each
/viewjobpage. - Stop on a refusal. On a 403, a 429 or the security check page, stop that search and retry it later at a slower pace. Retrying at once only adds more refusals. See 429 Too Many Requests and Cloudflare 403 errors.
- Short searches. A broad search can list more jobs than 10 pages hold. Split it: one search per city, and
fromage=1for daily runs, so each search stays within the script's 10 pages.
No setup prevents every refusal. On the Web Scraping API's design you would pay only for successful results.
Access opens once the payment is confirmed; the gateway, credentials or allowlist are in your account. The username format is <login>-country-us-city-new_york, or as a full line: gate.proxyfield.io:7000:<login>-country-us-city-new_york-session-3f9a1c2e-ttl-1800:<password>. With the IP allowlist, you connect to the port you set up in the dashboard instead of 7000. Residential proxies cost $2.95/GB for 1 GB and from $1.95/GB at volume; the traffic never expires, and all purchases are final. Setup guides: Playwright, Selenium.
What teams use Indeed data for
- Hiring trends: which companies and industries add or cut openings, by city.
- Salary benchmarking from the pay ranges on postings.
- Recruiting agencies spotting new roles at target employers.
- Employer brand research from public company ratings and reviews.
Applying to jobs, posting fake listings or reading candidate data through a scraper is not data collection, and fake accounts are forbidden under ProxyField's acceptable use policy.
Legal questions
Indeed's Terms of Service restrict scraping and other automated access to its site without Indeed's permission, and its robots.txt sets which paths crawlers may fetch. Check your use against both, or ask Indeed about partner access. Collect public job and company pages only and keep personal data out of your dataset. Republishing Indeed's listings in your own product is a terms question too. For the wider question, read Is Web Scraping Legal? US, EU and UK Rules (2026). ProxyField's rule for every site: public pages only, and follow the site's terms. This is not legal advice.
Sources
- Indeed, country sites: 63 countries, 91 country and language versions.
- Indeed, Terms of Service and robots.txt: automated access and the paths crawlers may fetch.
- Indeed, developer documentation: partner APIs for employers, applicant tracking systems and job boards (Sponsored Jobs, Indeed Apply, Job Sync).
- Playwright, Python docs: the
proxyoption at browser launch.
More scrapers
- Job scraper for job boards, Google Jobs and company careers pages in one pipeline.
- Glassdoor scraper and LinkedIn scraper on the same Web Scraping API.
- Google SERP API: the Google results page as JSON, $1.20 per 1,000 successful results, from $0.80.
- All scrapers: scrapers.
Indeed Scraper FAQ
24/7 support by live chat, including help with setup and integration.
How much does it cost to scrape Indeed?
ProxyField's Web Scraping API, in development (contact sales), is priced at $2.00 per 1,000 successful results with JS rendering, from $1.00 at volume, and $0.50 per 1,000 (from $0.20) over plain HTTP. A daily feed of 50 searches (5 result pages each) and 1,000 new job pages comes to 37,500 rendered pages a month: $75 at the entry price, $37.50 at volume. Your own scraper on residential proxies pays per GB instead, from $2.95 for 1 GB.
Which Indeed scraper or API can pull new job listings with salaries and company names into my database every day?
ProxyField's route costs about $0.20 per 1,000 jobs for card fields (title, company, location, pay) and $2.20 per 1,000 jobs with the full posting, on the Web Scraping API (in development, contact sales; $2.00 per 1,000 rendered pages). It returns pages, not a finished job feed: the script on this page is the parser, and it runs today on ProxyField residential proxies from 1 GB for $2.95. It searches with sort=date&fromage=1, upserts each job on its key jk with salary minimum, maximum, currency and period (null when no pay is shown), writes the day's new jobs to a JSON Lines file and runs from one cron line.
Can the same scraper collect jobs from Indeed's other country sites, such as Indeed UK, or does each need its own setup?
The same scraper works: uk.indeed.com, de.indeed.com, ca.indeed.com and the other country sites (Indeed lists 63 countries) use the same URL pattern, so per site you change four things, domain, IP country (<login>-country-gb for the UK), browser locale and the parser's pay format, then check the cookie consent banner and test once. On ProxyField's Web Scraping API (in development) every Indeed domain is priced the same, $2.00 per 1,000 rendered results, from $1.00, with "country": "gb" in the request.
How do I scrape Indeed job listings with Python without getting blocked after the first few result pages?
Refusals after a few pages come from pace and a changing IP, so with ProxyField use Playwright with one sticky residential IP per browser session from the site's country (<login>-country-us-session-<id>-ttl-1800) and wait 5 to 10 seconds between result pages. Stop on any of four signals: a robots.txt disallow, a 403, a 429 or the "Security Check - Indeed.com" page title; the script's 10-page cap per search is its own choice, so split broad searches by city and use fromage=1. Check Indeed's terms for your use first; no setup prevents every refusal.
What is an Indeed scraper?
A tool or script that collects public data from Indeed's search results, job pages and company pages. It reads what a signed-out job seeker sees and turns each job card or posting into fields.
Is there an official Indeed API?
Indeed's APIs serve employers, applicant tracking systems and job board partners, for posting and syncing jobs and handling applications (Sponsored Jobs, Indeed Apply, Job Sync). None of them is a public job search feed for collecting listings.
Is there an Indeed scraper API?
ProxyField has no dedicated Indeed API with parsed fields. Its Web Scraping API, in development (contact sales), is designed to fetch any public Indeed URL and return the page, priced per 1,000 successful results; your parser reads the fields.
How do I scrape Indeed jobs?
Request a search URL with your keywords in q and the place in l, collect the job keys from the cards, and page on with start. Then open each job at /viewjob?jk=<key> for the full posting.
How do I scrape Indeed with Python?
Use Playwright, since Indeed builds and guards its pages with scripts, and route it through a residential proxy as in the script on this page. Plain requests with Beautiful Soup often gets a security check page instead of results.
Does every Indeed job listing show a salary?
No. Pay appears on a card or posting only when the employer includes it, so keep the salary column nullable and store the text as shown. Parse minimum, maximum and period from that text later, per country site.
Show 12 more questionsShow fewer questions
What types of Indeed data can I collect?
Job titles, companies, locations, salary ranges when shown, job types, posting age, full descriptions and benefits, plus public company pages with ratings, reviews and reported salaries. Applicant data and anything behind a login are out of scope.
Does Indeed allow web scraping?
Indeed's Terms of Service restrict scraping and automated access without its permission, and the site actively blocks bots. Check your use against the terms, collect public pages only, or ask Indeed about partner access.
Does Indeed have anti-scraping measures?
Yes, and they are among the stricter ones on job sites: bursts of automated requests get a "Security Check" page, 403 errors or empty results. Indeed also changes its page markup, which breaks fixed selectors.
Can Indeed be scraped with Playwright?
Yes: Playwright runs a real Chromium that executes Indeed's scripts, and it takes a proxy server, username and password at launch. Use a sticky ProxyField session so the browser keeps one IP while it works through a search.
Do I need coding skills to use an Indeed scraper?
For the Web Scraping API or your own script, some coding helps: you send URLs and parse the pages. No-code tools exist too, but they still need IPs and break when Indeed changes its pages.
Does an Indeed scraper need an Indeed account or API key?
No: job searches, job pages and company overviews are public to signed-out visitors. You need credentials only for the scraping service or proxies you use.
How many results does one Indeed search return?
Each results page shows a set of job cards, and a broad search can match more jobs than you can page through in one run. The script on this page reads at most 10 pages per search, its own cap; split broad searches by city, date filter or narrower keywords.
How do I open a job page from an Indeed search result?
Read the card's job key, the data-jk value, and request /viewjob?jk=<key> on the same Indeed site. The key also works as a stable ID for deduplicating jobs across searches.
Can I scrape Indeed company reviews and salaries?
The public parts of a company's reviews and salaries tabs can be fetched like any other Indeed page. Indeed asks visitors to sign in for some deeper pages, and those stay out of scope.
Can I use an Indeed scraper to track hiring trends?
Yes: run the same searches every day and count new, open and closed postings by company, title and city. Over months that shows which employers and roles are growing.
Can I build a custom job database from Indeed?
Technically yes, keyed on each job's jk with first-seen and last-seen dates, as in the daily script on this page. Republishing Indeed's listings is a terms question, so check Indeed's terms before you build a product on them.
How does Indeed get its job listings?
Employers post jobs on Indeed directly, and Indeed also collects listings from company careers sites and applicant tracking systems. That is why the same job often appears on Indeed and on the employer's own page.
Related pages
Collect Indeed pages with the Web Scraping API
Billed on successful results only. Contact sales for the Web Scraping API and the volume you need, or run your own scraper on residential proxies. 24/7 live chat helps with setup and integration.