Da qualche parte intorno al mio cinquantesimo copia-incolla di un titolo di lavoro da Indeed in un foglio di calcolo, ho iniziato a mettere in discussione le mie scelte di carriera. Se hai mai provato a estrarre in modo programmatico dati strutturati da Indeed, sai già qual è il finale: l’errore 403 non è un bug, è una funzione del sistema di difesa di Indeed.
Indeed è la più grande bacheca di annunci di lavoro al mondo, con circa 350 milioni di visitatori unici mensili, 130 milioni di annunci di lavoro in qualsiasi momento e attività in oltre 60 Paesi. Questo la rende una delle fonti più ricche di dati sul mercato del lavoro al mondo — e una delle più difficili da estrarre. Lo scraper open source JobFunnel (migliaia di stelle su GitHub) è stato letteralmente archiviato dal suo maintainer nel dicembre 2025 dopo anni di sconfitta nella corsa agli armamenti contro i bot. Parole del maintainer: "Tutti gli utenti riescono a estrarre alcuni annunci, ma vengono rapidamente bloccati dal captcha, e l’estrazione fallisce, senza restituire alcun annuncio." Un altro contributore ha segnalato di aver ricevuto un CAPTCHA alla primissima richiesta. Insomma: non è un obiettivo banale da estrarre. In questa guida ti mostrerò ogni metodo pratico per estrarre Indeed con Python, ti farò vedere come sopravvivere davvero al guado del 403 e — per chi preferisce saltare del tutto il debugging — ti mostrerò un’alternativa no-code usando Thunderbit.
Cosa significa estrarre Indeed con Python?
Il web scraping, nella sua essenza, è l’estrazione automatica di dati strutturati dalle pagine web. Quando parliamo di estrarre Indeed con Python, intendiamo scrivere uno script che visita i risultati di ricerca e le pagine di dettaglio degli annunci di Indeed, legge l’HTML sottostante (o i dati incorporati) ed estrae campi come titolo del lavoro, azienda, località, stipendio e descrizione in un formato utilizzabile — un CSV, un database, un Google Sheet.
Le librerie Python tipiche coinvolte sono Requests (per le chiamate HTTP), BeautifulSoup (per il parsing HTML) e Selenium o Playwright (per l’automazione del browser). Ma Indeed non è un semplice sito statico. È un ibrido: HTML renderizzato lato server con un blob JSON di stato incorporato, protetto da Cloudflare Bot Management. Questo significa che il tuo scraper deve gestire contenuti renderizzati da JavaScript, nomi di classe CSS che ruotano e protezioni anti-bot aggressive — tutto prima ancora di analizzare un singolo titolo di lavoro.
Nel 2026 non esiste nemmeno una API ufficiale, gratuita e in sola lettura di Indeed. La vecchia Publisher Jobs API è stata dismessa intorno al 2020 e ciò che resta è solo lato datore di lavoro (Job Sync, Sponsored Jobs). Quindi le opzioni realistiche sono soltanto lo scraping oppure il pagamento di un fornitore terzo di dati.
Perché estrarre dati di lavoro da Indeed?
Il caso d’uso è semplice: consultare manualmente migliaia di annunci è impraticabile, e i dati contenuti in quegli annunci sono davvero preziosi.

| Caso d’uso | Chi ne beneficia | Esempio |
|---|---|---|
| Generazione di lead | Team sales e recruiting | Creare liste di aziende che assumono con i contatti |
| Ricerca sul mercato del lavoro | Analisti, team HR | Individuare competenze di tendenza, benchmark salariali per area |
| Competitive intelligence | Datori di lavoro, agenzie per il lavoro | Monitorare i modelli di assunzione dei concorrenti e le offerte salariali |
| Automazione della ricerca di lavoro personale | Candidati | Aggregare annunci che corrispondono ai tuoi criteri in più località |
| Dati di training per modelli ML | Data scientist | Creare modelli di previsione salariale dai dati storici sugli stipendi |
La stessa ricerca di Indeed Hiring Lab conferma che i dati degli annunci seguono da vicino i dati BLS JOLTS e possono fungere da proxy quasi in tempo reale delle condizioni del mercato del lavoro statunitense. Gli hedge fund usano la velocità di pubblicazione degli annunci come segnale di dati alternativi. I team HR confrontano le retribuzioni usando gli intervalli salariali estratti. E i recruiter costruiscono liste di prospect partendo dalle aziende che stanno assumendo attivamente.
Una nota pratica: i dati sugli stipendi su Indeed stanno migliorando, ma restano incompleti. A metà 2025, circa il 59% degli annunci negli USA include informazioni salariali, ma solo circa il 22% riporta una cifra esatta — il resto sono range. Qualunque analisi salariale basata sui dati di Indeed dovrebbe tenere conto di questa scarsità.
Scegliere il metodo giusto per estrarre Indeed con Python
Non esiste un solo modo “giusto” per estrarre Indeed. L’approccio migliore dipende dal tuo livello, dalla quantità di dati che ti serve e da quanta manutenzione sei disposto a tollerare. Ho testato tutti e quattro i principali approcci, e questo è il confronto:
| Criterio | BS4 + Requests | Selenium | JSON nascosto (window.mosaic) | No-code (Thunderbit) |
|---|---|---|---|---|
| Difficoltà | Principiante | Intermedio | Intermedio-avanzato | Nessuna (2 clic) |
| Velocità | Veloce | Lenta (render del browser) | Veloce | Veloce (cloud scraping) |
| Contenuti renderizzati da JS | No | Sì | Sì (dati incorporati) | Sì |
| Resistenza ai bot | Bassa | Media (rilevabile) | Medio-alta | Alta (gestita automaticamente) |
| Manutenzione quando cambia l’HTML | Alta (i selettori si rompono) | Alta | Media (struttura JSON più stabile) | Nessuna (l’AI si adatta) |
| Ideale per | Prototipi rapidi | Pagine dinamiche, contenuti protetti da login | Dati strutturati in massa | Non sviluppatori, risultati rapidi |
Questa guida illustra ogni metodo. Se sei uno sviluppatore Python, vorrai leggere le sezioni BS4, JSON nascosto e Selenium. Se non programmi (o sei semplicemente stanco di debuggare 403), passa direttamente alla sezione Thunderbit.
Prima di iniziare
- Difficoltà: Principiante-intermedio (sezioni Python); Nessuna (sezione Thunderbit)
- Tempo necessario: ~20–60 minuti per configurare Python e fare il primo scraping; ~2 minuti con Thunderbit
- Cosa ti serve: Python 3.9+, un editor di codice, browser Chrome e (per la via no-code) l’estensione Chrome di Thunderbit
Configurare l’ambiente Python per lo scraping di Indeed
Prima di scrivere qualsiasi codice di scraping, prepara il tuo ambiente.
Installa le librerie necessarie
Crea un ambiente virtuale e installa i pacchetti che ti servono:
python -m venv indeed_env
source indeed_env/bin/activate # Su Windows: indeed_env\Scripts\activate
# Per l’approccio HTTP + parsing
pip install requests beautifulsoup4 lxml httpx
# Per l’approccio con JSON nascosto (consigliato)
pip install curl_cffi parsel tenacity
# Per l’approccio con automazione del browser
pip install selenium
Alcune note:
curl_cffiè il valore predefinito nel 2026 per estrarre siti protetti da Cloudflare. Imita le impronte TLS reali del browser, cosa cherequestsehttpxnon possono fare. Ne parleremo meglio nella sezione anti-bot.- Selenium 4.6+ include Selenium Manager, quindi non devi più scaricare manualmente ChromeDriver — gestisce tutto in automatico.
- Usa
lxmlcome backend parser di BeautifulSoup. È circa 1,5 volte più veloce del parserhtml.parserdella libreria standard.
Crea la struttura del progetto
Mantienila semplice:
indeed_scraper/
├── scraper.py
├── requirements.txt
└── output/
Tutti gli esempi di codice qui sotto si basano su scraper.py.
Come estrarre Indeed con Python usando BeautifulSoup
Questo è l’approccio adatto ai principianti: usa requests per recuperare la pagina e BeautifulSoup per analizzare l’HTML. È il più rapido da configurare, ma anche il più fragile su Indeed.
Passo 1: costruire l’URL di ricerca Indeed
Gli URL di ricerca di Indeed seguono uno schema prevedibile:
https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
Per esempio, per cercare “data analyst” ad “Austin, TX” partendo dalla prima pagina:
from urllib.parse import urlencode
params = {
"q": "data analyst",
"l": "Austin, TX",
"start": 0,
}
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
print(url)
# https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX&start=0
Indeed impagina i risultati a blocchi di 10, con un limite massimo rigido di 1.000 risultati (start <= 990). Qualsiasi offset superiore a 990 restituisce in silenzio la stessa pagina.
Passo 2: inviare una richiesta HTTP con header corretti
Indeed blocca immediatamente le richieste con le stringhe user-agent predefinite di Python. Ti servono header realistici:
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)
Se ottieni un 200, per il momento sei dentro. Se ottieni un 403, Cloudflare ti ha intercettato. (Più avanti vedremo come sopravvivere.)
Passo 3: analizzare gli annunci dall’HTML
Usa BeautifulSoup per selezionare gli elementi delle schede lavoro. Punta agli attributi data-testid — sono più stabili dei nomi di classe CSS randomizzati di 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"Trovati {len(jobs)} annunci")
Passo 4: gestire la paginazione
Scorri le pagine incrementando il parametro start:
import time, random
all_jobs = []
for page in range(0, 50, 10): # Prime 5 pagine
params["start"] = page
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
response = requests.get(url, headers=headers, timeout=30)
# ... analizza come sopra ...
all_jobs.extend(jobs)
time.sleep(random.uniform(3, 6))
Limiti di questo approccio
Lo dico chiaramente: BS4 + Requests è il metodo più debole per Indeed nel 2026. requests usa la libreria TLS standard di Python, che produce una impronta JA3 che Cloudflare identifica all’istante come “non un browser”. Inoltre non supporta HTTP/2, mentre Indeed lo serve. È molto probabile che tu venga bloccato dopo poche pagine. E i selettori CSS? Indeed ruota frequentemente nomi di classe come css-1m4cuuf e jobsearch-JobComponent-embeddedBody-1n0gh5s molto frequentemente — quindi qualsiasi selettore che li usi è una bomba a orologeria.
Usa questo metodo per prototipare rapidamente su una singola pagina. Per qualunque cosa su scala maggiore, usa l’approccio con JSON nascosto.
Come estrarre Indeed con Python usando dati JSON nascosti
Questo è il metodo che consiglio alla maggior parte degli sviluppatori Python. Invece di analizzare elementi HTML fragili, estrai i dati strutturati da una variabile JavaScript incorporata nel sorgente della pagina di Indeed: window.mosaic.providerData["mosaic-provider-jobcards"].
Tutti i campi che ti interessano — titolo del lavoro, azienda, località, stipendio, job key, data di pubblicazione, flag remoto — sono già presenti in questo blob JSON. Non serve eseguire JavaScript. Lo schema è stabile almeno dal 2023, quindi è molto più resistente dei selettori DOM.
Passo 1: recuperare l’HTML della pagina
Usa curl_cffi invece di requests — imita le impronte TLS reali del browser, ed è fondamentale per sopravvivere a 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))
Perché curl_cffi? È un binding Python di curl-impersonate, che riproduce esattamente il ClientHello TLS, il frame HTTP/2 SETTINGS e l’ordine degli header dei browser reali. È l’unico client HTTP Python attivamente mantenuto che aggira sia JA3/JA4 sia l’impronta H2 di Akamai in una sola chiamata. I target di impersonificazione supportati includono chrome120, chrome124, chrome131, Safari e varianti Edge.
Passo 2: estrarre il JSON con una regex
Il blob JSON è incorporato in un tag <script>. Estrailo con una 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"Trovati {len(results)} annunci nel JSON nascosto")
else:
print("JSON nascosto non trovato — possibile blocco o modifica della pagina")
Passo 3: analizzare i campi degli annunci dal JSON
Ogni elemento in results contiene più dati di quanti ne siano visibili sulla pagina:
jobs = []
for job in results:
jobs.append({
"jobkey": job["jobkey"],
"title": job["title"],
"company": job.get("company"),
"location": job.get("formattedLocation"),
"remote": job.get("remoteLocation"),
"salary": (job.get("salarySnippet") or {}).get("text"),
"posted": job.get("formattedRelativeTime"),
"job_type": job.get("jobTypes"),
"easy_apply": job.get("indeedApplyEnabled"),
"url": f"https://www.indeed.com/viewjob?jk={job['jobkey']}",
})
Il JSON include spesso stime salariali, attributi di tassonomia (tag di competenze) e valutazioni aziendali che non sono sempre visibili nell’HTML renderizzato.
Passo 4: estrarre più pagine
Usa tierSummaries nel JSON per capire il numero totale di risultati, poi fai il loop:
import time, random
all_jobs = []
for start in range(0, 50, 10): # Prime 5 pagine
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"Totale: {len(all_jobs)} annunci estratti")
Perché il JSON nascosto è più resistente
La struttura window.mosaic.providerData cambia meno spesso dei nomi di classe CSS. Ottieni dati puliti e strutturati senza analizzare HTML disordinato. Detto questo, serve comunque una mitigazione anti-bot (header, ritardi, proxy) — che vedremo tra poco.
Come estrarre Indeed con Python usando Selenium
Selenium è l’approccio basato sull’automazione del browser. È utile quando devi interagire con la pagina — cliccare nei pannelli di dettaglio del lavoro, gestire contenuti protetti da login o estrarre descrizioni caricate dinamicamente che non sono nell’HTML iniziale.
Quando usare Selenium invece dei client HTTP
- Indeed carica alcuni contenuti dinamicamente (descrizioni complete nella barra laterale destra)
- Devi estrarre pagine che richiedono stato di sessione o login
- Stai facendo scraping su piccola scala, dove la velocità non è critica
Guida rapida
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") # L’headless è più rilevabile — usalo con cautela
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()
Limiti
Selenium è lento — ogni pagina richiede il rendering completo del browser. Chrome in modalità headless è rilevabile dal sistema anti-bot di Indeed (Cloudflare controlla navigator.webdriver, le stringhe del vendor WebGL, il numero di plugin e altro ancora). Anche undetected-chromedriver al massimo ritarda il rilevamento; non lo impedisce per sempre. E, come con BS4, i tuoi selettori si romperanno quando Indeed aggiornerà la UI.
Per la maggior parte dei casi d’uso, l’approccio con JSON nascosto ti dà gli stessi dati più velocemente e con meno manutenzione. Riserva Selenium ai casi limite in cui hai davvero bisogno di un browser.
Come evitare gli errori 403 quando estrai Indeed con Python
Questa è la sezione più importante. Se sei arrivato qui dopo una ricerca Google frustrante, sei nel posto giusto.

Perché Indeed blocca il tuo scraper
Indeed usa Cloudflare Bot Management più Cloudflare Turnstile — non DataDome, non PerimeterX. Lo confermano gli header di risposta: server: cloudflare, cf-ray e il cookie di bot management __cf_bm. Cloudflare esamina la tua impronta TLS (JA3/JA4), l’ordine degli header HTTP/2, i pattern delle richieste e i segnali di comportamento del browser. Se uno di questi elementi sembra non umano, ottieni un 403, un 429, un 503 o — nel caso più subdolo — un 200 OK con una pagina di sfida Turnstile al posto dei veri dati di lavoro.
Ruota User-Agent e header della richiesta
Un singolo User-Agent statico è il modo più rapido per farsi bloccare. Ruota tra una serie di stringhe attuali e realistiche. Importante: i campi minor version di Chrome sono bloccati a 0.0.0 dalla User-Agent Reduction — non inventare versioni minori non zero, altrimenti i sistemi anti-bot se ne accorgeranno.
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",
}
Assicurati anche che i Client Hints sec-ch-ua corrispondano alla versione del tuo UA. Un sec-ch-ua: "Chrome";v="131" accanto a un User-Agent che dichiara Chrome 145 è un segnale d’allarme immediato.
Aggiungi ritardi casuali tra le richieste
Intervalli fissi vengono individuati dal rilevamento di pattern. Usa jitter casuale:
import time, random
# Tra una richiesta e l’altra
time.sleep(random.uniform(3, 6))
# In caso di retry dopo un blocco
def backoff_sleep(attempt):
base = 4
sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
time.sleep(min(sleep_time, 60))
Il consenso emerso da ScrapeOps e WebScraping.AI è di 3–6 secondi tra le richieste per IP, con un tetto rigido di circa 100 richieste per IP per sessione prima della rotazione.
Usa la rotazione dei proxy
Questo è il singolo fattore più determinante per il successo. I proxy datacenter da range AWS/GCP ottengono circa il 5–15% di successo contro target Cloudflare Enterprise — di fatto inutilizzabili su Indeed. I proxy residenziali, insieme a un corretto fingerprint TLS, salgono all’80–95% di successo.
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,
)
Nel 2026 il prezzo dei proxy residenziali si aggira intorno a $4–8,50 per GB, a seconda del provider e del livello di impegno. Per Indeed in particolare, parti con un piccolo pool e scala solo quando serve.
Gestire in modo elegante i codici di stato 403, 429 e 503
Non limitarti a riprovare alla cieca. Codici diversi significano cose diverse:
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,
)
# Controlla il caso subdolo "200 con 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 — bloccato. Cambio proxy, tentativo {attempt + 1}")
elif r.status_code == 429:
print(f"429 — rate limit raggiunto. Rallento.")
elif r.status_code == 503:
print(f"503 — server sovraccarico o challenge JS.")
backoff_sleep(attempt)
except Exception as e:
print(f"Errore nella richiesta: {e}")
backoff_sleep(attempt)
raise RuntimeError(f"Fallito dopo {max_retries} tentativi: {url}")
Il caso 200-con-challenge è il più insidioso. Cerca sempre nel corpo della risposta i marker cf-turnstile o Just a moment prima di considerare un 200 come successo.
L’alternativa più semplice: lascia che Thunderbit gestisca l’anti-bot
Per chi non vuole costruire e mantenere pool di proxy, rotazione degli header e impersonificazione delle impronte TLS, lo scraping cloud di Thunderbit gestisce automaticamente CAPTCHA, rotazione dei proxy e protezioni anti-bot. Niente configurazione proxy, niente setup di curl_cffi, nessuna libreria per risolvere CAPTCHA. È la strada con meno attriti quando ti servono solo i dati.
Perché il tuo scraper di Indeed continua a rompersi (e come risolvere)
Il muro del 403 è il dolore acuto. Il dolore cronico è la manutenzione — gli scraper che funzionano oggi si rompono la settimana dopo, restituendo in silenzio dati vuoti o risultati obsoleti.
Come Indeed rompe i tuoi selettori
Indeed ruota in modo aggressivo i nomi delle classi CSS. La guida di Bright Data avverte esplicitamente che classi come css-1m4cuuf e css-1rqpxry “sembrano generate casualmente — probabilmente in fase di build”. I test A/B fanno sì che sessioni diverse vedano layout diversi. E la ristrutturazione del DOM avviene senza preavviso.
La vicenda JobFunnel è istruttiva. Un contributore ha segnalato: "CaptchaBuster ha mitigato con successo il captcha, e il motivo per cui la pagina [veniva ancora estratta senza successo] è dovuto a selettori Beautiful Soup obsoleti." Lo scraper non era bloccato — stava analizzando gli elementi sbagliati.
Strategia: preferire il JSON nascosto al parsing del DOM
Il blob window.mosaic.providerData ha mantenuto uno schema stabile almeno dal 2023. Il percorso metaData.mosaicProviderJobCardsModel.results[] è ancora canonico nel 2026. I selettori DOM si rompono ogni mese. L’estrazione da JSON si rompe, se va male, una volta l’anno.
Strategia: usare gli attributi data invece dei nomi di classe
Quando devi davvero toccare il DOM, punta ad attributi funzionali:
| Selettore | Scopo |
|---|---|
[data-testid="slider_item"] | Il contenitore di ogni scheda lavoro |
[data-testid="job-title"] oppure h2.jobTitle > a | Il link del titolo del lavoro |
[data-testid="company-name"] | Il nome del datore di lavoro |
[data-testid="text-location"] | Il testo della località |
data-jk="<jobkey>" su ogni scheda | L’aggancio più stabile in assoluto — invariato dal 2019 |
Aggiungi asserzioni per rilevare selettori obsoleti
Non lasciare mai che il tuo scraper giri in silenzio con zero risultati. Aggiungi un controllo dopo ogni fetch:
results = parse_hidden_json(html)
assert len(results) > 0, (
f"Indeed ha restituito un set di risultati vuoto a start={start} — "
"possibile blocco, CAPTCHA o deriva dei selettori. "
f"Primi 500 caratteri della risposta: {html[:500]}"
)
Registra i primi 500–2000 caratteri della risposta grezza in caso di errore. In questo modo capirai subito se hai ricevuto una sfida Turnstile, una barriera di accesso o una modifica dello schema. Esegui un test giornaliero di smoke test in CI su una query fissa (ad es. q=python&l=remote) che verifichi che i risultati siano diversi da zero.
L’alternativa AI: scraper che non si rompono mai
L’AI di Thunderbit legge la struttura della pagina ogni volta da zero — non si basa su selettori hardcoded o pattern regex. Quando Indeed cambia il suo HTML, Thunderbit si adatta automaticamente. Questo affronta direttamente il peso della manutenzione che gli utenti nei forum citano costantemente come principale frustrazione. Se ti sei mai svegliato con un messaggio Slack che dice “lo scraper sta restituendo di nuovo righe vuote”, sai quanto vale non doverlo sistemare.
Estrarre Indeed senza scrivere Python: l’alternativa no-code
Ogni guida concorrente dà per scontato che tu scriva codice Python. Ma i dati dei forum raccontano una storia diversa. Gli utenti dicono cose come "è semplicemente troppo difficile con bug ed errori continui" e alcuni suggeriscono di assumere qualcuno su Fiverr solo per ottenere i dati. Se ti riconosci in questo, questa sezione è la tua via d’uscita.
Come estrarre Indeed con Thunderbit (passo dopo passo)
Passo 1: Installa l’estensione Chrome di Thunderbit dal Chrome Web Store. È gratis per iniziare.
Passo 2: Vai in una pagina dei risultati di ricerca di Indeed nel tuo browser — per esempio, https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.
Passo 3: Fai clic sull’icona di Thunderbit nella barra degli strumenti del browser, poi su "Suggerisci campi con AI." L’AI di Thunderbit analizza la pagina e rileva automaticamente colonne come Titolo del lavoro, Azienda, Località, Stipendio, URL del lavoro e Data di pubblicazione. Puoi rivedere e modificare i campi suggeriti — rimuovere le colonne che non ti servono oppure aggiungerne di personalizzate descrivendo ciò che vuoi in linguaggio naturale.
Passo 4: Fai clic su "Estrai." Thunderbit estrae i dati dalla pagina e li mostra in una tabella strutturata. Dovresti vedere righe di annunci con i campi che hai configurato.
Arricchisci con lo scraping delle sottopagine
Dopo aver estratto la pagina elenco, fai clic su "Estrai sottopagine" per far visitare a Thunderbit ogni singola pagina di dettaglio del lavoro. Recupera descrizioni complete, qualifiche, benefit e link di candidatura — senza bisogno di configurazione aggiuntiva. È l’equivalente di scrivere un secondo scraper Python per visitare ogni URL /viewjob?jk=<jobkey>, ma basta un clic.
Gestione automatica della paginazione
Thunderbit gestisce automaticamente la paginazione basata sui clic di Indeed. Non serve costruire manualmente URL con offset o scrivere cicli di paginazione. Clicca tra le pagine e aggrega i risultati.
Esporta nei tuoi strumenti preferiti
Esporta i dati estratti in CSV, Excel, Google Sheets, Airtable o Notion — completamente gratis. Nessun bisogno di scrivere codice csv.writer() o pandas.to_csv().
Quando usare Python vs Thunderbit
| Scenario | Strumento migliore |
|---|---|
| Pipeline dati personalizzate, automazione pianificata via cron/Airflow | Python |
| Integrazione in una codebase più grande | Python |
| Logica di parsing altamente personalizzata | Python |
| Ricerca una tantum o analisi di mercato | Thunderbit |
| I membri del team non tecnici hanno bisogno dei dati | Thunderbit |
| Ottenere subito i dati senza debuggare i 403 | Thunderbit |
| Arricchimento delle sottopagine senza alcuna configurazione | Thunderbit |
Confronto dei tempi: configurazione Python + debugging anti-bot = ore o giorni (soprattutto la prima volta). Thunderbit = meno di 2 minuti per gli stessi dati. Non sto dicendo che Python sia sbagliato — sto dicendo che dipende da ciò di cui hai bisogno.
È legale estrarre Indeed? Quello che devi sapere
Nessuna delle guide meglio posizionate sull’estrazione di Indeed affronta la legalità, il che è sorprendente visto quanto spesso viene fuori la domanda "È legale estrarre Indeed?" nei forum. Questo non è un parere legale, ma ecco il quadro generale.
Termini di servizio di Indeed
I Termini di servizio di Indeed (indeed.com/legal) non contengono una clausola generale di “divieto di scraping”. L’unica proibizione esplicita sull’automazione è la sezione A.3.5, che vieta "l’uso di automazione, scripting o bot per automatizzare il processo Indeed Apply". È una limitazione circoscritta al flusso di candidatura, non alla lettura passiva degli annunci pubblici. Il principale strumento di enforcement di Indeed è tecnico — sfide Cloudflare, ban IP, fingerprinting del dispositivo — non il tribunale.
Precedenti legali rilevanti
Il caso statunitense più citato è hiQ Labs v. LinkedIn. La 9th Circuit ha stabilito nell’aprile 2022 che lo scraping di dati pubblicamente accessibili “probabilmente non viola il CFAA” (Computer Fraud and Abuse Act). Tuttavia, hiQ è stata poi ritenuta responsabile per violazione di contratto perché i suoi dipendenti avevano creato falsi profili LinkedIn e accettato i ToS.
Più di recente, Meta v. Bright Data (N.D. Cal., gennaio 2024) ha prodotto una decisione ancora più chiara. Il giudice Chen ha stabilito che i Termini di Facebook e Instagram “non vietano lo scraping, quando si è disconnessi, dei dati pubblici”. Il mese successivo Meta ha ritirato volontariamente le restanti richieste.
robots.txt di Indeed
Il robots.txt di Indeed vieta in modo ampio /jobs/ e /job/ per il User-agent: * predefinito, ma consente esplicitamente a Googlebot e Bingbot di accedere a /viewjob? — le singole pagine di dettaglio degli annunci. I crawler per l’addestramento dell’AI (GPTBot, CCBot, anthropic-ai) sono fortemente limitati. robots.txt non è giuridicamente vincolante negli USA, ma rispettarlo è una buona pratica ed è prova di buona fede.
Linee guida pratiche per uno scraping responsabile
- Estrai solo dati pubblicamente disponibili — non effettuare mai login, non creare account falsi
- Rispetta i limiti di frequenza: 1 richiesta ogni 3–6 secondi per IP, concorrenza a una cifra
- Non ripubblicare i dati estratti come se fossero una tua bacheca di lavoro
- Usa i dati per ricerche personali o interne, non per rivenderli commercialmente senza permesso
- Elimina o rendi hash i dati personali non necessari; imposta un tetto di conservazione per i dati vicini a informazioni personali
- Se operi su larga scala o nell’UE/UK, consulta un avvocato — gli obblighi di trasparenza dell’Articolo 14 del GDPR si applicano ai dati personali estratti
La scala del rischio: l’automazione della ricerca di lavoro personale è nella fascia bassa. La rivendita commerciale su larga scala dei dati di Indeed è nella fascia alta.
Conclusione e punti chiave
Estrarre Indeed con Python è fattibile, ma non è un progetto da weekend da impostare e dimenticare. La protezione Cloudflare di Indeed, i selettori che ruotano e le misure anti-bot aggressive significano che devi affrontare il tutto con gli strumenti giusti e le aspettative giuste.
Ecco cosa mi porto via da tutto questo:
- Indeed è la fonte più ricca di dati sul mercato del lavoro sul web — 350 milioni di visitatori mensili, 130 milioni di annunci — ma reagisce duramente contro gli scraper.
- L’estrazione del JSON nascosto (
window.mosaic.providerData) è l’approccio Python più resistente. Lo schema è stabile da anni, mentre i selettori CSS si rompono ogni mese. curl_cfficon impersonificazione del browser è il client HTTP predefinito del 2026 per i siti protetti da Cloudflare.requestsehttpxstandard vengono bloccati già per la sola impronta TLS.- Usa sempre header rotanti, ritardi casuali e proxy residenziali per evitare errori 403. I proxy datacenter sono quasi inutili contro Cloudflare Enterprise.
- Aggiungi controlli con assert così sai subito quando i selettori si rompono o quando ti viene servita una pagina di challenge invece dei dati di lavoro.
- Per chi non è tecnico o per chi vuole semplicemente risultati rapidi, Thunderbit offre un percorso no-code, basato su AI, che si adatta automaticamente ai cambiamenti del sito — niente proxy, niente debugging, niente manutenzione.
Se vuoi provare la strada no-code, Thunderbit offre un piano gratuito così puoi testarlo su Indeed senza alcun impegno. E se invece scegli la via Python, gli esempi di codice qui sopra sono un buon punto di partenza — ricordati solo di trattare la resilienza anti-bot come una priorità, non come un ripensamento.
Per approfondire altri approcci e strumenti di web scraping, consulta le nostre guide su come fare web scraping con Python, i migliori strumenti automatizzati per il web scraping e come fare web scraping senza essere bloccati. Puoi anche guardare tutorial sul canale YouTube di Thunderbit.
Prova Thunderbit per estrarre più velocemente i dati di Indeed Get Started Free
FAQ
Quali librerie Python sono migliori per estrarre Indeed?
Per le richieste HTTP, curl_cffi è la scelta più forte nel 2026 — imita le impronte TLS reali del browser, cosa essenziale per aggirare Cloudflare. httpx con HTTP/2 è un buon piano B per target meno protetti. Per il parsing HTML, BeautifulSoup4 con lxml resta lo standard. Per l’automazione del browser, Playwright (con playwright-stealth) o undetected-chromedriver funzionano, anche se entrambi sono sempre più rilevabili. L’approccio con regex sul JSON nascosto (window.mosaic.providerData) evita del tutto la necessità di un parsing pesante.
Perché continuo a ricevere errori 403 quando estraggo Indeed?
Indeed usa Cloudflare Bot Management, che controlla la tua impronta TLS (JA3/JA4), l’ordine degli header HTTP/2, i pattern delle richieste e il comportamento del browser. Se usi requests standard, la tua impronta TLS ti identifica subito come script Python — il 403 arriva prima ancora che i tuoi header vengano letti. Risolvi passando a curl_cffi con impersonificazione del browser, ruotando stringhe User-Agent realistiche, aggiungendo ritardi casuali (3–6 secondi) e usando proxy residenziali. Controlla anche il caso “200 con challenge Turnstile” — cerca i marker cf-turnstile nel corpo della risposta.
Posso estrarre Indeed senza programmare?
Sì. Strumenti come Thunderbit ti permettono di estrarre gli annunci di Indeed in pochi clic — installa l’estensione Chrome, vai su una pagina di ricerca Indeed, fai clic su "Suggerisci campi con AI" e poi su "Estrai". L’AI di Thunderbit rileva automaticamente campi come titolo del lavoro, azienda, località e stipendio. Gestisce paginazione, arricchimento delle sottopagine (descrizioni complete) e protezioni anti-bot in automatico. Esporta in CSV, Google Sheets, Airtable o Notion gratuitamente.
Quanto spesso Indeed cambia la sua struttura HTML?
Indeed ruota regolarmente i nomi delle classi CSS (ad esempio css-1m4cuuf, stringhe hash casuali) e ristruttura gli elementi DOM senza preavviso. I test A/B fanno sì che utenti diversi possano vedere layout diversi nello stesso momento. L’approccio con JSON nascosto (window.mosaic.providerData) è molto più stabile — lo schema è coerente almeno dal 2023. Quando devi usare selettori DOM, punta agli attributi data-testid e a data-jk (job key) invece che alle classi CSS.
È legale estrarre Indeed?
Lo scraping, quando si è disconnessi, di URL pubblicamente accessibili di Indeed è improbabile che generi responsabilità CFAA negli USA, sulla base della sentenza della 9th Circuit hiQ v. LinkedIn (2022) e della decisione Meta v. Bright Data (2024). I ToS di Indeed vietano specificamente l’automazione del processo Apply, non la lettura passiva degli annunci pubblici. Detto questo, estrai sempre in modo responsabile: non effettuare login, non creare account falsi, rispetta i limiti di frequenza, non ripubblicare i dati come se fossero una tua bacheca di lavoro e gestisci con attenzione eventuali dati personali (nomi di recruiter, email) ai sensi di GDPR/CCPA. Per operazioni commerciali su larga scala, consulta un avvocato.
Scopri di più
- Come fare web scraping senza essere bloccati in Python
- Web scraping in Python: evita i blocchi con l’uso intelligente dei proxy
- Come scrivere uno scraper web con Python: dall’inizio alla fine
- Come estrarre dati da un sito web usando Python in modo efficiente
- Come estrarre dati con Python: tutorial per principianti


