Wer schon einmal versucht hat, strukturierte Daten programmgesteuert aus Indeed zu ziehen, kennt das Problem. Der 403-Fehler ist kein Bug, sondern ein gewolltes Element von Indeeds Verteidigungssystem. Die Plattform wehrt sich nach Kräften gegen Scraper, und genau das macht sie zu einem der anspruchsvollsten Ziele überhaupt.
Indeed ist das weltweit größte Jobportal mit rund 350 Millionen eindeutigen monatlichen Besuchern, zu jedem Zeitpunkt 130 Millionen Stellenanzeigen und Aktivität in über 60 Ländern. Das macht die Plattform zu einer der reichsten Arbeitsmarktdatenquellen des Planeten und zugleich zu einer der am schwersten zu scrapenden. Der Open-Source-Scraper JobFunnel (mit Tausenden GitHub-Stars) wurde im Dezember 2025 von seinem Maintainer förmlich archiviert, nachdem der jahrelange Anti-Bot-Wettlauf verloren war. So formulierte es der Maintainer: „Alle Nutzer können einige Jobs scrapen, werden aber schnell von Captchas getroffen, und das Scraping schlägt fehl, sodass keine Jobs zurückgegeben werden.“ Ein anderer Mitwirkender berichtete, dass schon beim allerersten Request ein CAPTCHA auftauchte. Ein triviales Scraping-Ziel sieht anders aus. Im Folgenden gehe ich alle praktikablen Wege durch, Indeed mit Python zu scrapen, zeige, wie du die 403-Hürde tatsächlich überwindest, und stelle für alle, die das Debugging lieber komplett auslassen, eine No-Code-Alternative mit Thunderbit vor.
Was bedeutet es, Indeed mit Python zu scrapen?
Web-Scraping ist im Kern das automatisierte Extrahieren strukturierter Daten aus Webseiten. „Indeed mit Python scrapen“ heißt konkret: ein Skript schreiben, das die Suchergebnisse und Stellen-Detailseiten von Indeed aufruft, das zugrunde liegende HTML (oder die eingebetteten Daten) ausliest und Felder wie Stellenbezeichnung, Unternehmen, Ort, Gehalt und Beschreibung in ein nutzbares Format überführt, etwa CSV, eine Datenbank oder ein Google Sheet.
Typische Python-Bibliotheken dafür sind Requests (für HTTP-Aufrufe), BeautifulSoup (für HTML-Parsing) und Selenium oder Playwright (für Browser-Automatisierung). Indeed ist allerdings keine schlichte statische Website, sondern ein Hybrid: servergerendertes HTML mit einem eingebetteten JSON-Datenblock, abgesichert durch Cloudflare Bot Management. Dein Scraper muss also JavaScript-gerenderte Inhalte, wechselnde CSS-Klassennamen und aggressive Anti-Bot-Schutzmaßnahmen meistern, und zwar alles, bevor du überhaupt die erste Stellenbezeichnung geparst hast.
Hinzu kommt: 2026 existiert keine offizielle, kostenlose, nur lesende Indeed-API. Die alte Publisher Jobs API wurde um 2020 abgeschaltet, geblieben sind nur Arbeitgeber-Funktionen (Job Sync, Sponsored Jobs). Damit bleiben als realistische Optionen nur Scraping oder ein kostenpflichtiger Drittanbieter.
Warum Indeed-Jobdaten scrapen?
Der Business-Case liegt auf der Hand: Tausende Anzeigen von Hand durchzugehen ist undurchführbar, und die darin steckenden Daten sind enorm wertvoll.

| Anwendungsfall | Wer profitiert | Beispiel |
|---|---|---|
| Leadgenerierung | Sales- und Recruiting-Teams | Listen von einstellenden Unternehmen mit Kontaktdaten erstellen |
| Arbeitsmarktforschung | Analysten, HR-Teams | Trendfähigkeiten und Gehaltsbenchmarks nach Region identifizieren |
| Wettbewerbsanalyse | Arbeitgeber, Personaldienstleister | Einstellungsmuster und Gehaltsangebote von Wettbewerbern überwachen |
| Automatisierte Jobsuche für Privatpersonen | Jobsuchende | Anzeigen aus verschiedenen Regionen nach den eigenen Kriterien bündeln |
| Trainingsdaten für ML-Modelle | Data Scientists | Gehaltsprognosemodelle aus historischen Daten aufbauen |
Die Forschung des Indeed Hiring Lab zeigt selbst, dass Stellenausschreibungsdaten die BLS-JOLTS-Daten eng nachbilden und als nahezu Echtzeit-Indikator für den US-Arbeitsmarkt taugen. Hedgefonds nutzen die Dynamik von Stellenanzeigen als alternatives Datensignal. HR-Teams benchmarken Vergütungen über gescrapte Gehaltsspannen. Und Recruiter erstellen Prospect-Listen aus Unternehmen, die aktiv einstellen.
Ein Hinweis aus der Praxis: Die Gehaltsdaten auf Indeed werden besser, bleiben aber lückenhaft. Mitte 2025 enthalten rund 59 % der US-Anzeigen Gehaltsangaben, doch nur etwa 22 % nennen einen exakten Betrag, der Rest sind Spannen. Jede Gehaltsanalyse auf Basis von Indeed-Daten sollte diese Lücken einkalkulieren.
So wählst du deine Methode, um Indeed mit Python zu scrapen
Den einen „richtigen“ Weg, Indeed zu scrapen, gibt es nicht. Der beste Ansatz hängt von deinem Kenntnisstand ab, von der benötigten Datenmenge und davon, wie viel Wartungsaufwand du zu akzeptieren bereit bist. Ich habe alle vier großen Ansätze getestet, und so fällt das Ergebnis aus:
| Kriterium | BS4 + Requests | Selenium | Verstecktes JSON (window.mosaic) | No-Code (Thunderbit) |
|---|---|---|---|---|
| Schwierigkeit | Anfänger | Fortgeschrittene | Fortgeschritten bis sehr fortgeschritten | Keine (2 Klicks) |
| Geschwindigkeit | Schnell | Langsam (Browser-Rendering) | Schnell | Schnell (Cloud-Scraping) |
| JS-gerenderte Inhalte | Nein | Ja | Ja (eingebettete Daten) | Ja |
| Widerstand gegen Bot-Schutz | Niedrig | Mittel (erkennbar) | Mittel bis hoch | Hoch (automatisch gehandhabt) |
| Wartung bei HTML-Änderungen | Hoch (Selektoren brechen) | Hoch | Mittel (JSON-Struktur stabiler) | Keine (KI passt sich an) |
| Am besten geeignet für | Schnelle Prototypen | Dynamische Seiten, Login-geschützte Inhalte | Strukturierte Massendaten | Nicht-Entwickler, schnelle Ergebnisse |
Dieser Leitfaden geht jede Methode durch. Bist du Python-Entwickler, lies die Abschnitte zu BS4, verstecktem JSON und Selenium. Schreibst du keinen Code (oder hast schlicht genug vom 403-Debugging), spring direkt zum Thunderbit-Abschnitt.
Bevor du anfängst
- Schwierigkeitsgrad: Anfänger bis Fortgeschrittene (Python-Abschnitte); keiner (Thunderbit-Abschnitt)
- Benötigte Zeit: ca. 20–60 Minuten für das Python-Setup und den ersten Scrape; ca. 2 Minuten mit Thunderbit
- Was du brauchst: Python 3.9+, einen Code-Editor, den Chrome-Browser und (für den No-Code-Weg) die Thunderbit Chrome Extension
Dein Python-Umfeld für das Indeed-Scraping einrichten
Bevor auch nur eine Zeile Scraping-Code entsteht, richte zuerst deine Umgebung ein.
Die benötigten Bibliotheken installieren
Leg eine virtuelle Umgebung an und installiere die nötigen Pakete:
python -m venv indeed_env
source indeed_env/bin/activate # Unter Windows: indeed_env\Scripts\activate
# Für den HTTP- + Parsing-Ansatz
pip install requests beautifulsoup4 lxml httpx
# Für den versteckten JSON-Ansatz (empfohlen)
pip install curl_cffi parsel tenacity
# Für den Browser-Automatisierungs-Ansatz
pip install selenium
Ein paar Anmerkungen:
curl_cffiist 2026 der Standard, um Cloudflare-geschützte Seiten zu scrapen. Es imitiert echte Browser-TLS-Fingerprints, etwas, das einfachemrequestsundhttpxverwehrt bleibt. Mehr dazu im Anti-Bot-Abschnitt.- Selenium 4.6+ bringt den Selenium Manager mit, sodass das manuelle Herunterladen von ChromeDriver entfällt; die Browser-Binärdatei wird automatisch verwaltet.
- Setze
lxmlals Parser-Backend für BeautifulSoup ein. Es ist ungefähr 1,5-mal schneller als der Standard-html.parser.
Deine Projektstruktur anlegen
Halte es schlank:
indeed_scraper/
├── scraper.py
├── requirements.txt
└── output/
Alle Codebeispiele unten bauen auf scraper.py auf.
So scrapst du Indeed mit Python mit BeautifulSoup
Das ist der einsteigerfreundliche Ansatz: requests lädt die Seite, BeautifulSoup parst das HTML. Er ist am schnellsten eingerichtet, auf Indeed aber zugleich am fragilsten.
Schritt 1: Die Indeed-Such-URL zusammensetzen
Die Such-URLs von Indeed folgen einem vorhersehbaren Muster:
https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
Beispiel: Suche nach „data analyst“ in „Austin, TX“ ab der ersten Seite:
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 paginiert in 10er-Schritten und hat eine harte Obergrenze von 1.000 Ergebnissen (start <= 990). Jeder Offset über 990 liefert klammheimlich dieselbe Seite zurück.
Schritt 2: Einen HTTP-Request mit passenden Headern senden
Requests mit den Standard-User-Agent-Strings von Python blockiert Indeed augenblicklich. Du brauchst realistische Header:
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)
Bekommst du 200, bist du vorerst durch. Bekommst du 403, hat Cloudflare dich erkannt. (Mehr dazu weiter unten.)
Schritt 3: Jobanzeigen aus dem HTML parsen
Wähle die Jobkarten mit BeautifulSoup aus. Nimm dafür data-testid-Attribute, sie sind stabiler als die zufälligen CSS-Klassennamen von Indeed:
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"{len(jobs)} Jobs gefunden")
Schritt 4: Paginierung behandeln
Geh durch die Seiten, indem du den Parameter start hochzählst:
import time, random
all_jobs = []
for page in range(0, 50, 10): # Erste 5 Seiten
params["start"] = page
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
response = requests.get(url, headers=headers, timeout=30)
# ... wie oben parsen ...
all_jobs.extend(jobs)
time.sleep(random.uniform(3, 6))
Grenzen dieses Ansatzes
Klare Ansage: BS4 + Requests ist 2026 die schwächste Methode für Indeed. Reines requests greift auf die TLS-Bibliothek der Python-Standardbibliothek zurück, die einen JA3-Fingerprint erzeugt, den Cloudflare sofort als „kein Browser“ entlarvt. Außerdem beherrscht es kein HTTP/2, das Indeed ausliefert. Nach ein paar Seiten bist du wahrscheinlich blockiert. Und die CSS-Selektoren? Indeed tauscht Klassennamen wie css-1m4cuuf und jobsearch-JobComponent-embeddedBody-1n0gh5s sehr häufig aus. Jeder darauf gestützte Selektor ist also eine tickende Zeitbombe.
Nutze diese Methode für schnelle Prototypen auf einer einzelnen Seite. Für alles Größere greifst du besser zum versteckten JSON-Ansatz.
So scrapst du Indeed mit Python mithilfe versteckter JSON-Daten
Diese Methode empfehle ich den meisten Python-Entwicklern. Statt fragiler HTML-Elemente parst du strukturierte Daten aus einer JavaScript-Variable im Seitenquelltext von Indeed: window.mosaic.providerData["mosaic-provider-jobcards"].
Jedes Feld, das dich interessiert (Stellenbezeichnung, Unternehmen, Ort, Gehalt, Job-Key, Veröffentlichungsdatum, Remote-Flag), steckt bereits in diesem JSON-Block. Kein JavaScript-Rendering nötig. Das Schema ist mindestens seit 2023 stabil und damit deutlich robuster als DOM-Selektoren.
Schritt 1: Den Seiten-HTML abrufen
Nimm curl_cffi statt requests, denn es imitiert echte Browser-TLS-Fingerprints, was gegen Cloudflare entscheidend ist:
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))
Warum curl_cffi? Es ist ein Python-Binding für curl-impersonate und reproduziert exakt den TLS ClientHello, den HTTP/2-SETTINGS-Frame und die Header-Reihenfolge echter Browser. Es ist der einzige aktiv gepflegte Python-HTTP-Client, der sowohl JA3/JA4 als auch den Akamai-H2-Fingerprint in einem einzigen Aufruf umgeht. Zu den unterstützten Impersonations-Zielen zählen unter anderem chrome120, chrome124, chrome131 sowie Safari- und Edge-Varianten.
Schritt 2: Das JSON mit einem regulären Ausdruck extrahieren
Der JSON-Block sitzt in einem <script>-Tag. Hol ihn mit einem Regex heraus:
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"{len(results)} Jobs im versteckten JSON gefunden")
else:
print("Verstecktes JSON nicht gefunden — möglicher Block oder Seitenänderung")
Schritt 3: Job-Felder aus dem JSON parsen
Jeder Eintrag in results enthält mehr Daten, als auf der Seite zu sehen ist:
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']}",
})
Das JSON führt häufig auch Gehaltsschätzungen, Taxonomie-Attribute (Skill-Tags) und Unternehmensbewertungen, die im gerenderten HTML nicht immer auftauchen.
Schritt 4: Mehrere Seiten scrapen
Über tierSummaries im JSON ermittelst du die Gesamtzahl der Ergebnisse und iterierst anschließend:
import time, random
all_jobs = []
for start in range(0, 50, 10): # Erste 5 Seiten
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"Insgesamt: {len(all_jobs)} Jobs gescrapt")
Warum verstecktes JSON robuster ist
Die Struktur window.mosaic.providerData ändert sich deutlich seltener als CSS-Klassennamen. Du erhältst saubere, strukturierte Daten, ohne dich durch unübersichtliches HTML zu arbeiten. Anti-Bot-Maßnahmen (Header, Pausen, Proxys) brauchst du trotzdem weiterhin; darauf gehen wir als Nächstes ein.
So scrapst du Indeed mit Python mit Selenium
Selenium ist der Weg über Browser-Automatisierung. Er hilft, wenn du mit der Seite interagieren musst, etwa um in Job-Detail-Panels zu klicken, Login-geschützte Inhalte zu verarbeiten oder dynamisch nachgeladene Beschreibungen zu erfassen, die im ursprünglichen HTML fehlen.
Wann du Selenium statt HTTP-Clients verwenden solltest
- Indeed lädt einige Inhalte dynamisch nach (vollständige Stellenbeschreibungen im rechten Seitenpanel)
- Du musst Seiten scrapen, die Session-State oder Login erfordern
- Du scrapest in kleinem Umfang, bei dem Geschwindigkeit nicht entscheidend ist
Schneller Rundgang
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 ist leichter erkennbar — mit Vorsicht verwenden
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()
Grenzen
Selenium ist langsam, denn jede Seite muss im Browser vollständig gerendert werden. Headless Chrome ist für das Anti-Bot-System von Indeed erkennbar (Cloudflare prüft navigator.webdriver, WebGL-Vendor-Strings, die Anzahl der Plugins und mehr). Selbst undetected-chromedriver verzögert die Erkennung nur, verhindert sie aber nicht dauerhaft. Und wie bei BS4 brechen deine Selektoren, sobald Indeed seine UI aktualisiert.
Für die meisten Anwendungsfälle liefert der versteckte JSON-Ansatz dieselben Daten schneller und mit weniger Wartung. Heb dir Selenium für die Randfälle auf, in denen du wirklich einen Browser brauchst.
Wie du 403-Fehler beim Scrapen von Indeed mit Python vermeidest
Dieser Abschnitt ist der wichtigste. Wer nach einer frustrierten Google-Suche hier gelandet ist, ist genau richtig.

Warum Indeed deinen Scraper blockiert
Indeed setzt Cloudflare Bot Management plus Cloudflare Turnstile ein – nicht DataDome, nicht PerimeterX. Die Response-Header bestätigen es: server: cloudflare, cf-ray und das Bot-Management-Cookie __cf_bm. Cloudflare prüft deinen TLS-Fingerprint (JA3/JA4), die Reihenfolge der HTTP/2-Header, Request-Muster und Browsersignale. Wirkt eines davon unnatürlich, fängst du dir einen 403, 429, 503 ein – oder, im heimtückischsten Fall, ein 200 OK mit einer Turnstile-Challenge-Seite statt echter Jobdaten.
User-Agent und Request-Header rotieren
Ein einzelner, statischer User-Agent ist der schnellste Weg in die Sperre. Rotiere aus einem Pool aktueller, realistischer Strings. Wichtig: Seit der User-Agent-Reduktion sind die Minor-Versionen von Chrome auf 0.0.0 eingefroren. Erfinde also keine nicht-null Minor-Versionen, sonst schlagen die Anti-Bots an.
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",
}
Achte außerdem darauf, dass deine sec-ch-ua Client Hints zur UA-Version passen. Ein sec-ch-ua: "Chrome";v="131" neben einem User-Agent, der Chrome 145 behauptet, ist ein sofortiges Warnsignal.
Zufällige Pausen zwischen den Requests einbauen
Feste Intervalle fallen der Mustererkennung auf. Nutze stattdessen zufälligen Jitter:
import time, random
# Zwischen jedem Request
time.sleep(random.uniform(3, 6))
# Beim erneuten Versuch nach einer Blockade
def backoff_sleep(attempt):
base = 4
sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
time.sleep(min(sleep_time, 60))
Der Feldkonsens aus ScrapeOps und WebScraping.AI liegt bei 3–6 Sekunden zwischen Requests pro IP, mit einer harten Obergrenze von etwa 100 Requests pro IP und Session, bevor rotiert wird.
Proxy-Rotation einsetzen
Das ist der größte einzelne Erfolgsfaktor. Datacenter-Proxys aus AWS-/GCP-Bereichen erreichen bei Cloudflare-Enterprise-Zielen nur etwa 5–15 % Erfolgsrate, auf Indeed also praktisch unbrauchbar. Residential Proxys plus korrekte TLS-Fingerprints heben das auf 80–95 %.
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,
)
Die Preise für Residential Proxys liegen 2026 je nach Anbieter und Bindung ungefähr bei $4–8,50 pro GB. Für Indeed empfiehlt sich der Start mit einem kleinen Pool und das Skalieren bei Bedarf.
403-, 429- und 503-Statuscodes sauber behandeln
Versuche es nicht blind erneut. Verschiedene Statuscodes bedeuten Verschiedenes:
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,
)
# Prüfe den heimtückischen Fall „200 mit Challenge“
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 — blockiert. Proxy wird rotiert, Versuch {attempt + 1}")
elif r.status_code == 429:
print(f"429 — Rate-Limit erreicht. Verlangsamen.")
elif r.status_code == 503:
print(f"503 — Server überlastet oder JS-Challenge.")
backoff_sleep(attempt)
except Exception as e:
print(f"Request-Fehler: {e}")
backoff_sleep(attempt)
raise RuntimeError(f"Nach {max_retries} Versuchen fehlgeschlagen: {url}")
Der Fall „200 mit Challenge“ ist der kniffligste. Prüfe den Response-Body immer auf cf-turnstile- oder Just a moment-Marker, bevor du einen 200er als Erfolg verbuchst.
Die einfachere Alternative: Lass Thunderbit den Anti-Bot-Schutz übernehmen
Wer keine Proxy-Pools, Header-Rotation und TLS-Fingerprint-Imitation bauen und pflegen will, überlässt das Cloud-Scraping von Thunderbit die Arbeit: Es kümmert sich automatisch um CAPTCHAs, Proxy-Rotation und Anti-Bot-Schutz. Kein Proxy-Setup, keine curl_cffi-Konfiguration, keine CAPTCHA-Solving-Bibliotheken. Der Weg des geringsten Widerstands, wenn du schlicht die Daten brauchst.
Warum dein Indeed-Scraper ständig kaputtgeht (und wie du das behebst)
Die 403-Mauer ist das akute Problem. Das chronische Problem ist die Wartung: Scraper, die heute laufen, sind nächste Woche zerschossen und liefern klammheimlich leere Daten oder veraltete Ergebnisse.
Wie Indeed deine Selektoren kaputtmacht
Indeed rotiert CSS-Klassennamen aggressiv. Der Leitfaden von Bright Data warnt ausdrücklich davor, dass Klassen wie css-1m4cuuf und css-1rqpxry „scheinbar zufällig generiert werden – vermutlich zur Build-Zeit“. A/B-Tests sorgen dafür, dass verschiedene Sessions verschiedene Seitenlayouts zu sehen bekommen. Und DOM-Umstrukturierungen passieren ohne Vorwarnung.
Die JobFunnel-Geschichte ist lehrreich. Ein Mitwirkender berichtete: „CaptchaBuster hat das CAPTCHA erfolgreich entschärft, und der Grund, warum die Seite weiterhin nicht erfolgreich gescrapt wurde, [liegt] in veralteten Beautiful-Soup-Selektoren.“ Der Scraper war nicht blockiert, er griff nur auf die falschen Elemente zu.
Strategie: Verstecktes JSON statt DOM-Parsing bevorzugen
Der window.mosaic.providerData-Block ist seit mindestens 2023 schema-stabil. Der Pfad metaData.mosaicProviderJobCardsModel.results[] ist auch 2026 noch der kanonische Weg. DOM-Selektoren brechen monatlich. JSON-Extraktion bricht höchstens jährlich, wenn überhaupt.
Strategie: Datenattribute statt Klassennamen verwenden
Wenn du doch ins DOM musst, wähle funktionale Attribute:
| Selektor | Zweck |
|---|---|
[data-testid="slider_item"] | Container jeder Jobkarte |
[data-testid="job-title"] oder h2.jobTitle > a | Link zur Stellenbezeichnung |
[data-testid="company-name"] | Name des Arbeitgebers |
[data-testid="text-location"] | Ortsangabe |
data-jk="<jobkey>" auf jeder Karte | Der stabilste Hook überhaupt — seit 2019 unverändert |
Assertion-Checks hinzufügen, um veraltete Selektoren zu erkennen
Lass deinen Scraper niemals klammheimlich mit null Ergebnissen durchlaufen. Häng nach jedem Abruf eine Prüfung an:
results = parse_hidden_json(html)
assert len(results) > 0, (
f"Indeed hat bei start={start} einen leeren Ergebnisdatensatz zurückgegeben — "
"möglicher Block, CAPTCHA oder Drift der Selektoren. "
f"Die ersten 500 Zeichen der Antwort: {html[:500]}"
)
Protokolliere im Fehlerfall die ersten 500 bis 2000 Zeichen der Rohantwort. So erkennst du auf einen Blick, ob du eine Turnstile-Challenge, eine Login-Sperre oder eine Schemaänderung erhalten hast. Lass außerdem täglich einen CI-Smoke-Test gegen eine feste Suche laufen (z. B. q=python&l=remote), der auf nicht-null Ergebnisse prüft.
Die KI-Alternative: Scraper, die nie brechen
Die KI von Thunderbit liest die Seitenstruktur jedes Mal neu und hängt nicht an fest codierten Selektoren oder Regex-Mustern. Ändert Indeed sein HTML, passt sich Thunderbit automatisch an. Das löst genau den Wartungsaufwand, den Forennutzer immer wieder als ihr größtes Ärgernis nennen. Wer schon einmal mit der Slack-Nachricht „Der Scraper liefert schon wieder leere Zeilen“ in den Tag gestartet ist, weiß zu schätzen, es nicht selbst reparieren zu müssen.
Indeed scrapen, ohne Python zu schreiben: Die No-Code-Alternative
Jeder konkurrierende Leitfaden setzt voraus, dass du Python-Code schreiben wirst. Die Forendaten erzählen eine andere Geschichte. Nutzer sagen Dinge wie „Es ist einfach so schwierig mit ständigen Bugs und Fehlern“, und manche raten sogar dazu, jemanden auf Fiverr zu beauftragen, nur um an die Daten zu kommen. Kommt dir das bekannt vor, ist dieser Abschnitt dein Ausweg.
So scrapst du Indeed mit Thunderbit (Schritt für Schritt)
Schritt 1: Installiere die Thunderbit Chrome Extension aus dem Chrome Web Store. Der Einstieg ist kostenlos.
Schritt 2: Öffne in deinem Browser eine Indeed-Suchergebnisseite – zum Beispiel https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.
Schritt 3: Klicke auf das Thunderbit-Symbol in deiner Browser-Toolbar und dann auf „KI-Felder vorschlagen“. Thunderbits KI scannt die Seite und erkennt automatisch Spalten wie Stellenbezeichnung, Unternehmen, Ort, Gehalt, Job-URL und Veröffentlichungsdatum. Du kannst die vorgeschlagenen Felder prüfen und anpassen, also überflüssige Spalten entfernen oder eigene hinzufügen, indem du in einfachem Deutsch beschreibst, was du willst.
Schritt 4: Klicke auf „Scrape“. Thunderbit extrahiert die Daten von der Seite und zeigt sie in einer strukturierten Tabelle. Du solltest Zeilen mit Jobanzeigen und den von dir konfigurierten Feldern sehen.
Mit Subpage-Scraping anreichern
Nach dem Scrapen der Übersichtsseite klickst du auf „Subpages scrapen“, damit Thunderbit jede einzelne Stellen-Detailseite aufruft. Es holt die vollständige Stellenbeschreibung, Qualifikationen, Benefits und Bewerbungslinks, ganz ohne zusätzliche Einrichtung. Funktional entspricht das einem zweiten Python-Scraper, der jede /viewjob?jk=<jobkey>-URL besucht, nur eben mit einem einzigen Klick.
Paginierung automatisch behandeln
Thunderbit verarbeitet die klickbasierte Paginierung von Indeed automatisch. Du baust keine Offset-URLs von Hand zusammen und schreibst keine Paginierungsschleifen. Es klickt sich durch die Seiten und fasst die Ergebnisse zusammen.
In deine bevorzugten Tools exportieren
Exportiere die gescrapten Daten in CSV, Excel, Google Sheets, Airtable oder Notion, komplett kostenlos. Du musst keinen csv.writer()- oder pandas.to_csv()-Code schreiben.
Wann du Python und wann Thunderbit verwenden solltest
| Szenario | Bestes Tool |
|---|---|
| Eigene Datenpipelines, geplante Automatisierung per Cron/Airflow | Python |
| Integration in eine größere Codebasis | Python |
| Hochgradig angepasste Parsing-Logik | Python |
| Einmalige Recherche oder Marktanalyse | Thunderbit |
| Nicht-technische Teammitglieder brauchen Daten | Thunderbit |
| Daten jetzt sofort, ohne 403-Debugging | Thunderbit |
| Anreicherung von Unterseiten ohne Setup | Thunderbit |
Zeitvergleich: Python-Setup plus Anti-Bot-Debugging = Stunden bis Tage (besonders beim ersten Mal). Thunderbit = unter 2 Minuten für dieselben Daten. Damit will ich nicht sagen, dass Python falsch ist; es hängt schlicht davon ab, was du brauchst.
Ist das Scrapen von Indeed legal? Das solltest du wissen
Keine der gut platzierten Indeed-Scraping-Anleitungen geht auf die Legalität ein, was verwundert, denn die Frage „Ist das Scrapen von Indeed legal?“ taucht in Foren ständig auf. Das hier ist keine Rechtsberatung, aber so sieht die Lage aus.
Die Nutzungsbedingungen von Indeed
In den AGB von Indeed (indeed.com/legal) gibt es keine pauschale „kein Scraping“-Klausel. Das einzige ausdrückliche Automatisierungsverbot ist Abschnitt A.3.5, der die „Verwendung von Automatisierung, Skripten oder Bots zur Automatisierung des Indeed-Apply-Prozesses“ untersagt. Das ist eng auf den Bewerbungsprozess begrenzt, nicht auf das passive Lesen öffentlicher Stellenanzeigen. Indeeds wichtigstes Durchsetzungsinstrument ist technisch (Cloudflare-Challenges, IP-Sperren, Device-Fingerprinting), nicht der Gerichtssaal.
Relevante Rechtsprechung
Der meistzitierte US-Fall ist hiQ Labs v. LinkedIn. Der 9th Circuit entschied im April 2022, dass das Scrapen öffentlich zugänglicher Daten „wahrscheinlich nicht gegen den CFAA“ (Computer Fraud and Abuse Act) verstößt. Später wurde hiQ allerdings wegen Vertragsverletzung haftbar gemacht, weil Mitarbeiter gefälschte LinkedIn-Profile angelegt und den AGB zugestimmt hatten.
Noch jüngst führte Meta v. Bright Data (N.D. Cal., Jan. 2024) zu einer noch klareren Entscheidung. Richter Chen stellte fest, dass die Nutzungsbedingungen von Facebook und Instagram das ausgeloggte Scraping öffentlicher Daten nicht verbieten. Meta zog die übrigen Ansprüche im Folgemonat freiwillig zurück.
Indeeds robots.txt
Indeeds robots.txt untersagt dem Standard-User-agent: * den Zugriff auf /jobs/ und /job/, erlaubt Googlebot und Bingbot aber ausdrücklich den Zugriff auf /viewjob?, also die einzelnen Job-Detailseiten. KI-Trainings-Crawler (GPTBot, CCBot, anthropic-ai) sind stark eingeschränkt. robots.txt ist in den USA rechtlich nicht bindend, doch ihre Beachtung gilt als Best Practice und Zeichen guten Willens.
Praktische Leitlinien für verantwortungsvolles Scraping
- Scrape nur öffentlich zugängliche Daten, niemals einloggen, niemals Fake-Konten anlegen
- Halte Rate Limits ein: 1 Request alle 3–6 Sekunden pro IP, nur geringe Parallelität
- Veröffentliche gescrapte Daten nicht als dein eigenes Jobportal neu
- Nutze die Daten für persönliche oder interne Recherche, nicht für kommerziellen Weiterverkauf ohne Erlaubnis
- Verwirf oder hashe personenbezogene Daten, die du nicht brauchst; setze eine Aufbewahrungsgrenze für personenbezogene Randdaten
- Arbeitest du in großem Umfang oder in der EU/im Vereinigten Königreich, konsultiere einen Anwalt: Die Transparenzpflichten aus Artikel 14 der DSGVO gelten für gescrapte personenbezogene Daten
Das Risikospektrum: Automatisierte Jobsuche für den persönlichen Gebrauch liegt am unteren Ende. Der groß angelegte kommerzielle Weiterverkauf von Indeed-Daten liegt am oberen Ende.
Fazit und wichtigste Erkenntnisse
Indeed mit Python zu scrapen ist machbar, aber kein Wochenendprojekt, das man einfach laufen lässt und dann vergisst. Der Cloudflare-Schutz von Indeed, die wechselnden Selektoren und die aggressiven Anti-Bot-Maßnahmen verlangen, dass du mit den richtigen Werkzeugen und realistischen Erwartungen herangehst.
Das würde ich aus all dem mitnehmen:
- Indeed ist die reichhaltigste Quelle für Arbeitsmarktdaten im Web – 350 Mio. monatliche Besucher, 130 Mio. Anzeigen –, wehrt sich aber sehr stark gegen Scraper.
- Die Extraktion des versteckten JSONs (
window.mosaic.providerData) ist der robusteste Python-Ansatz. Das Schema ist seit Jahren stabil, während CSS-Selektoren monatlich brechen. curl_cffimit Browser-Imitation ist 2026 der Standard-HTTP-Client für Cloudflare-geschützte Seiten. Reinesrequestsundhttpxscheitern schon an ihrem TLS-Fingerprint.- Verwende immer rotierende Header, zufällige Pausen und Residential Proxys, um 403-Fehler zu vermeiden. Datacenter-Proxys sind gegen Cloudflare Enterprise nahezu nutzlos.
- Füge Assertion-Checks hinzu, damit du sofort merkst, wenn Selektoren brechen oder dir statt Jobdaten eine Challenge-Seite ausgeliefert wird.
- Für nicht-technische Nutzer oder alle, die einfach schnell Ergebnisse wollen, bietet Thunderbit einen No-Code-, KI-gestützten Weg, der sich automatisch an Seitenänderungen anpasst: keine Proxys, kein Debugging, keine Wartung.
Wenn du den No-Code-Weg ausprobieren willst: Thunderbit hat einen kostenlosen Tarif, sodass du es ganz unverbindlich auf Indeed testen kannst. Und wenn du den Python-Weg gehst, sind die obigen Codebeispiele ein solider Startpunkt; behandle die Anti-Bot-Resilienz dabei aber bitte als erstklassige Anforderung und nicht als nachträglichen Gedanken.
Mehr zu Web-Scraping-Ansätzen und -Tools findest du in unseren Leitfäden zu Web-Scraping mit Python, den besten automatisierten Web-Scraping-Tools und Web-Scraping ohne Blockierung. Außerdem findest du Tutorials auf dem Thunderbit YouTube-Kanal.
Thunderbit ausprobieren, um Indeed-Daten schneller zu scrapen Get Started Free
FAQs
Welche Python-Bibliotheken eignen sich am besten zum Scrapen von Indeed?
Für HTTP-Requests ist curl_cffi 2026 die stärkste Wahl, denn es imitiert echte Browser-TLS-Fingerprints, was zum Umgehen von Cloudflare essenziell ist. httpx mit HTTP/2 ist eine brauchbare Alternative für weniger geschützte Ziele. Fürs HTML-Parsing bleibt BeautifulSoup4 mit lxml der Standard. Für Browser-Automatisierung funktionieren Playwright (mit playwright-stealth) oder undetected-chromedriver, auch wenn beide zunehmend erkennbar sind. Der Regex-Ansatz mit verstecktem JSON (window.mosaic.providerData) umgeht aufwendiges Parsing vollständig.
Warum bekomme ich beim Scrapen von Indeed ständig 403-Fehler?
Indeed nutzt Cloudflare Bot Management, das deinen TLS-Fingerprint (JA3/JA4), die Reihenfolge der HTTP/2-Header, Request-Muster und das Browserverhalten prüft. Verwendest du einfach requests, entlarvt dich der TLS-Fingerprint sofort als Python-Skript; der 403 kommt, bevor deine Header überhaupt gelesen werden. Beheben lässt sich das mit curl_cffi und Browser-Imitation, rotierenden realistischen User-Agent-Strings, zufälligen Pausen (3–6 Sekunden) und Residential Proxys. Prüfe außerdem den Fall „200 mit Turnstile-Challenge“: Suche in den Response-Bodies nach cf-turnstile-Markern.
Kann ich Indeed ohne Programmierung scrapen?
Ja. Tools wie Thunderbit erlauben es dir, Indeed-Jobanzeigen mit wenigen Klicks zu extrahieren – Chrome-Erweiterung installieren, eine Indeed-Suchseite öffnen, auf „KI-Felder vorschlagen“ klicken und dann auf „Scrape“. Thunderbits KI erkennt Felder wie Stellenbezeichnung, Unternehmen, Ort und Gehalt automatisch. Es übernimmt Paginierung, die Anreicherung von Unterseiten (vollständige Stellenbeschreibungen) und Anti-Bot-Schutz automatisch. Exportiere kostenlos nach CSV, Google Sheets, Airtable oder Notion.
Wie oft ändert Indeed seine HTML-Struktur?
Indeed rotiert regelmäßig CSS-Klassennamen (z. B. css-1m4cuuf, zufällig gehashte Strings) und baut DOM-Elemente ohne Ankündigung um. A/B-Tests bedeuten, dass verschiedene Nutzer gleichzeitig verschiedene Layouts sehen können. Der Ansatz mit verstecktem JSON (window.mosaic.providerData) ist deutlich stabiler; das Schema ist seit mindestens 2023 konsistent. Musst du DOM-Selektoren nutzen, ziele lieber auf data-testid-Attribute und data-jk (Job-Key) als auf CSS-Klassen.
Ist es legal, Indeed zu scrapen?
Das ausgeloggte Scraping öffentlich zugänglicher Indeed-URLs dürfte in den USA nach dem 9th-Circuit-Urteil hiQ v. LinkedIn (2022) und der Entscheidung Meta v. Bright Data (2024) wahrscheinlich keine CFAA-Haftung auslösen. In Indeeds AGB ist speziell die Automatisierung des Bewerbungsprozesses verboten, nicht das passive Lesen öffentlicher Anzeigen. Trotzdem gilt: immer verantwortungsvoll scrapen, also nicht einloggen, keine Fake-Konten anlegen, Rate Limits beachten, Daten nicht als eigenes Jobportal neu veröffentlichen und personenbezogene Daten (z. B. Namen von Recruitern, E-Mails) unter DSGVO/CCPA sorgfältig behandeln. Bei kommerziellen Großprojekten solltest du einen Anwalt konsultieren.
Mehr erfahren
- Wie man Web-Scraping ohne Blockierung in Python durchführt
- Python Web Scraping: Blocks mit smarter Proxy-Nutzung vermeiden
- Wie man mit Python einen Web-Scraper schreibt: Von Anfang bis Ende
- Wie man Daten effizient mit Python von einer Website scrapt
- Wie man Daten mit Python scrapt: Ein Einsteiger-Tutorial


