Indeed scrapen met Python (en 403-fouten vermijden) in 2026

Laatst bijgewerkt op July 9, 2026
Indeed scrapen met Python (en 403-fouten vermijden) in 2026

Iets rond mijn vijftigste kopieer-plakactie van een functietitel van Indeed naar een spreadsheet begon ik mijn carrièrekeuzes in twijfel te trekken. Als je ooit geprobeerd hebt om gestructureerde data programmatisch uit Indeed te halen, ken je de clou al: de 403-fout is geen bug — het is een functie van Indeed's verdedigingssysteem.

Indeed is 's werelds grootste vacaturebank, met ongeveer 350 miljoen unieke maandelijkse bezoekers, 130 miljoen vacatures op elk moment, en activiteiten in meer dan 60 landen. Daarmee is het een van de rijkste bronnen voor arbeidsmarktdata op aarde — en een van de lastigste om te scrapen. De open-source scraper JobFunnel (duizenden GitHub-stars) werd in december 2025 letterlijk door de maintainer gearchiveerd na jaren van verliezen in de anti-bot-wapenwedloop. In de woorden van de maintainer zelf: "Alle gebruikers kunnen een deel van de vacatures scrapen, maar worden al snel geraakt door captcha, waarna het scrapen faalt en er geen vacatures overblijven." Een andere bijdrager meldde zelfs een CAPTCHA al bij de allereerste request. Dus ja — dit is geen simpel scrapingdoel. In deze gids loop ik door alle praktische manieren om Indeed met Python te scrapen, laat ik zien hoe je de 403-horde daadwerkelijk kunt overleven, en — voor wie liever het debuggen helemaal overslaat — demonstreer ik een no-code alternatief met Thunderbit.

Wat betekent het om Indeed met Python te scrapen?

Webscraping is in de kern het geautomatiseerd extraheren van gestructureerde data uit webpagina's. Als we het hebben over Indeed scrapen met Python, bedoelen we het schrijven van een script dat Indeed's zoekresultaten en vacaturepagina's bezoekt, de onderliggende HTML (of ingesloten data) leest, en velden als functietitel, bedrijf, locatie, salaris en beschrijving ophaalt in een bruikbaar formaat — een CSV, een database, een Google Sheet.

De gebruikelijke Python-bibliotheken hierbij zijn Requests (voor HTTP-calls), BeautifulSoup (voor HTML-parsing) en Selenium of Playwright (voor browserautomatisering). Maar Indeed is geen simpele statische site. Het is een hybride: server-side gerenderde HTML met een ingebedde JSON-stateblob, beveiligd door Cloudflare Bot Management. Dat betekent dat je scraper moet omgaan met JavaScript-gerenderde content, wisselende CSS-klassen en agressieve anti-botbescherming — nog vóór je één functietitel kunt parseren.

Er is in 2026 ook geen officiële, gratis, read-only Indeed API. De oude Publisher Jobs API werd rond 2020 uitgefaseerd, en wat resteert is alleen voor werkgevers (Job Sync, Sponsored Jobs). Scrapen of betalen voor een externe dataprovider zijn dus de enige realistische opties.

Waarom Indeed-vacaturedata scrapen?

De zakelijke reden om Indeed te scrapen is eenvoudig: handmatig door duizenden vacatures bladeren is onpraktisch, en de data in die vacatures is echt waardevol.

indeed_stats_dca2a43cec.png

GebruiksscenarioWie profiteertVoorbeeld
LeadgeneratieSales- en recruitingteamsLijsten bouwen van bedrijven die aannemen, met contactgegevens
Onderzoek naar de arbeidsmarktAnalisten, HR-teamsTrendende vaardigheden en salarismarkeringen per regio identificeren
Concurrentie-informatieWerkgevers, uitzendbureausInhuurpatronen en salarisaangeboden van concurrenten volgen
Automatisering van persoonlijke baanzoektochtWerkzoekendenVacatures bundelen die aan je criteria voldoen, over meerdere locaties
Trainingsdata voor ML-modellenData scientistsSalarisvoorspellingsmodellen bouwen op basis van historische vacaturedata

Indeed Hiring Lab bevestigt in eigen onderzoek dat vacaturedata nauw samenhangt met BLS JOLTS en kan dienen als een bijna realtime afspiegeling van de Amerikaanse arbeidsmarkt. Hedgefondsen gebruiken de snelheid waarmee vacatures worden geplaatst als alternatieve datasignaal. HR-teams benchmarken compensatie met behulp van gescrapete salarisschalen. En recruiters bouwen prospectlijsten op van bedrijven die actief personeel zoeken.

Een praktische opmerking: salarisdata op Indeed wordt beter, maar is nog steeds onvolledig. Medio 2025 bevat ongeveer 59% van de Amerikaanse vacatures salarisinformatie, maar slechts ongeveer 22% geeft een exact bedrag; de rest zijn bandbreedtes. Elke salarisanalyse op basis van Indeed-data moet rekening houden met die schaarste.

Je methode kiezen om Indeed met Python te scrapen

Er is niet één "juiste" manier om Indeed te scrapen. De beste aanpak hangt af van je vaardigheidsniveau, hoeveel data je nodig hebt en hoeveel onderhoud je wilt accepteren. Ik heb alle vier de belangrijkste aanpakken getest, en dit is hoe ze zich verhouden:

CriteriaBS4 + RequestsSeleniumVerborgen JSON (window.mosaic)No-code (Thunderbit)
MoeilijkheidsgraadBeginnerGemiddeldGemiddeld-gevorderdGeen (2 klikken)
SnelheidSnelTraag (browser renderen)SnelSnel (cloudscraping)
JS-gerenderde contentNeeJaJa (ingesloten data)Ja
Weerbaarheid tegen anti-botLaagMidden (detecteerbaar)Midden-hoogHoog (automatisch afgehandeld)
Onderhoud bij HTML-wijzigingenHoog (selectors breken)HoogGemiddeld (JSON-structuur stabieler)Geen (AI past zich aan)
Het beste voorSnelle prototypesDynamische pagina's, login-afgeschermdGrote hoeveelheden gestructureerde dataNiet-developers, snelle resultaten

Deze gids loopt door elke methode. Ben je Python-developer, lees dan vooral de BS4-, Verborgen JSON- en Selenium-secties. Ben je geen codeur (of ben je gewoon moe van 403-debugging), sla dan door naar het Thunderbit-gedeelte.

Voor je begint

  • Moeilijkheidsgraad: Beginner tot gemiddeld (Python-gedeelten); geen (Thunderbit-gedeelte)
  • Benodigde tijd: ~20–60 minuten voor Python-setup en eerste scrape; ~2 minuten met Thunderbit
  • Wat je nodig hebt: Python 3.9+, een code-editor, Chrome-browser en (voor de no-code route) de Thunderbit Chrome-extensie

Je Python-omgeving instellen voor Indeed-scraping

Voordat je scrapingcode schrijft, zet je omgeving goed klaar.

Installeer de vereiste bibliotheken

Maak een virtuele omgeving en installeer de pakketten die je nodig hebt:

python -m venv indeed_env
source indeed_env/bin/activate  # Op Windows: indeed_env\Scripts\activate

# Voor de HTTP + parsing-aanpak
pip install requests beautifulsoup4 lxml httpx

# Voor de verborgen JSON-aanpak (aanbevolen)
pip install curl_cffi parsel tenacity

# Voor de browserautomatiseringsaanpak
pip install selenium

Een paar opmerkingen:

  • curl_cffi is in 2026 de standaard voor het scrapen van sites achter Cloudflare. Het imiteert echte browser-TLS-fingerprints, iets wat gewone requests en httpx niet kunnen. Meer over waarom dit belangrijk is in de anti-botsectie.
  • Selenium 4.6+ wordt geleverd met Selenium Manager, dus je hoeft ChromeDriver niet langer handmatig te downloaden — het beheert de browserbinary automatisch.
  • Gebruik lxml als parser-backend voor BeautifulSoup. Die is ongeveer 1,5x sneller dan de standaard html.parser.

Maak je projectstructuur aan

Houd het simpel:

indeed_scraper/
├── scraper.py
├── requirements.txt
└── output/

Alle codevoorbeelden hieronder bouwen verder op scraper.py.

Hoe je Indeed met Python scrapt met BeautifulSoup

Dit is de beginnersvriendelijke aanpak: gebruik requests om de pagina op te halen en BeautifulSoup om de HTML te parsen. Het is het snelst om op te zetten, maar ook het kwetsbaarst op Indeed.

Stap 1: Bouw de Indeed-zoek-URL

Indeed's zoek-URL's volgen een voorspelbaar patroon:

https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>

Bijvoorbeeld, zoeken naar "data analyst" in "Austin, TX", beginnend op de eerste pagina:

from urllib.parse import urlencode

params = {
    "q": "data analyst",
    "l": "Austin, TX",
    "start": 0,
}
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
print(url)
# https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX&start=0

Indeed pagineert in stappen van 10, met een harde limiet van 1.000 resultaten (start <= 990). Elke offset boven 990 geeft stilzwijgend dezelfde pagina terug.

Stap 2: Stuur een HTTP-verzoek met de juiste headers

Indeed blokkeert requests met standaard Python user-agent strings direct. Je hebt realistische headers nodig:

import requests

headers = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
    ),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Referer": "https://www.indeed.com/",
}

response = requests.get(url, headers=headers, timeout=30)
print(response.status_code)

Als je een 200 krijgt, zit je erin — voorlopig. Krijg je een 403, dan heeft Cloudflare je gepakt. (Meer over hoe je daar doorheen komt hieronder.)

Stap 3: Parse vacatures uit de HTML

Gebruik BeautifulSoup om vacaturekaart-elementen te selecteren. Richt je op data-testid-attributen — die zijn stabieler dan Indeed's willekeurig veranderende CSS-klassen:

from bs4 import BeautifulSoup

soup = BeautifulSoup(response.text, "lxml")
cards = soup.find_all("div", attrs={"data-testid": "slider_item"})

jobs = []
for card in cards:
    title_el = card.find("h2", class_="jobTitle")
    title = title_el.get_text(strip=True) if title_el else None
    company = card.find(attrs={"data-testid": "company-name"})
    location = card.find(attrs={"data-testid": "text-location"})
    link = title_el.find("a")["href"] if title_el and title_el.find("a") else None

    jobs.append({
        "title": title,
        "company": company.get_text(strip=True) if company else None,
        "location": location.get_text(strip=True) if location else None,
        "url": f"https://www.indeed.com{link}" if link else None,
    })

print(f"Gevonden vacatures: {len(jobs)}")

Stap 4: Omgaan met paginering

Loop door de pagina's door de start-parameter te verhogen:

import time, random

all_jobs = []
for page in range(0, 50, 10):  # Eerste 5 pagina's
    params["start"] = page
    url = f"https://www.indeed.com/jobs?{urlencode(params)}"
    response = requests.get(url, headers=headers, timeout=30)
    # ... parse zoals hierboven ...
    all_jobs.extend(jobs)
    time.sleep(random.uniform(3, 6))

Beperkingen van deze aanpak

Ik ben daar eerlijk over: BS4 + Requests is in 2026 de zwakste methode voor Indeed. Gewone requests gebruikt Python's standaard TLS-bibliotheek, die een JA3-fingerprint produceert die Cloudflare direct herkent als "geen browser". Bovendien ondersteunt het geen HTTP/2, terwijl Indeed dat wel aanbiedt. Je loopt waarschijnlijk tegen blokkades aan na een handvol pagina's. En die CSS-selectors? Indeed roteert klassen als css-1m4cuuf en jobsearch-JobComponent-embeddedBody-1n0gh5s regelmatig — elke selector daarop is dus een tikkende tijdbom.

Gebruik deze methode voor snelle prototyping op één pagina. Voor iets op schaal gebruik je beter de verborgen JSON-aanpak.

Hoe je Indeed met Python scrapt met verborgen JSON-data

Dit is de methode die ik de meeste Python-developers aanraad. In plaats van fragiele HTML-elementen te parsen, haal je gestructureerde data uit een JavaScript-variabele die in de broncode van Indeed zit: window.mosaic.providerData["mosaic-provider-jobcards"].

Elk veld dat je nodig hebt — functietitel, bedrijf, locatie, salaris, vacaturekey, publicatiedatum, remote-indicator — zit al in die JSON-blob. Geen JavaScript-uitvoering nodig. Het schema is minstens sinds 2023 stabiel, waardoor het veel robuuster is dan DOM-selectors.

Stap 1: Haal de HTML van de pagina op

Gebruik curl_cffi in plaats van requests — het imiteert echte browser-TLS-fingerprints, wat cruciaal is om Cloudflare te overleven:

from curl_cffi import requests as cffi_requests

response = cffi_requests.get(
    "https://www.indeed.com/jobs?q=python+developer&l=Remote&start=0",
    impersonate="chrome124",
    headers={
        "Accept-Language": "en-US,en;q=0.9",
        "Referer": "https://www.indeed.com/",
    },
    timeout=30,
)
print(response.status_code, len(response.text))

Waarom curl_cffi? Het is een Python-binding rond curl-impersonate, die exact de TLS ClientHello, HTTP/2 SETTINGS-frame en header-volgorde van echte browsers reproduceert. Het is de enige actief onderhouden Python HTTP-client die zowel JA3/JA4 als de Akamai H2-fingerprint in één call omzeilt. Ondersteunde impersonatie-doelen zijn onder andere chrome120, chrome124, chrome131, Safari en Edge-varianten.

Stap 2: Extraheer de JSON met een reguliere expressie

De JSON-blob zit ingebed in een <script>-tag. Haal hem eruit met een regex:

import re, json

MOSAIC_RE = re.compile(
    r'window\.mosaic\.providerData\["mosaic-provider-jobcards"\]=(\{.+?\});',
    re.DOTALL,
)

match = MOSAIC_RE.search(response.text)
if match:
    data = json.loads(match.group(1))
    results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
    print(f"Gevonden vacatures in verborgen JSON: {len(results)}")
else:
    print("Verborgen JSON niet gevonden — mogelijk blokkade of paginawijziging")

Stap 3: Parse vacaturevelden uit de JSON

Elk item in results bevat meer data dan wat zichtbaar is op de pagina:

jobs = []
for job in results:
    jobs.append({
        "jobkey": job["jobkey"],
        "title": job["title"],
        "company": job.get("company"),
        "location": job.get("formattedLocation"),
        "remote": job.get("remoteLocation"),
        "salary": (job.get("salarySnippet") or {}).get("text"),
        "posted": job.get("formattedRelativeTime"),
        "job_type": job.get("jobTypes"),
        "easy_apply": job.get("indeedApplyEnabled"),
        "url": f"https://www.indeed.com/viewjob?jk={job['jobkey']}",
    })

De JSON bevat vaak ook salarisinschattingen, taxonomie-attributen (vaardigheidstags) en bedrijfsbeoordelingen die niet altijd zichtbaar zijn in de gerenderde HTML.

Stap 4: Scrape meerdere pagina's

Gebruik tierSummaries in de JSON om het totale aantal resultaten te begrijpen, en loop dan door de pagina's:

import time, random

all_jobs = []
for start in range(0, 50, 10):  # Eerste 5 pagina's
    url = f"https://www.indeed.com/jobs?q=python+developer&l=Remote&start={start}&sort=date"
    response = cffi_requests.get(
        url,
        impersonate="chrome124",
        headers={"Accept-Language": "en-US,en;q=0.9", "Referer": "https://www.indeed.com/"},
        timeout=30,
    )
    match = MOSAIC_RE.search(response.text)
    if match:
        data = json.loads(match.group(1))
        results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
        all_jobs.extend([{
            "jobkey": j["jobkey"],
            "title": j["title"],
            "company": j.get("company"),
            "location": j.get("formattedLocation"),
            "salary": (j.get("salarySnippet") or {}).get("text"),
            "url": f"https://www.indeed.com/viewjob?jk={j['jobkey']}",
        } for j in results])
    time.sleep(random.uniform(3, 7))

print(f"Totaal: {len(all_jobs)} vacatures gescrapet")

Waarom verborgen JSON robuuster is

De window.mosaic.providerData-structuur verandert minder vaak dan CSS-klassen. Je krijgt schone, gestructureerde data zonder rommelige HTML te hoeven parsen. Dat gezegd hebbende: je hebt nog steeds anti-botmaatregelen nodig (headers, vertragingen, proxies) — en die komen straks aan bod.

Hoe je Indeed met Python scrapt met Selenium

Selenium is de browserautomatiseringsaanpak. Die is handig wanneer je met de pagina moet interacteren — doorklikken naar vacaturedetailpanelen, content achter login afhandelen, of dynamisch geladen beschrijvingen scrapen die niet in de initiële HTML staan.

Wanneer Selenium gebruiken in plaats van HTTP-clients

  • Indeed laadt bepaalde content dynamisch (volledige vacaturebeschrijvingen in het rechterpaneel)
  • Je moet pagina's scrapen waarvoor sessiestatus of login nodig is
  • Je doet kleinschalig scrapen waarbij snelheid niet cruciaal is

Snelle walkthrough

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
import time

options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
# options.add_argument("--headless=new")  # Headless is beter detecteerbaar — gebruik voorzichtig

driver = webdriver.Chrome(options=options)
driver.get("https://www.indeed.com/jobs?q=data+engineer&l=New+York")
time.sleep(3)

cards = driver.find_elements(By.CSS_SELECTOR, "[data-testid='slider_item']")
for card in cards:
    try:
        title = card.find_element(By.CSS_SELECTOR, "h2.jobTitle").text
        company = card.find_element(By.CSS_SELECTOR, "[data-testid='company-name']").text
        location = card.find_element(By.CSS_SELECTOR, "[data-testid='text-location']").text
        print(f"{title} | {company} | {location}")
    except Exception:
        continue

driver.quit()

Beperkingen

Selenium is traag — elke pagina moet volledig in de browser worden gerenderd. Headless Chrome is detecteerbaar door Indeed's anti-botsysteem (Cloudflare controleert navigator.webdriver, WebGL-vendor strings, pluginaantallen en meer). Zelfs undetected-chromedriver vertraagt detectie slechts; het voorkomt die niet permanent. En net als bij BS4 breken je selectors wanneer Indeed zijn UI aanpast.

Voor de meeste use-cases levert de verborgen JSON-aanpak dezelfde data, sneller en met minder onderhoud. Bewaar Selenium voor edge cases waarin je echt een browser nodig hebt.

Hoe je 403-fouten vermijdt bij het scrapen van Indeed met Python

Dit is de sectie die het belangrijkst is. Als je hier bent beland na een gefrustreerde Google-zoekopdracht, zit je op de juiste plek.

indeed_antibot_374d080ff4.png

Waarom Indeed je scraper blokkeert

Indeed gebruikt Cloudflare Bot Management plus Cloudflare Turnstile — niet DataDome, niet PerimeterX. Dat zie je terug in de responseheaders: server: cloudflare, cf-ray en de __cf_bm-cookie voor botmanagement. Cloudflare inspecteert je TLS-fingerprint (JA3/JA4), de volgorde van HTTP/2-headers, requestpatronen en signalen van browsergedrag. Als iets daarvan niet menselijk lijkt, krijg je een 403, 429, 503 of — het sluwste geval — een 200 OK met een Turnstile-uitdagingspagina in plaats van echte vacaturedata.

Roteer User-Agent en requestheaders

Eén statische User-Agent is de snelste manier om geblokkeerd te worden. Roteer uit een pool van actuele, realistische strings. Belangrijk: de minor-versionvelden van Chrome zijn sinds User-Agent Reduction bevroren op 0.0.0 — verzin geen niet-nul minor-versies, anders gaan anti-bots daar direct op af.

import random

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.3800.97",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
    "(KHTML, like Gecko) Version/17.4 Safari/605.1.15",
]

headers = {
    "User-Agent": random.choice(USER_AGENTS),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br, zstd",
    "Referer": "https://www.indeed.com/",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "same-origin",
}

Zorg er ook voor dat je sec-ch-ua Client Hints overeenkomen met de UA-versie. Een sec-ch-ua: "Chrome";v="131" naast een User-Agent die Chrome 145 claimt, is een directe rode vlag.

Voeg willekeurige vertragingen tussen requests toe

Vaste intervallen worden door patroonherkenning opgepikt. Gebruik willekeurige jitter:

import time, random

# Tussen elke request
time.sleep(random.uniform(3, 6))

# Bij opnieuw proberen na een blokkade
def backoff_sleep(attempt):
    base = 4
    sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
    time.sleep(min(sleep_time, 60))

De consensus uit ScrapeOps en WebScraping.AI is 3–6 seconden tussen requests per IP, met een harde limiet van ongeveer 100 requests per IP per sessie voordat je roteert.

Gebruik proxyrotatie

Dit is veruit de grootste succesfactor. Datacenterproxies uit AWS/GCP bereiken op Cloudflare Enterprise-doelen slechts ongeveer 5–15% succes — in de praktijk onbruikbaar op Indeed. Residential proxies plus correcte TLS-fingerprinting brengen dat op 80–95% succes.

PROXIES = [
    "http://user:pass@us.residential.example:7777",
    "http://user:pass@us.residential.example:7778",
    "http://user:pass@us.residential.example:7779",
]

proxy = random.choice(PROXIES)
response = cffi_requests.get(
    url,
    impersonate="chrome124",
    headers=headers,
    proxies={"https": proxy},
    timeout=30,
)

De prijs van residential proxies in 2026 ligt ruwweg op $4–8,50 per GB, afhankelijk van aanbieder en contractniveau. Voor Indeed specifiek kun je het best beginnen met een kleine pool en die uitbouwen wanneer nodig.

Ga netjes om met 403-, 429- en 503-statuscodes

Herhaal niet blind. Verschillende statuscodes betekenen verschillende dingen:

def fetch_with_retry(url, proxy_pool, max_retries=5):
    for attempt in range(max_retries):
        proxy = random.choice(proxy_pool)
        headers["User-Agent"] = random.choice(USER_AGENTS)
        try:
            r = cffi_requests.get(
                url,
                impersonate=random.choice(["chrome124", "chrome120", "edge101"]),
                headers=headers,
                proxies={"https": proxy},
                timeout=30,
            )
            # Controleer op het sluwe "200 met challenge"-geval
            if r.status_code == 200 and "cf-turnstile" not in r.text and "Just a moment" not in r.text:
                return r

            if r.status_code == 403:
                print(f"403 — geblokkeerd. Proxy roteren, poging {attempt + 1}")
            elif r.status_code == 429:
                print(f"429 — rate limited. Langzamer gaan.")
            elif r.status_code == 503:
                print(f"503 — server overbelast of JS-challenge.")

            backoff_sleep(attempt)
        except Exception as e:
            print(f"Verzoekfout: {e}")
            backoff_sleep(attempt)

    raise RuntimeError(f"Mislukt na {max_retries} pogingen: {url}")

Het 200-met-challenge-geval is het lastigst. Scan altijd de responsebody op cf-turnstile of Just a moment voordat je een 200 als succes behandelt.

Het eenvoudigere alternatief: laat Thunderbit de anti-botafhandeling doen

Voor gebruikers die geen proxy-pools, headerrotatie en TLS-fingerprint-imitatie willen bouwen en onderhouden, handelt Thunderbit's cloudscraping CAPTCHA's, proxyrotatie en anti-botbescherming automatisch af. Geen proxy-instelling, geen curl_cffi-configuratie, geen CAPTCHA-oplossingsbibliotheken. Het is de weg van de minste weerstand wanneer je gewoon de data nodig hebt.

Waarom je Indeed-scraper steeds kapotgaat (en hoe je dat oplost)

De 403-muur is de acute pijn. De chronische pijn is onderhoud — scrapers die vandaag werken, vallen volgende week uit en geven stilletjes lege data of verouderde resultaten terug.

Hoe Indeed je selectors kapotmaakt

Indeed roteert CSS-klassen agressief. Bright Data's gids waarschuwt expliciet dat klassen als css-1m4cuuf en css-1rqpxry "willekeurig gegenereerd lijken — waarschijnlijk tijdens build-tijd." A/B-testen zorgen ervoor dat verschillende sessies andere paginalay-outs zien. En DOM-herstructurering gebeurt zonder waarschuwing.

De JobFunnel-saga is leerzaam. Een bijdrager meldde: "CaptchaBuster heeft de captcha succesvol gemitigeerd, en de reden dat de pagina nog steeds niet succesvol werd gescrapet [is] verouderde Beautiful Soup-selectors." De scraper werd dus niet geblokkeerd — hij parseerde gewoon de verkeerde elementen.

Strategie: geef voorkeur aan verborgen JSON boven DOM-parsing

De window.mosaic.providerData-blob is qua schema stabiel sinds minstens 2023. Het pad metaData.mosaicProviderJobCardsModel.results[] is in 2026 nog steeds canoniek. DOM-selectors breken maandelijks. JSON-extractie breekt hooguit jaarlijks, als het al gebeurt.

Strategie: gebruik data-attributen in plaats van klassennamen

Als je toch de DOM moet aanraken, richt je dan op functionele attributen:

SelectorDoel
[data-testid="slider_item"]Container van elke vacaturekaart
[data-testid="job-title"] of h2.jobTitle > aLink naar de functietitel
[data-testid="company-name"]Naam van de werkgever
[data-testid="text-location"]Locatietekst
data-jk="<jobkey>" op elke kaartDe meest stabiele hook — onveranderd sinds 2019

Voeg assertion-checks toe om verouderde selectors te detecteren

Laat je scraper nooit stilletjes met nul resultaten draaien. Voeg na elke fetch een controle toe:

results = parse_hidden_json(html)
assert len(results) > 0, (
    f"Indeed gaf een lege resultatenreeks terug bij start={start} — "
    "mogelijke blokkade, CAPTCHA of selector-drift. "
    f"Eerste 500 tekens van de response: {html[:500]}"
)

Log bij fouten de eerste 500–2000 tekens van de ruwe response. Dan zie je meteen of je een Turnstile-challenge, een inlogmuur of een schemawijziging hebt gekregen. Draai dagelijks een eenvoudige CI-test tegen een vaste query (bijv. q=python&l=remote) die controleert op niet-nul resultaten.

Het AI-alternatief: scrapers die nooit kapotgaan

Thunderbit's AI leest elke keer opnieuw de structuur van de pagina — het is niet afhankelijk van hardcoded selectors of regex-patronen. Als Indeed zijn HTML wijzigt, past Thunderbit zich automatisch aan. Daarmee pakt het precies die onderhoudslast aan die forumgebruikers steeds als hun grootste frustratie noemen. Als je ooit wakker werd met een Slack-bericht dat "de scraper weer lege rijen teruggeeft", weet je hoe waardevol het is om dat niet te hoeven oplossen.

Indeed scrapen zonder Python te schrijven: het no-code alternatief

Elke concurrerende gids gaat ervan uit dat je Python-code gaat schrijven. Maar de forumdata vertelt een ander verhaal. Gebruikers zeggen dingen als "het is gewoon zo moeilijk met constante bugs en fouten" en sommigen stellen voor iemand op Fiverr in te huren om de data überhaupt binnen te krijgen. Klinkt dat herkenbaar, dan is dit jouw uitweg.

Hoe je Indeed met Thunderbit scrapt (stap voor stap)

Stap 1: Installeer de Thunderbit Chrome-extensie vanuit de Chrome Web Store. Je kunt gratis beginnen.

Stap 2: Ga in je browser naar een Indeed-zoekresultatenpagina — bijvoorbeeld https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.

Stap 3: Klik op het Thunderbit-pictogram in je browserwerkbalk en klik daarna op "AI-velden voorstellen." Thunderbit's AI scant de pagina en detecteert automatisch kolommen als functietitel, bedrijf, locatie, salaris, vacature-URL en publicatiedatum. Je kunt de voorgestelde velden bekijken en aanpassen — kolommen verwijderen die je niet nodig hebt, of aangepaste kolommen toevoegen door in gewone taal te beschrijven wat je wilt.

Stap 4: Klik op "Scrapen." Thunderbit haalt de data van de pagina en toont die in een gestructureerde tabel. Je zou rijen met vacatures moeten zien, inclusief de velden die je hebt ingesteld.

Verrijking met subpagina-scraping

Na het scrapen van de lijstpagina klik je op "Subpagina's scrapen" zodat Thunderbit elke individuele vacaturedetailpagina bezoekt. Het haalt volledige vacaturebeschrijvingen, vereisten, voordelen en sollicitatielinks op — zonder extra configuratie. Dit is het equivalent van het schrijven van een tweede Python-scraper die elke /viewjob?jk=<jobkey>-URL bezoekt, maar dan met één klik.

Paginering automatisch afhandelen

Thunderbit handelt Indeed's paginering op basis van klikken automatisch af. Je hoeft geen offset-URL's handmatig samen te stellen of pagineringslussen te schrijven. Het klikt door de pagina's heen en voegt de resultaten samen.

Exporteren naar je favoriete tools

Exporteer gescrapete data naar CSV, Excel, Google Sheets, Airtable of Notion — volledig gratis. Je hoeft geen code te schrijven voor csv.writer() of pandas.to_csv().

Wanneer Python gebruiken en wanneer Thunderbit

ScenarioBeste tool
Aangepaste datapijplijnen, geplande automatisering via cron/AirflowPython
Integratie in een grotere codebasePython
Sterk aangepaste parsingslogicaPython
Eenmalig onderzoek of marktanalyseThunderbit
Niet-technische teamleden hebben data nodigThunderbit
Nu data ophalen zonder 403-fouten te debuggenThunderbit
Verrijking van subpagina's zonder setupThunderbit

Tijdvergelijking: Python-setup + anti-botdebugging = uren tot dagen (zeker de eerste keer). Thunderbit = minder dan 2 minuten voor dezelfde data. Ik zeg niet dat Python verkeerd is — ik zeg dat het afhangt van wat je nodig hebt.

Is Indeed scrapen legaal? Wat je moet weten

Geen van de best scorende gidsen over Indeed-scraping gaat in op legaliteit, wat opvallend is gezien hoe vaak "Is Indeed scrapen legaal?" opduikt in forums. Dit is geen juridisch advies, maar dit is het speelveld.

Indeed's servicevoorwaarden

Indeed's ToS (indeed.com/legal) bevat geen algemene "niet scrapen"-clausule. De enige expliciete verbodsbepaling rond automatisering staat in Sectie A.3.5, waar "gebruik van automatisering, scripting of bots om het Indeed Apply-proces te automatiseren" wordt verboden. Dat is beperkt tot de Apply-flow, niet tot het passief lezen van publieke vacaturelijsten. Indeed's belangrijkste handhavingsmiddel is technisch — Cloudflare-challenges, IP-bans, device fingerprinting — niet de rechtszaal.

Relevant juridisch precedent

De vaakst aangehaalde Amerikaanse zaak is hiQ Labs v. LinkedIn. Het 9th Circuit oordeelde in april 2022 dat het scrapen van publiek toegankelijke data "waarschijnlijk niet in strijd is met de CFAA" (Computer Fraud and Abuse Act). Later werd hiQ echter aansprakelijk bevonden wegens contractbreuk omdat medewerkers nep-LinkedIn-profielen hadden aangemaakt en de ToS hadden geaccepteerd.

Meer recent leverde Meta v. Bright Data (N.D. Cal., jan. 2024) een nog duidelijker uitspraak op. Rechter Chen oordeelde dat de voorwaarden van Facebook en Instagram "het scrapen van publieke data wanneer men is uitgelogd niet verbieden." Meta trok de resterende claims de maand erna vrijwillig in.

Indeed's robots.txt

Indeed's robots.txt verbiedt in algemene zin /jobs/ en /job/ voor de standaard User-agent: *, maar staat Googlebot en Bingbot expliciet toe om /viewjob? te benaderen — de individuele vacaturedetailpagina's. AI-trainingscrawlers (GPTBot, CCBot, anthropic-ai) worden sterk beperkt. robots.txt is in de VS niet juridisch bindend, maar het respecteren ervan is wel een best practice en een bewijs van goede trouw.

Praktische richtlijnen voor verantwoord scrapen

  • Scrape alleen publiek beschikbare data — log nooit in, maak nooit nepaccounts aan
  • Respecteer rate limits: 1 request per 3–6 seconden per IP, lage gelijktijdigheid
  • Herpubliceer gescrapete data niet als je eigen vacaturebank
  • Gebruik data voor persoonlijke of interne research, niet voor commerciële doorverkoop zonder toestemming
  • Gooi onnodige PII weg of hash die; hanteer een bewaartermijn voor persoonsgegevens en daaraan gerelateerde data
  • Als je op grote schaal werkt of in de EU/het VK actief bent, raadpleeg een jurist — GDPR's transparantieverplichtingen uit artikel 14 gelden voor gescrapete persoonsgegevens

Het risicospectrum: persoonlijke automatisering van je baanzoektocht zit aan de lage kant. Commerciële doorverkoop op grote schaal van Indeed-data zit aan de hoge kant.

Conclusie en belangrijkste lessen

Indeed scrapen met Python is zeker mogelijk, maar het is geen weekendproject dat je één keer opzet en daarna vergeet. Indeed's Cloudflare-bescherming, wisselende selectors en agressieve anti-botmaatregelen betekenen dat je dit met de juiste tools en verwachtingen moet aanpakken.

Dit is wat ik hieruit zou meenemen:

  • Indeed is de rijkste bron van arbeidsmarktdata op het web — 350 miljoen maandelijkse bezoekers, 130 miljoen vacatures — maar het vecht hard terug tegen scrapers.
  • Extractie via verborgen JSON (window.mosaic.providerData) is de meest robuuste Python-aanpak. Het schema is al jaren stabiel, terwijl CSS-selectors maandelijks breken.
  • curl_cffi met browser-impersonatie is in 2026 de standaard HTTP-client voor sites achter Cloudflare. Gewone requests en httpx worden al op basis van de TLS-fingerprint geblokkeerd.
  • Gebruik altijd roterende headers, willekeurige vertragingen en residential proxies om 403-fouten te vermijden. Datacenterproxies zijn bijna waardeloos tegen Cloudflare Enterprise.
  • Voeg assertion-checks toe zodat je meteen weet wanneer selectors breken of wanneer je in plaats van vacaturedata een challenge-pagina krijgt.
  • Voor niet-technische gebruikers of iedereen die snel resultaat wil, biedt Thunderbit een no-code, AI-gedreven route die automatisch meebeweegt met sitewijzigingen — geen proxies, geen debugging, geen onderhoud.

Wil je de no-code-route proberen, dan biedt Thunderbit een gratis laag zodat je het zonder verplichtingen op Indeed kunt testen. En kies je voor Python, dan zijn de codevoorbeelden hierboven een solide startpunt — onthoud alleen dat anti-botweerbaarheid een eerste prioriteit moet zijn, niet iets voor later.

Voor meer informatie over webscraping-aanpakken en -tools, bekijk onze gidsen over webscraping met Python, de beste geautomatiseerde webscrapingtools, en webscraping zonder geblokkeerd te worden. Je kunt ook tutorials bekijken op het Thunderbit YouTube-kanaal.

Probeer Thunderbit om Indeed-data sneller te scrapen Get Started Free

FAQ's

Welke Python-bibliotheken zijn het beste voor het scrapen van Indeed?

Voor HTTP-verzoeken is curl_cffi in 2026 de sterkste keuze — het imiteert echte browser-TLS-fingerprints, wat essentieel is om Cloudflare te omzeilen. httpx met HTTP/2 is een degelijke fallback voor minder goed beveiligde doelen. Voor HTML-parsing blijft BeautifulSoup4 met lxml de standaard. Voor browserautomatisering werken Playwright (met playwright-stealth) of undetected-chromedriver, al worden beide steeds beter detecteerbaar. De verborgen JSON-regexaanpak (window.mosaic.providerData) maakt zware parsing helemaal overbodig.

Waarom krijg ik steeds 403-fouten bij het scrapen van Indeed?

Indeed gebruikt Cloudflare Bot Management, dat je TLS-fingerprint (JA3/JA4), HTTP/2-header-volgorde, requestpatronen en browsergedrag inspecteert. Als je gewone requests gebruikt, verraadt je TLS-fingerprint je direct als Python-script — de 403 komt al voordat je headers überhaupt worden gelezen. Los dit op door over te stappen op curl_cffi met browser-impersonatie, realistische roterende User-Agent-strings, willekeurige vertragingen (3–6 seconden) en residential proxies. Controleer ook op het geval "200 met Turnstile-challenge" — scan response bodies op cf-turnstile-markeringen.

Kan ik Indeed scrapen zonder te coderen?

Ja. Tools zoals Thunderbit laten je Indeed-vacatures in een paar klikken extraheren — installeer de Chrome-extensie, ga naar een Indeed-zoekpagina, klik op "AI-velden voorstellen" en daarna op "Scrapen." Thunderbit's AI detecteert automatisch velden als functietitel, bedrijf, locatie en salaris. Het handelt paginering, verrijking van subpagina's (volledige vacaturebeschrijvingen) en anti-botbescherming automatisch af. Exporteer gratis naar CSV, Google Sheets, Airtable of Notion.

Hoe vaak verandert Indeed zijn HTML-structuur?

Indeed roteert regelmatig CSS-klassen (bijv. css-1m4cuuf, willekeurig gehashte strings) en herstructureert DOM-elementen zonder waarschuwing. A/B-testen zorgen ervoor dat verschillende gebruikers tegelijk andere lay-outs kunnen zien. De verborgen JSON-aanpak (window.mosaic.providerData) is aanzienlijk stabieler — het schema is sinds minstens 2023 consistent. Wanneer je toch DOM-selectors moet gebruiken, richt je op data-testid-attributen en data-jk (vacaturekey) in plaats van CSS-klassen.

Is het legaal om Indeed te scrapen?

Scraping terwijl je bent uitgelogd en publiek toegankelijke Indeed-vacature-URL's bekijkt, leidt in de VS waarschijnlijk niet tot CFAA-aansprakelijkheid, op basis van het 9th Circuit-oordeel in hiQ v. LinkedIn (2022) en de Meta v. Bright Data-uitspraak (2024). Indeed's voorwaarden verbieden specifiek het automatiseren van het Apply-proces, niet het passief lezen van openbare lijsten. Toch geldt: scrape altijd verantwoord. Log niet in, maak geen nepaccounts aan, respecteer rate limits, herpubliceer data niet als je eigen vacaturebank en ga zorgvuldig om met persoonsgegevens (zoals recruitersnamen en e-mails) onder GDPR/CCPA. Voor commerciële activiteiten op schaal: raadpleeg een jurist.

Meer leren

Fawad Khan
Fawad Khan
Fawad schrijft voor zijn werk en eerlijk gezegd vindt hij dat best leuk. Hij heeft jaren besteed aan het uitzoeken wat een tekst laat hangen — en wat lezers juist laat doorscrollen. Vraag hem naar marketing en hij kan er uren over praten. Vraag hem naar carbonara en hij doet er nog een schep bovenop.
Inhoudsopgave

Vraag het en scrape een webpagina

Zeg gewoon in gewone taal wat je nodig hebt. Of nog beter: zeg helemaal niets.

Probeer Thunderbit gratis
Data extraheren met AI
Gegevens eenvoudig overzetten naar Google Sheets, Airtable of Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week