Comment extraire des données Indeed avec Python (et éviter les erreurs 403) en 2026

Dernière mise à jour le July 8, 2026
Comment extraire des données Indeed avec Python (et éviter les erreurs 403) en 2026

Au cinquantième copier-coller d’un intitulé de poste depuis Indeed vers un tableur, on commence sérieusement à s’interroger sur ses choix de carrière. Quiconque a déjà tenté d’extraire par programme des données structurées depuis Indeed connaît la suite par cœur : l’erreur 403 n’est pas un dysfonctionnement, c’est une pièce maîtresse du système de défense d’Indeed.

Avec environ 350 millions de visiteurs uniques par mois, 130 millions d’offres d’emploi en permanence et une présence dans plus de 60 pays, Indeed est le plus grand site d’emploi de la planète. C’est donc l’une des mines de données les plus riches sur le marché du travail — et l’une des plus coriaces à extraire. Le scraper open source JobFunnel (des milliers d’étoiles sur GitHub) en a fait les frais : son mainteneur l’a carrément archivé en décembre 2025, épuisé par des années de course à l’armement contre les anti-bots. Ses propres mots : « Tous les utilisateurs peuvent extraire quelques offres, mais se heurtent rapidement à un captcha, puis l’extraction échoue, ne renvoyant aucune offre. » Un autre contributeur a rapporté un CAPTCHA dès la toute première requête. Autant dire que la cible n’a rien d’anodin. Dans ce guide, je passe en revue toutes les méthodes concrètes pour extraire Indeed avec Python, je vous montre comment survivre au véritable parcours du combattant des 403, et — pour qui préfère se passer totalement de débogage — je présente une alternative sans code avec Thunderbit.

Que signifie extraire Indeed avec Python ?

Le web scraping, au fond, c’est l’extraction automatisée de données structurées depuis des pages web. Appliqué à Indeed avec Python, cela revient à écrire un script qui parcourt les pages de résultats et les fiches d’offres, lit le HTML sous-jacent (ou les données embarquées) et en tire des champs comme l’intitulé du poste, l’entreprise, le lieu, le salaire et la description, dans un format exploitable — un CSV, une base de données, une feuille Google.

Côté outillage Python, on retrouve habituellement Requests (pour les appels HTTP), BeautifulSoup (pour l’analyse HTML) et Selenium ou Playwright (pour l’automatisation du navigateur). Sauf qu’Indeed n’est pas un simple site statique. C’est un hybride : du HTML rendu côté serveur, doublé d’un bloc d’état JSON embarqué, le tout sous la protection de Cloudflare Bot Management. Conséquence : votre scraper doit composer avec du contenu rendu par JavaScript, des noms de classes CSS mouvants et des protections anti-bot agressives — et ce, avant même d’avoir analysé le moindre intitulé de poste.

Précision utile : en 2026, il n’existe aucune API Indeed officielle, gratuite et en lecture seule. L’ancienne Publisher Jobs API a été dépréciée autour de 2020, et ce qu’il en reste est réservé aux employeurs (Job Sync, Sponsored Jobs). Le scraping ou le recours à un fournisseur de données tiers restent donc les seules pistes réalistes.

Pourquoi extraire les données d’offres Indeed ?

Le raisonnement tient en une phrase : éplucher manuellement des milliers d’annonces n’est pas tenable, et les données qu’elles renferment ont une vraie valeur.

indeed_stats_dca2a43cec.png

Cas d’usageQui en bénéficieExemple
Génération de leadsÉquipes commerciales et recrutementConstituer des listes d’entreprises qui recrutent avec leurs coordonnées
Étude du marché de l’emploiAnalystes, équipes RHIdentifier les compétences émergentes, les repères salariaux par région
Veille concurrentielleEmployeurs, agences d’intérimSuivre les tendances de recrutement et les offres de rémunération des concurrents
Automatisation de la recherche d’emploi personnelleCandidatsRegrouper les offres correspondant à vos critères, tous lieux confondus
Données d’entraînement pour modèles MLData scientistsConstruire des modèles de prédiction salariale à partir de données historiques

Les recherches du Hiring Lab d’Indeed le confirment : les données d’offres collent de près au BLS JOLTS et peuvent faire office de proxy quasi temps réel des conditions du marché du travail américain. Les hedge funds, eux, exploitent la cadence de publication des offres comme signal de données alternatives. Les équipes RH se servent des fourchettes salariales extraites pour étalonner les rémunérations. Et les recruteurs constituent leurs viviers de prospects à partir des entreprises en phase active de recrutement.

Un point pratique mérite attention : les données salariales d’Indeed progressent, mais restent lacunaires. À la mi-2025, environ 59 % des offres aux États-Unis affichent une information salariale, mais seulement 22 % environ indiquent un montant exact — le reste se présente sous forme de fourchettes. Toute analyse salariale fondée sur les données Indeed doit composer avec cette rareté.

Choisir votre méthode pour extraire Indeed avec Python

Il n’existe pas de méthode unique pour extraire Indeed. Le bon choix dépend de votre niveau, du volume de données visé et de la maintenance que vous êtes prêt à assumer. J’ai éprouvé les quatre grandes approches ; voici le verdict comparé :

CritèreBS4 + RequestsSeleniumJSON caché (window.mosaic)Sans code (Thunderbit)
DifficultéDébutantIntermédiaireIntermédiaire-avancéAucune (2 clics)
VitesseRapideLente (rendu du navigateur)RapideRapide (scraping cloud)
Contenu rendu en JSNonOuiOui (données embarquées)Oui
Résistance aux anti-botsFaibleMoyenne (détectable)Moyenne à élevéeÉlevée (prise en charge automatique)
Maintenance lors des changements HTMLÉlevée (les sélecteurs cassent)ÉlevéeMoyenne (structure JSON plus stable)Aucune (l’IA s’adapte)
Idéal pourPrototypes rapidesPages dynamiques, contenu derrière connexionDonnées structurées en masseNon-développeurs, résultats rapides

Ce guide détaille chacune de ces méthodes. Développeur Python ? Les sections BS4, JSON caché et Selenium vous concernent. Vous ne codez pas (ou vous en avez simplement assez de déboguer des 403) ? Filez directement à la section Thunderbit.

Avant de commencer

  • Difficulté : Débutant à intermédiaire (sections Python) ; aucune (section Thunderbit)
  • Temps nécessaire : ~20 à 60 minutes pour la configuration Python et la première extraction ; ~2 minutes avec Thunderbit
  • Ce qu’il vous faut : Python 3.9+, un éditeur de code, le navigateur Chrome et (pour la voie sans code) l’extension Chrome Thunderbit

Configurer votre environnement Python pour le scraping d’Indeed

Avant la première ligne de code de scraping, mettez votre environnement en place.

Installer les bibliothèques requises

Créez un environnement virtuel et installez les paquets nécessaires :

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

# Pour l’approche HTTP + parsing
pip install requests beautifulsoup4 lxml httpx

# Pour l’approche JSON caché (recommandée)
pip install curl_cffi parsel tenacity

# Pour l’approche d’automatisation navigateur
pip install selenium

Quelques précisions :

  • curl_cffi s’impose comme le choix par défaut en 2026 pour les sites protégés par Cloudflare. Il imite les empreintes TLS réelles d’un navigateur, là où requests et httpx en sont incapables. J’explique l’enjeu dans la section anti-bot.
  • Selenium 4.6+ embarque Selenium Manager : plus besoin de télécharger ChromeDriver à la main — il gère automatiquement le binaire du navigateur.
  • Adoptez lxml comme moteur d’analyse pour BeautifulSoup. Il est environ 1,5 fois plus rapide que le html.parser de la bibliothèque standard.

Créer la structure de votre projet

Restez sobre :

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

Tous les exemples de code ci-dessous reposent sur scraper.py.

Comment extraire Indeed avec Python en utilisant BeautifulSoup

C’est le point d’entrée le plus accessible : requests récupère la page, puis BeautifulSoup en analyse le HTML. La méthode se met en place en un clin d’œil — mais c’est aussi la plus fragile face à Indeed.

Étape 1 : construire l’URL de recherche Indeed

Les URL de recherche Indeed suivent un schéma prévisible :

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

Par exemple, pour rechercher « data analyst » à « Austin, TX » depuis la première page :

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 pagine par tranches de 10, avec un plafond strict de 1 000 résultats (start <= 990). Au-delà de 990, l’offset renvoie en silence la même page.

Étape 2 : envoyer une requête HTTP avec les bons en-têtes

Indeed bloque d’emblée les requêtes portant les chaînes d’agent utilisateur Python par défaut. Des en-têtes réalistes sont indispensables :

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)

Un 200 ? Vous êtes dans la place — pour le moment. Un 403 ? Cloudflare vous a démasqué. (La méthode pour y survivre arrive plus bas.)

Étape 3 : analyser les offres à partir du HTML

BeautifulSoup sélectionne les éléments des cartes d’offres. Visez les attributs data-testid — bien plus stables que les noms de classes CSS randomisés d’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"Found {len(jobs)} jobs")

Étape 4 : gérer la pagination

Bouclez sur les pages en incrémentant le paramètre start :

import time, random

all_jobs = []
for page in range(0, 50, 10):  # 5 premières pages
    params["start"] = page
    url = f"https://www.indeed.com/jobs?{urlencode(params)}"
    response = requests.get(url, headers=headers, timeout=30)
    # ... analyser comme ci-dessus ...
    all_jobs.extend(jobs)
    time.sleep(random.uniform(3, 6))

Limites de cette approche

Disons-le franchement : en 2026, BS4 + Requests est la méthode la plus faible face à Indeed. requests s’appuie sur la bibliothèque TLS standard de Python, qui génère une empreinte JA3 aussitôt rangée par Cloudflare dans la catégorie « pas un navigateur ». Elle ne gère pas non plus HTTP/2, pourtant employé par Indeed. Vous serez vraisemblablement bloqué au bout de quelques pages. Quant aux sélecteurs CSS, Indeed fait tourner très fréquemment des noms de classes comme css-1m4cuuf et jobsearch-JobComponent-embeddedBody-1n0gh5s — tout sélecteur qui les vise est une bombe à retardement.

Réservez cette méthode au prototype express sur une seule page. Pour toute montée en échelle, passez à l’approche JSON caché.

Comment extraire Indeed avec Python en utilisant les données JSON cachées

C’est la méthode que je conseille à la majorité des développeurs Python. Plutôt que d’analyser des éléments HTML capricieux, vous puisez des données structurées dans une variable JavaScript intégrée au code source d’Indeed : window.mosaic.providerData["mosaic-provider-jobcards"].

Chaque champ qui compte — intitulé, entreprise, lieu, salaire, identifiant de l’offre, date de publication, indicateur de télétravail — figure déjà dans ce bloc JSON. Aucune exécution JavaScript n’est requise. Et comme le schéma est stable depuis au moins 2023, il s’avère nettement plus résilient que les sélecteurs DOM.

Étape 1 : récupérer le HTML de la page

Utilisez curl_cffi plutôt que requests — il imite les empreintes TLS réelles d’un navigateur, ce qui est déterminant pour franchir Cloudflare :

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))

Pourquoi curl_cffi ? C’est un binding Python de curl-impersonate, qui reproduit à l’identique le ClientHello TLS, le frame HTTP/2 SETTINGS et l’ordre des en-têtes des vrais navigateurs. C’est le seul client HTTP Python activement maintenu qui déjoue à la fois JA3/JA4 et l’empreinte H2 d’Akamai en un seul appel. Parmi les cibles d’imitation prises en charge : chrome120, chrome124, chrome131, sans oublier des variantes Safari et Edge.

Étape 2 : extraire le JSON avec une expression régulière

Le bloc JSON est niché dans une balise <script>. Récupérez-le via une 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"Found {len(results)} jobs in hidden JSON")
else:
    print("Hidden JSON not found — possible block or page change")

Étape 3 : analyser les champs d’offres depuis le JSON

Chaque élément de results renferme plus de données que ce qui apparaît à l’écran :

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']}",
    })

Le JSON contient souvent des estimations salariales, des attributs de taxonomie (tags de compétences) et des évaluations d’entreprise absents du HTML rendu.

Étape 4 : extraire plusieurs pages

Exploitez tierSummaries dans le JSON pour connaître le nombre total de résultats, puis bouclez :

import time, random

all_jobs = []
for start in range(0, 50, 10):  # 5 premières pages
    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"Total: {len(all_jobs)} jobs scraped")

Pourquoi le JSON caché est plus résilient

La structure window.mosaic.providerData bouge bien moins souvent que les noms de classes CSS. Vous obtenez des données propres et structurées sans avoir à démêler un HTML brouillon. Cela dit, l’anti-bot reste à gérer (en-têtes, délais, proxies) — on y vient tout de suite.

Comment extraire Indeed avec Python en utilisant Selenium

Selenium, c’est la voie de l’automatisation du navigateur. Elle prend tout son sens quand vous devez interagir avec la page — cliquer dans les panneaux de détail des offres, accéder à du contenu derrière connexion, ou récupérer des descriptions chargées dynamiquement, absentes du HTML initial.

Quand utiliser Selenium plutôt qu’un client HTTP

  • Indeed charge une partie du contenu dynamiquement (descriptions complètes dans le panneau de droite)
  • Vous devez extraire des pages qui exigent un état de session ou une connexion
  • Vous faites du scraping à petite échelle, où la vitesse importe peu

Visite guidée rapide

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")  # Le mode headless est plus détectable — à utiliser avec prudence
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()

Limites

Selenium est lent — chaque page impose un rendu complet du navigateur. En mode headless, Chrome est repérable par le système anti-bot d’Indeed (Cloudflare ausculte navigator.webdriver, les chaînes de l’éditeur WebGL, le nombre de plugins, etc.). Même undetected-chromedriver ne fait que repousser la détection, sans jamais l’écarter pour de bon. Et, comme avec BS4, vos sélecteurs lâcheront à la prochaine refonte de l’interface d’Indeed.

Dans la plupart des cas, l’approche JSON caché vous livre les mêmes données plus vite et avec moins de maintenance. Gardez Selenium pour les cas limites où un navigateur est réellement incontournable.

Comment éviter les erreurs 403 lors du scraping d’Indeed avec Python

C’est la section décisive. Si vous atterrissez ici après une recherche Google frustrante, vous frappez à la bonne porte.

indeed_antibot_374d080ff4.png

Pourquoi Indeed bloque votre scraper

Indeed mise sur Cloudflare Bot Management avec Cloudflare Turnstile — ni DataDome, ni PerimeterX. Les en-têtes de réponse ne mentent pas : server: cloudflare, cf-ray, et le cookie de gestion des bots __cf_bm. Cloudflare examine votre empreinte TLS (JA3/JA4), l’ordre des en-têtes HTTP/2, les schémas de requête et les signaux comportementaux du navigateur. Au moindre élément qui sonne non humain, vous récoltez un 403, un 429, un 503 ou — le plus retors — un 200 OK doublé d’une page de défi Turnstile à la place des vraies données.

Faire tourner l’User-Agent et les en-têtes de requête

Un User-Agent statique unique est le plus court chemin vers le blocage. Faites tourner une liste de chaînes récentes et crédibles. Attention : depuis la réduction de l’User-Agent, les champs de version mineure de Chrome sont figés à 0.0.0 — n’inventez pas de versions mineures non nulles, sous peine de vous faire repérer.

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",
}

Vérifiez aussi la cohérence entre vos Client Hints sec-ch-ua et la version annoncée par l’User-Agent. Un sec-ch-ua: "Chrome";v="131" adossé à un User-Agent qui revendique Chrome 145, c’est le drapeau rouge immédiat.

Ajouter des délais aléatoires entre les requêtes

Des intervalles figés se font cueillir par la détection de motifs. Misez sur une variation aléatoire :

import time, random

# Entre chaque requête
time.sleep(random.uniform(3, 6))

# En cas de nouvelle tentative après blocage
def backoff_sleep(attempt):
    base = 4
    sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
    time.sleep(min(sleep_time, 60))

Le consensus relevé sur ScrapeOps et WebScraping.AI tourne autour de 3 à 6 secondes entre les requêtes par adresse IP, avec un plafond d’environ 100 requêtes par IP et par session avant de basculer.

Utiliser la rotation de proxies

C’est le levier de réussite le plus décisif. Les proxies de datacenter issus d’AWS/GCP affichent un taux de réussite d’environ 5 à 15 % sur les cibles Cloudflare Enterprise — autant dire inexploitables sur Indeed. Les proxies résidentiels, couplés à un bon fingerprint TLS, grimpent à 80–95 % de réussite.

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,
)

En 2026, les proxies résidentiels se négocient grosso modo entre 4 et 8,50 $ le Go (environ 3,70 à 7,90 €), selon le fournisseur et le niveau d’engagement. Pour Indeed, démarrez avec un petit pool, puis montez en charge si besoin.

Gérer proprement les codes de statut 403, 429 et 503

Ne relancez pas à l’aveugle. Chaque code de statut raconte une histoire différente :

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,
            )
            # Vérifier le cas vicieux « 200 avec défi »
            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 — bloqué. Changement de proxy, tentative {attempt + 1}")
            elif r.status_code == 429:
                print(f"429 — limitation de débit. Ralentissement.")
            elif r.status_code == 503:
                print(f"503 — serveur surchargé ou défi JS.")

            backoff_sleep(attempt)
        except Exception as e:
            print(f"Erreur de requête : {e}")
            backoff_sleep(attempt)

    raise RuntimeError(f"Échec après {max_retries} tentatives : {url}")

Le cas « 200 avec défi » est le plus piégeux. Inspectez toujours le corps de la réponse à la recherche des marqueurs cf-turnstile ou Just a moment avant de tenir un 200 pour un succès.

L’alternative plus simple : laisser Thunderbit gérer l’anti-bot pour vous

Pour qui n’a aucune envie de construire et d’entretenir des pools de proxies, de la rotation d’en-têtes et de l’imitation d’empreintes TLS, le scraping cloud de Thunderbit prend automatiquement en charge les CAPTCHA, la rotation des proxies et les protections anti-bot. Pas de configuration de proxy, pas de réglage curl_cffi, pas de bibliothèque de résolution de CAPTCHA. La voie de moindre résistance quand vous voulez juste les données.

Pourquoi votre scraper Indeed se casse sans cesse (et comment le réparer)

Le mur des 403, c’est la douleur aiguë. La maintenance, elle, c’est la douleur chronique — ces scrapers qui tournent aujourd’hui et lâchent la semaine suivante, en renvoyant en sourdine des données vides ou périmées.

Comment Indeed casse vos sélecteurs

Indeed fait tourner ses noms de classes CSS sans ménagement. Le guide de Bright Data le souligne noir sur blanc : des classes comme css-1m4cuuf et css-1rqpxry « semblent être générées aléatoirement — probablement à la compilation ». Les tests A/B font voir des mises en page différentes selon les sessions. Et les remaniements du DOM tombent sans crier gare.

Le cas JobFunnel est éclairant. Un contributeur a noté : « CaptchaBuster a réussi à atténuer le captcha, et si l’extraction de la page échoue encore, c’est [à cause] de sélecteurs Beautiful Soup obsolètes. » Le scraper n’était pas bloqué — il analysait simplement les mauvais éléments.

Stratégie : privilégier le JSON caché au parsing du DOM

Le bloc window.mosaic.providerData est resté stable au niveau du schéma depuis au moins 2023. Le chemin metaData.mosaicProviderJobCardsModel.results[] demeure canonique en 2026. Les sélecteurs DOM cassent chaque mois. L’extraction JSON, elle, casse au pire une fois par an.

Stratégie : utiliser des attributs de données plutôt que les noms de classes

Quand il faut composer avec le DOM, visez les attributs fonctionnels :

SélecteurUtilité
[data-testid="slider_item"]Conteneur de chaque carte d’offre
[data-testid="job-title"] ou h2.jobTitle > aLien du titre de l’offre
[data-testid="company-name"]Nom de l’employeur
[data-testid="text-location"]Texte du lieu
data-jk="<jobkey>" sur chaque carteL’ancrage le plus stable — inchangé depuis 2019

Ajouter des assertions pour détecter des sélecteurs obsolètes

Ne laissez jamais votre scraper tourner en silence avec zéro résultat. Glissez une vérification après chaque récupération :

results = parse_hidden_json(html)
assert len(results) > 0, (
    f"Indeed a renvoyé un ensemble de résultats vide pour start={start} — "
    "blocage possible, CAPTCHA ou dérive des sélecteurs. "
    f"500 premiers caractères de la réponse : {html[:500]}"
)

En cas d’échec, consignez les 500 à 2000 premiers caractères de la réponse brute. Vous saurez sur-le-champ si vous avez décroché un défi Turnstile, une barrière de connexion ou un changement de schéma. Faites tourner un test de fumée quotidien au niveau CI sur une requête fixe (par ex. q=python&l=remote) qui s’assure que les résultats ne sont pas nuls.

L’alternative IA : des scrapers qui ne cassent jamais

L’IA de Thunderbit relit la structure de la page à chaque exécution — elle ne dépend d’aucun sélecteur codé en dur ni d’aucun motif regex figé. Quand Indeed retouche son HTML, Thunderbit s’ajuste automatiquement. Voilà une réponse directe au fardeau de maintenance que les utilisateurs de forums citent comme leur frustration numéro un. Si vous vous êtes déjà réveillé devant un message Slack annonçant « le scraper renvoie à nouveau des lignes vides », vous mesurez ce que vaut le fait de ne pas avoir à le réparer.

Extraire Indeed sans écrire de Python : l’alternative sans code

Tous les guides concurrents tiennent pour acquis que vous écrirez du Python. Sauf que les forums racontent une autre histoire. Les utilisateurs y lâchent des phrases comme « c’est tellement difficile avec les bugs et les erreurs constants », et certains envisagent carrément de recruter quelqu’un sur Fiverr juste pour récupérer les données. Si cela vous parle, cette section est votre issue de secours.

Comment extraire Indeed avec Thunderbit, étape par étape

Étape 1 : installez l’extension Chrome Thunderbit depuis le Chrome Web Store. Le démarrage est gratuit.

Étape 2 : ouvrez une page de résultats Indeed dans votre navigateur — par exemple, https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.

Étape 3 : cliquez sur l’icône Thunderbit dans la barre d’outils du navigateur, puis sur « Suggérer des champs avec l’IA ». L’IA de Thunderbit décortique la page et détecte automatiquement des colonnes comme Titre du poste, Entreprise, Lieu, Salaire, URL de l’offre et Date de publication. Vous pouvez vérifier et ajuster les champs suggérés — retirer les colonnes superflues ou en ajouter sur mesure en décrivant votre besoin en langage courant.

Étape 4 : cliquez sur « Extraire ». Thunderbit récupère les données de la page et les présente dans un tableau structuré. Vous devriez voir s’afficher des lignes d’offres avec les champs configurés.

Enrichir avec l’extraction des sous-pages

Une fois la page de liste extraite, cliquez sur « Extraire les sous-pages » : Thunderbit visite alors chaque fiche d’offre. Il en ramène les descriptions complètes, les qualifications, les avantages et les liens de candidature — sans aucune configuration supplémentaire. L’équivalent d’un deuxième scraper Python chargé de parcourir chaque URL /viewjob?jk=<jobkey>, à ceci près qu’ici, un clic suffit.

Gérer automatiquement la pagination

Thunderbit prend en charge tout seul la pagination par clic d’Indeed. Inutile de fabriquer des URL avec offset à la main ni d’écrire des boucles de pagination. Il enchaîne les pages et agrège les résultats.

Exporter vers vos outils préférés

Exportez les données extraites vers CSV, Excel, Google Sheets, Airtable ou Notion — entièrement gratuit. Pas une ligne de csv.writer() ou de pandas.to_csv() à écrire.

Quand utiliser Python plutôt que Thunderbit

ScénarioMeilleur outil
Pipelines de données sur mesure, automatisation planifiée via cron/AirflowPython
Intégration dans une base de code plus largePython
Logique d’analyse hautement personnaliséePython
Recherche ponctuelle ou analyse de marchéThunderbit
Les membres non techniques de l’équipe ont besoin des donnéesThunderbit
Obtenir les données maintenant sans déboguer des 403Thunderbit
Enrichissement de sous-pages sans aucune configurationThunderbit

Côté temps : configuration Python + débogage anti-bot = des heures à des jours (surtout la première fois). Thunderbit = moins de 2 minutes pour les mêmes données. Je ne dis pas que Python est à jeter — je dis que tout dépend de votre besoin.

Le scraping d’Indeed est-il légal ? Ce qu’il faut savoir

Aucun des guides de scraping Indeed les mieux classés n’aborde la légalité, ce qui surprend vu la fréquence à laquelle la question « Le scraping Indeed est-il légal ? » surgit sur les forums. Ceci n’est pas un avis juridique, mais voici le panorama.

Les conditions d’utilisation d’Indeed

Les CGU d’Indeed (indeed.com/legal) ne comportent aucune clause générale interdisant tout scraping. La seule interdiction explicite d’automatisation figure à la section A.3.5, qui proscrit « l’utilisation de toute automatisation, script ou bot pour automatiser le processus Indeed Apply ». L’interdiction cible donc étroitement le flux Apply, et non la consultation passive des offres publiques. Le principal levier de contrôle d’Indeed est technique — défis Cloudflare, bannissements d’IP, empreintes d’appareil — et non judiciaire.

Jurisprudence pertinente

L’affaire américaine la plus citée est hiQ Labs v. LinkedIn. La 9e Cour d’appel a jugé en avril 2022 que l’extraction de données publiquement accessibles « ne viole probablement pas » le CFAA (Computer Fraud and Abuse Act). hiQ a néanmoins été ensuite reconnue responsable d’une rupture de contrat, ses employés ayant créé de faux profils LinkedIn et accepté les CGU.

Plus récemment, Meta v. Bright Data (N.D. Cal., janvier 2024) a livré une décision encore plus tranchée. Le juge Chen a estimé que les conditions d’utilisation de Facebook et Instagram « n’interdisent pas l’extraction de données publiques lorsque l’on est déconnecté ». Meta a renoncé de son plein gré aux autres griefs le mois suivant.

robots.txt d’Indeed

Le robots.txt d’Indeed interdit largement /jobs/ et /job/ à l’User-agent: * par défaut, mais autorise explicitement Googlebot et Bingbot à accéder à /viewjob? — les fiches de détail des offres. Les crawlers d’entraînement IA (GPTBot, CCBot, anthropic-ai) sont fortement bridés. Aux États-Unis, robots.txt n’a pas de portée juridiquement contraignante, mais le respecter reste une bonne pratique et un gage de bonne foi.

Bonnes pratiques pour un scraping responsable

  • Ne collectez que des données accessibles publiquement — ne vous connectez jamais, ne créez jamais de faux comptes
  • Respectez les limites de débit : 1 requête toutes les 3 à 6 secondes par IP, avec une faible concurrence
  • Ne republiez pas les données extraites comme si elles provenaient de votre propre site d’offres
  • Réservez les données à un usage personnel ou à une recherche interne, pas à de la revente commerciale sans autorisation
  • Supprimez ou hachez les données personnelles dont vous n’avez pas besoin ; fixez une durée de conservation pour les données proches de l’identifiant personnel
  • En cas d’opérations à grande échelle ou dans l’UE/au Royaume-Uni, consultez un juriste — les obligations de transparence de l’article 14 du RGPD s’appliquent aux données personnelles extraites

L’éventail du risque s’étend de l’automatisation d’une recherche d’emploi personnelle, au niveau bas, à la revente commerciale à grande échelle des données Indeed, au niveau élevé.

Conclusion et points clés à retenir

Extraire Indeed avec Python, c’est jouable, mais ce n’est pas le projet d’un week-end qu’on installe puis qu’on oublie. Entre la protection Cloudflare d’Indeed, les sélecteurs mouvants et les mesures anti-bot agressives, mieux vaut s’y attaquer avec les bons outils et les bonnes attentes.

Voici ce que je retiendrais au final :

  • Indeed est la source de données la plus riche sur le marché de l’emploi sur le web — 350 M de visiteurs mensuels, 130 M d’annonces — mais il se défend âprement contre les scrapers.
  • L’extraction du JSON caché (window.mosaic.providerData) est l’approche Python la plus résiliente. Le schéma tient depuis des années, là où les sélecteurs CSS cassent chaque mois.
  • curl_cffi avec imitation du navigateur est le client HTTP par défaut en 2026 pour les sites protégés par Cloudflare. requests et httpx classiques se font bloquer rien que sur l’empreinte TLS.
  • Combinez toujours en-têtes rotatifs, délais aléatoires et proxies résidentiels pour éviter les erreurs 403. Les proxies de datacenter sont quasi inutiles face à Cloudflare Enterprise.
  • Ajoutez des vérifications par assertion pour savoir immédiatement quand les sélecteurs cassent ou quand vous récoltez une page de défi à la place des offres.
  • Pour les utilisateurs non techniques, ou pour quiconque veut simplement des résultats sans délai, Thunderbit ouvre une voie sans code, propulsée par l’IA, qui s’adapte automatiquement aux changements du site — pas de proxy, pas de débogage, pas de maintenance.

Pour tester la voie sans code, Thunderbit propose une formule gratuite : de quoi l’essayer sur Indeed sans engagement. Et si vous optez pour Python, les exemples de code ci-dessus constituent un bon tremplin — n’oubliez pas de traiter la robustesse anti-bot comme une exigence de premier plan, et non comme un détail de fin de parcours.

Pour creuser les approches et outils de web scraping, parcourez nos guides sur comment faire du web scraping avec Python, les meilleurs outils automatisés de web scraping et le web scraping sans se faire bloquer. Vous pouvez aussi suivre des tutoriels sur la chaîne YouTube de Thunderbit.

Essayez Thunderbit pour extraire plus vite les données Indeed Get Started Free

FAQ

Quelles bibliothèques Python sont les meilleures pour extraire Indeed ?

Pour les requêtes HTTP, curl_cffi s’impose en 2026 — il imite les empreintes TLS réelles d’un navigateur, indispensable pour franchir Cloudflare. httpx avec HTTP/2 fait une bonne solution de repli sur des cibles moins protégées. Pour l’analyse HTML, BeautifulSoup4 avec lxml reste la référence. Pour l’automatisation navigateur, Playwright (avec playwright-stealth) ou undetected-chromedriver tiennent la route, même si tous deux deviennent de plus en plus détectables. L’approche regex sur le JSON caché (window.mosaic.providerData) évite, elle, tout le gros œuvre d’analyse.

Pourquoi est-ce que je reçois sans cesse des erreurs 403 en extrayant Indeed ?

Indeed s’appuie sur Cloudflare Bot Management, qui ausculte votre empreinte TLS (JA3/JA4), l’ordre des en-têtes HTTP/2, les schémas de requête et le comportement du navigateur. Avec requests seul, votre empreinte TLS vous identifie aussitôt comme un script Python — le 403 tombe avant même la lecture de vos en-têtes. Le remède : passer à curl_cffi avec imitation du navigateur, faire tourner des User-Agents crédibles, ajouter des délais aléatoires (3 à 6 secondes) et recourir à des proxies résidentiels. Surveillez aussi le cas du « 200 avec défi Turnstile » — traquez les marqueurs cf-turnstile dans le corps des réponses.

Puis-je extraire Indeed sans coder ?

Oui. Des outils comme Thunderbit permettent d’extraire des offres Indeed en quelques clics — installez l’extension Chrome, ouvrez une page de recherche Indeed, cliquez sur « Suggérer des champs avec l’IA », puis sur « Extraire ». L’IA de Thunderbit détecte automatiquement des champs comme l’intitulé du poste, l’entreprise, le lieu et le salaire. Elle prend en charge toute seule la pagination, l’enrichissement des sous-pages (descriptions complètes) et les protections anti-bot. Exportez gratuitement vers CSV, Google Sheets, Airtable ou Notion.

À quelle fréquence Indeed modifie-t-il sa structure HTML ?

Indeed fait régulièrement tourner ses noms de classes CSS (par ex. css-1m4cuuf, des chaînes hachées au hasard) et remanie les éléments DOM sans préavis. Les tests A/B font que différents utilisateurs voient parfois des mises en page différentes au même moment. L’approche JSON caché (window.mosaic.providerData) est nettement plus stable — le schéma est resté cohérent depuis au moins 2023. Quand vous devez recourir à des sélecteurs DOM, visez les attributs data-testid et data-jk (job key) plutôt que les classes CSS.

Est-il légal d’extraire Indeed ?

L’extraction hors connexion de pages Indeed publiques est peu susceptible d’engager une responsabilité au titre du CFAA aux États-Unis, à la lumière de l’arrêt hiQ v. LinkedIn de la 9e Cour d’appel (2022) et de la décision Meta v. Bright Data (2024). Les CGU d’Indeed interdisent spécifiquement l’automatisation du processus Apply, pas la consultation passive des offres publiques. Cela dit, restez toujours responsable : ne vous connectez pas, ne créez pas de faux comptes, respectez les limites de débit, ne republiez pas les données comme s’il s’agissait de votre propre site d’offres, et maniez avec soin toute donnée personnelle (noms de recruteurs, emails) au regard du RGPD/CCPA. Pour une activité commerciale à grande échelle, consultez un avocat.

En savoir plus

Fawad Khan
Fawad Khan
Fawad écrit pour gagner sa vie, et honnêtement, il adore ça. Il a passé des années à comprendre ce qui fait qu’une phrase publicitaire marque les esprits — et ce qui pousse les lecteurs à défiler sans s’arrêter. Parlez-lui de marketing, et il pourrait en discuter pendant des heures. Parlez-lui de carbonara, et il parlera encore plus longtemps.
Table des matières

Récupère une page web en la demandant simplement

Dis ce qu’il te faut en français courant. Ou mieux encore, ne dis rien du tout.

Essayer Thunderbit gratuit
Extraire des données avec l’IA
Transfère facilement des données vers Google Sheets, Airtable ou Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week