Guides / Scrape Indeed

How to Scrape Indeed in 2026

Indeed uses bot detection, CAPTCHA challenges, and JavaScript-rendered job cards that only appear after the page fully loads. Scraping it yourself means managing residential proxies, solving CAPTCHAs, and waiting for dynamic content. With Browser7, you get fully rendered job listings in a single API call.

What makes Indeed hard to scrape

JavaScript-rendered job cards

Indeed's job listings are rendered client-side by JavaScript. The initial HTML contains a page shell, but the actual job cards - titles, companies, salaries, and locations - are loaded dynamically after the page initializes. A simple HTTP request returns an empty results container.

Bot detection and CAPTCHAs

Indeed actively detects automated traffic and serves CAPTCHA challenges or redirects to sign-in pages. Datacenter IPs are blocked immediately, and even residential IPs can be flagged if request patterns look automated.

Geo-targeted job listings

Indeed operates country-specific sites (indeed.com, indeed.co.uk, indeed.de) with different job listings, salary formats, and page structures. To see listings as a local user sees them, you need proxies in that country.

Dynamic content loading

Job cards load asynchronously after the page shell renders. You need to wait for the job cards container to appear before extracting data, or you will get an empty page.

Scrape Indeed job listings

Browser7 handles proxy rotation, CAPTCHA solving, and JavaScript rendering automatically. This example searches for remote software engineer jobs, waits for the job cards to load, and returns the fully rendered HTML. The wait action ensures the dynamic content is present before Browser7 returns the response.

from browser7 import Browser7, wait_for_selector

client = Browser7(
    api_key="b7_your_api_key",
    base_url="https://ca-api.browser7.com/v1"
)

result = client.render(
    "https://www.indeed.com/jobs?q=software+engineer&l=Remote",
    country_code="US",
    captcha="auto",
    wait_for=[
        wait_for_selector("#mosaic-provider-jobcards", timeout=15000),
    ]
)

print(result.html)

The wait_for_selector call tells Browser7 to wait until the job cards container appears in the DOM before returning the HTML. Without this, you might get the page shell before Indeed's JavaScript has finished rendering the job listings.

Data you can extract

The rendered HTML contains all the job listing data Indeed shows to a real visitor. Common data points to extract:

Job details

  • Job title and description
  • Company name
  • Location (city, state, remote)
  • Job type (full-time, contract, etc.)
  • Date posted

Compensation

  • Salary range (annual or hourly)
  • Benefits mentioned
  • Bonus and profit sharing
  • Indeed's estimated salary (when listed)

Company info

  • Company name and profile link
  • Company rating
  • Number of reviews
  • Company size and industry

Search metadata

  • Total results count
  • Results per page
  • Sponsored vs organic listings
  • Pagination info

Complete example: parse job listings

Here is a complete example that renders an Indeed search results page and extracts structured data for each job listing. The Python example uses BeautifulSoup, Node.js uses Cheerio, and PHP uses DOMDocument.

from browser7 import Browser7, wait_for_selector
from bs4 import BeautifulSoup
import json

client = Browser7(
    api_key="b7_your_api_key",
    base_url="https://ca-api.browser7.com/v1"
)

result = client.render(
    "https://www.indeed.com/jobs?q=software+engineer&l=Remote",
    country_code="US",
    captcha="auto",
    wait_for=[
        wait_for_selector("#mosaic-provider-jobcards", timeout=15000),
    ]
)

soup = BeautifulSoup(result.html, "html.parser")

jobs = []
for i, card in enumerate(soup.select("div.cardOutline")):
    job = {
        "position": i + 1,
        "title": None,
        "company": None,
        "location": None,
        "salary": None,
    }

    title = card.select_one("a.jcs-JobTitle")
    if title:
        job["title"] = title.get_text(strip=True)

    company = card.select_one('span[data-testid="company-name"]')
    if company:
        job["company"] = company.get_text(strip=True)

    location = card.select_one('div[data-testid="text-location"]')
    if location:
        job["location"] = location.get_text(strip=True)

    salary = card.select_one(".salary-snippet-container")
    if salary:
        job["salary"] = salary.get_text(strip=True)

    jobs.append(job)

print(json.dumps(jobs, indent=2))

CSS selectors may change if Indeed updates their page structure. Inspect the current page if any fields return null. Not all listings include salary data.

Sample output:

[
  {
    "position": 1,
    "title": "Senior C++ Software Engineer",
    "company": "ON1, inc.",
    "location": "Remote",
    "salary": "$135,000 - $155,000 a year"
  },
  {
    "position": 2,
    "title": "DevOps Engineer",
    "company": "Q-Free America",
    "location": "Remote",
    "salary": "From $120,000 a year"
  },
  ...
]

Scrape page 2 and beyond

Indeed uses the start query parameter for pagination, just like Google. Page 1 is the default, page 2 is &start=10, page 3 is &start=20, and so on.

from browser7 import Browser7, wait_for_selector
from bs4 import BeautifulSoup

client = Browser7(
    api_key="b7_your_api_key",
    base_url="https://ca-api.browser7.com/v1"
)

# Page 2: add &start=10 to skip the first 10 results
result = client.render(
    "https://www.indeed.com/jobs?q=software+engineer&l=Remote&start=10",
    country_code="US",
    captcha="auto",
    wait_for=[
        wait_for_selector("#mosaic-provider-jobcards", timeout=15000),
    ]
)

soup = BeautifulSoup(result.html, "html.parser")
for i, card in enumerate(soup.select("div.cardOutline")):
    title = card.select_one("a.jcs-JobTitle")
    name = title.get_text(strip=True) if title else "No title"
    print(f"{i + 11}. {name}")

Scrape Indeed from a different country

Indeed operates country-specific sites with different job listings and salary formats. In this example, because we are targeting the UK, we use the EU API endpoint for optimal performance and lower latency. Geo-targeting is included in the $0.01 per page price - no extra charge.

from browser7 import Browser7, wait_for_selector

# Use the EU endpoint for European targets
client = Browser7(
    api_key="b7_your_api_key",
    base_url="https://eu-api.browser7.com/v1"
)

# Get Indeed UK job listings from a London IP
result = client.render(
    "https://www.indeed.co.uk/jobs?q=software+engineer&l=London",
    country_code="GB",
    city="london",
    captcha="auto",
    wait_for=[
        wait_for_selector("#mosaic-provider-jobcards", timeout=15000),
    ]
)

print(result.html)
print(f"Rendered from: {result.selected_city}")

Take a screenshot of job listings

Capture Indeed search results as an image for job market reports, competitor analysis dashboards, or historical tracking of job postings over time.

import base64
from browser7 import Browser7, wait_for_selector

client = Browser7(
    api_key="b7_your_api_key",
    base_url="https://ca-api.browser7.com/v1"
)

result = client.render(
    "https://www.indeed.com/jobs?q=software+engineer&l=Remote",
    country_code="US",
    captcha="auto",
    block_images=False,
    include_screenshot=True,
    screenshot_full_page=True,
    screenshot_format="png",
    wait_for=[
        wait_for_selector("#mosaic-provider-jobcards", timeout=15000),
    ]
)

# Save the screenshot
with open("indeed-jobs.png", "wb") as f:
    f.write(base64.b64decode(result.screenshot))

print("Screenshot saved")

What this costs

Every Indeed page render costs $0.01 - the same as any other website. Residential proxies, JavaScript rendering, CAPTCHA solving, geo-targeting, wait actions, and screenshots are all included. There are no per-domain surcharges, no credit multipliers, and no bandwidth fees.

Scraping 10 pages of job listings across 50 different searches costs $5. You know this before you start.

Try it yourself

100 free renders - enough to test Indeed scraping with no payment required.