So scrapt man Indeed mit Python (und vermeidet 403-Fehler) im Jahr 2026

Zuletzt aktualisiert am July 13, 2026
So scrapt man Indeed mit Python (und vermeidet 403-Fehler) im Jahr 2026

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.

indeed_stats_dca2a43cec.png

AnwendungsfallWer profitiertBeispiel
LeadgenerierungSales- und Recruiting-TeamsListen von einstellenden Unternehmen mit Kontaktdaten erstellen
ArbeitsmarktforschungAnalysten, HR-TeamsTrendfähigkeiten und Gehaltsbenchmarks nach Region identifizieren
WettbewerbsanalyseArbeitgeber, PersonaldienstleisterEinstellungsmuster und Gehaltsangebote von Wettbewerbern überwachen
Automatisierte Jobsuche für PrivatpersonenJobsuchendeAnzeigen aus verschiedenen Regionen nach den eigenen Kriterien bündeln
Trainingsdaten für ML-ModelleData ScientistsGehaltsprognosemodelle 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:

KriteriumBS4 + RequestsSeleniumVerstecktes JSON (window.mosaic)No-Code (Thunderbit)
SchwierigkeitAnfängerFortgeschritteneFortgeschritten bis sehr fortgeschrittenKeine (2 Klicks)
GeschwindigkeitSchnellLangsam (Browser-Rendering)SchnellSchnell (Cloud-Scraping)
JS-gerenderte InhalteNeinJaJa (eingebettete Daten)Ja
Widerstand gegen Bot-SchutzNiedrigMittel (erkennbar)Mittel bis hochHoch (automatisch gehandhabt)
Wartung bei HTML-ÄnderungenHoch (Selektoren brechen)HochMittel (JSON-Struktur stabiler)Keine (KI passt sich an)
Am besten geeignet fürSchnelle PrototypenDynamische Seiten, Login-geschützte InhalteStrukturierte MassendatenNicht-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_cffi ist 2026 der Standard, um Cloudflare-geschützte Seiten zu scrapen. Es imitiert echte Browser-TLS-Fingerprints, etwas, das einfachem requests und httpx verwehrt 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 lxml als 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.

indeed_antibot_374d080ff4.png

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:

SelektorZweck
[data-testid="slider_item"]Container jeder Jobkarte
[data-testid="job-title"] oder h2.jobTitle > aLink zur Stellenbezeichnung
[data-testid="company-name"]Name des Arbeitgebers
[data-testid="text-location"]Ortsangabe
data-jk="<jobkey>" auf jeder KarteDer 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

SzenarioBestes Tool
Eigene Datenpipelines, geplante Automatisierung per Cron/AirflowPython
Integration in eine größere CodebasisPython
Hochgradig angepasste Parsing-LogikPython
Einmalige Recherche oder MarktanalyseThunderbit
Nicht-technische Teammitglieder brauchen DatenThunderbit
Daten jetzt sofort, ohne 403-DebuggingThunderbit
Anreicherung von Unterseiten ohne SetupThunderbit

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_cffi mit Browser-Imitation ist 2026 der Standard-HTTP-Client für Cloudflare-geschützte Seiten. Reines requests und httpx scheitern 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

Fawad Khan
Fawad Khan
Fawad verdient seinen Lebensunterhalt mit Schreiben und liebt es ehrlich gesagt ziemlich. Seit Jahren beschäftigt er sich damit, was gute Texte einprägsam macht – und was dazu führt, dass Leser einfach weiterscrollen. Frag ihn nach Marketing, und er redet stundenlang. Frag ihn nach Carbonara, und er redet noch länger.
Inhaltsverzeichnis

Eine Webseite einfach per Anfrage scrapen

Sag einfach in normalem Deutsch, was du brauchst. Oder noch besser: sag gar nichts.

Thunderbit ausprobieren kostenlos
Daten mit KI extrahieren
Daten einfach zu Google Sheets, Airtable oder Notion übertragen
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week