Dopo aver copiato e incollato per l’ennesima volta un titolo di lavoro da Indeed in un foglio di calcolo, ho iniziato a mettere in dubbio le mie scelte di carriera. Se hai mai provato a estrarre dati strutturati da Indeed in modo programmatico, sai già come finisce: l’errore 403 non è un bug, è proprio una funzione del sistema di difesa di Indeed.
Indeed è la più grande bacheca di offerte di lavoro al mondo, con circa , in qualunque momento e attività in oltre . Questo la rende una delle fonti più ricche di dati sul mercato del lavoro al mondo — ma anche una delle più difficili da estrarre. Lo scraper open source JobFunnel (migliaia di stelle su GitHub) è stato persino nel dicembre 2025, dopo anni di lotta persa contro i sistemi anti-bot. Parole sue: "Tutti gli utenti riescono a estrarre qualche offerta, ma vengono rapidamente bloccati dal captcha e lo scraping fallisce, senza restituire alcun job." Un altro contributor ha segnalato un CAPTCHA . Quindi sì: non è un target banale. In questa guida ti mostrerò tutti i metodi pratici per fare scraping di Indeed con Python, come sopravvivere davvero al muro dei 403 e, per chi preferisce evitare del tutto il debug, anche un’alternativa no-code con .
Cosa significa fare scraping di Indeed con Python?
Alla base, il web scraping è l’estrazione automatizzata di dati strutturati da pagine web. Quando parliamo di scraping di Indeed con Python, intendiamo scrivere uno script che visita le pagine dei risultati di ricerca e le schede delle offerte, legge l’HTML sottostante (o i dati incorporati) ed estrae campi come titolo, azienda, località, stipendio e descrizione in un formato utile: CSV, database, Google Sheet.
Le librerie Python più usate 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 incorporato, protetto da Cloudflare Bot Management. Questo significa che il tuo scraper deve gestire contenuti renderizzati via JavaScript, classi CSS che cambiano spesso e protezioni anti-bot aggressive — prima ancora di analizzare un solo titolo di lavoro.
Inoltre, nel 2026 non esiste alcuna API gratuita, ufficiale e read-only di Indeed. La vecchia Publisher Jobs API è stata dismessa intorno al 2020, e ciò che resta è disponibile solo lato employer (Job Sync, Sponsored Jobs). Quindi, oggi, le opzioni realistiche sono solo due: fare scraping oppure pagare un fornitore terzo di dati.
Perché fare scraping dei dati lavoro di Indeed?
Il caso d’uso è chiarissimo: navigare manualmente migliaia di annunci è poco pratico, e i dati dentro quelle inserzioni hanno un valore concreto.

| Caso d'uso | Chi ne beneficia | Esempio |
|---|---|---|
| Generazione di lead | Team sales e recruiting | Creare liste di aziende che stanno assumendo con i relativi contatti |
| Analisi del mercato del lavoro | Analisti, team HR | Individuare competenze di tendenza e benchmark salariali per area geografica |
| Competitive intelligence | Datori di lavoro, agenzie interinali | Monitorare i pattern di assunzione e le offerte salariali dei competitor |
| Automazione della ricerca di lavoro personale | Candidati | Aggregare le offerte che corrispondono ai propri criteri in più località |
| Dati di training per modelli ML | Data scientist | Costruire modelli di previsione salariale partendo da dati storici |
Le ricerche di Indeed Hiring Lab che i dati degli annunci seguono da vicino il BLS JOLTS e possono servire come proxy quasi in tempo reale delle condizioni del mercato del lavoro statunitense. I fondi hedge 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 salariali su Indeed stanno migliorando, ma restano incompleti. A metà 2025 circa includeva informazioni sullo stipendio, ma solo circa riportava una cifra esatta; il resto mostrava dei range. Qualsiasi analisi salariale basata su Indeed dovrebbe tenere conto di questa parzialità.
Scegliere il metodo giusto per fare scraping di Indeed con Python
Non esiste un solo modo “giusto” per estrarre dati da Indeed. La scelta migliore dipende dal tuo livello tecnico, dalla quantità di dati che ti serve e da quanta manutenzione sei disposto a reggere. Ho testato tutti e quattro gli approcci principali, e il confronto è questo:
| Criterio | BS4 + Requests | Selenium | JSON nascosto (window.mosaic) | No-code (Thunderbit) |
|---|---|---|---|---|
| Difficoltà | Principiante | Intermedio | Intermedio-avanzato | Nessuna (2 clic) |
| Velocità | Veloce | Lento (render del browser) | Veloce | Veloce (cloud scraping) |
| Contenuti renderizzati via JS | No | Sì | Sì (dati incorporati) | Sì |
| Resistenza anti-bot | Bassa | Media (rilevabile) | Medio-alta | Alta (gestita automaticamente) |
| Manutenzione quando cambia l'HTML | Alta (si rompono i selettori) | Alta | Media (struttura JSON più stabile) | Nessuna (l'AI si adatta) |
| Ideale per | Prototipi rapidi | Pagine dinamiche, contenuti dietro login | Dati strutturati in massa | Non sviluppatori, risultati rapidi |
Questa guida passa in rassegna ogni metodo. Se sei uno sviluppatore Python, ti conviene leggere le sezioni su BS4, JSON nascosto e Selenium. Se invece non programmi — o sei semplicemente stanco di fare debug sui 403 — salta pure alla sezione Thunderbit.
Prima di iniziare
- Difficoltà: da principiante a intermedio (sezioni Python); nessuna (sezione Thunderbit)
- Tempo richiesto: circa 20–60 minuti per configurare Python e ottenere il primo scraping; circa 2 minuti con Thunderbit
- Cosa ti serve: Python 3.9+, un editor di codice, il browser Chrome e, per il percorso no-code, la
Configurare l’ambiente Python per lo scraping di Indeed
Prima di scrivere qualsiasi codice, prepara l’ambiente.
Installare le librerie necessarie
Crea un ambiente virtuale e installa i pacchetti richiesti:
1python -m venv indeed_env
2source indeed_env/bin/activate # Su Windows: indeed_env\Scripts\activate
3# Per l'approccio HTTP + parsing
4pip install requests beautifulsoup4 lxml httpx
5# Per l'approccio con JSON nascosto (consigliato)
6pip install curl_cffi parsel tenacity
7# Per l'approccio di automazione browser
8pip install selenium
Alcune note:
curl_cffinel 2026 è la scelta di riferimento per lo scraping di siti protetti da Cloudflare. Imita le impronte TLS di un browser reale, cosa cherequestsehttpxda soli non possono fare. Torneremo su questo punto nella sezione anti-bot.- Selenium 4.6+ include Selenium Manager, quindi non devi più scaricare manualmente ChromeDriver: la gestione del binario del browser è automatica.
- Usa
lxmlcome backend parser per BeautifulSoup. È circa del parser standardhtml.parser.
Creare la struttura del progetto
Teniamola semplice:
1indeed_scraper/
2├── scraper.py
3├── requirements.txt
4└── output/
Tutti gli esempi di codice qui sotto si basano su scraper.py.
Come fare scraping di Indeed con Python usando BeautifulSoup
Questo è l’approccio più adatto ai principianti: usare requests per scaricare la pagina e BeautifulSoup per analizzare l’HTML. È il più rapido da avviare, ma anche il più fragile su Indeed.
Passo 1: costruire l’URL di ricerca di Indeed
Gli URL di ricerca di Indeed seguono uno schema prevedibile:
1https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
Per esempio, cercando “data analyst” ad Austin, TX, a partire dalla prima pagina:
1from urllib.parse import urlencode
2params = {
3 "q": "data analyst",
4 "l": "Austin, TX",
5 "start": 0,
6}
7url = f"https://www.indeed.com/jobs?{urlencode(params)}"
8print(url)
9# 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 di 1.000 risultati (start <= 990). Qualsiasi offset superiore a 990 restituisce silenziosamente la stessa pagina.
Passo 2: inviare una richiesta HTTP con header corretti
Indeed blocca subito le richieste con il classico user-agent di Python. Servono header realistici:
1import requests
2headers = {
3 "User-Agent": (
4 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
6 ),
7 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8 "Accept-Language": "en-US,en;q=0.9",
9 "Accept-Encoding": "gzip, deflate, br",
10 "Referer": "https://www.indeed.com/",
11}
12response = requests.get(url, headers=headers, timeout=30)
13print(response.status_code)
Se ricevi un 200, per il momento sei dentro. Se ricevi un 403, Cloudflare ti ha già fermato. Più avanti vedremo come aggirare il problema.
Passo 3: analizzare le inserzioni dall’HTML
Usa BeautifulSoup per selezionare gli elementi delle card di lavoro. Punta agli attributi data-testid, che sono più stabili dei nomi di classe CSS randomizzati di Indeed:
1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "lxml")
3cards = soup.find_all("div", attrs={"data-testid": "slider_item"})
4jobs = []
5for card in cards:
6 title_el = card.find("h2", class_="jobTitle")
7 title = title_el.get_text(strip=True) if title_el else None
8 company = card.find(attrs={"data-testid": "company-name"})
9 location = card.find(attrs={"data-testid": "text-location"})
10 link = title_el.find("a")["href"] if title_el and title_el.find("a") else None
11 jobs.append({
12 "title": title,
13 "company": company.get_text(strip=True) if company else None,
14 "location": location.get_text(strip=True) if location else None,
15 "url": f"https://www.indeed.com{link}" if link else None,
16 })
17print(f"Trovati {len(jobs)} job")
Passo 4: gestire la paginazione
Scorri le pagine incrementando il parametro start:
1import time, random
2all_jobs = []
3for page in range(0, 50, 10): # Prime 5 pagine
4 params["start"] = page
5 url = f"https://www.indeed.com/jobs?{urlencode(params)}"
6 response = requests.get(url, headers=headers, timeout=30)
7 # ... analisi come sopra ...
8 all_jobs.extend(jobs)
9 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 un che Cloudflare identifica subito come “non browser”. Inoltre non supporta HTTP/2, mentre Indeed lo serve. Molto probabilmente verrai bloccato dopo poche pagine. E i selettori CSS? Indeed cambia spesso classi come css-1m4cuuf e jobsearch-JobComponent-embeddedBody-1n0gh5s tale che ogni selettore basato su di esse è una bomba a orologeria.
Usa questo metodo per prototipi rapidi su una singola pagina. Per qualunque uso su scala, meglio il metodo con JSON nascosto.
Come fare scraping di Indeed con Python usando i dati JSON nascosti
Questo è il metodo che consiglio alla maggior parte degli sviluppatori Python. Invece di analizzare elementi HTML fragili, estrai dati strutturati da una variabile JavaScript incorporata nel sorgente della pagina di Indeed: window.mosaic.providerData["mosaic-provider-jobcards"].
Ogni campo che ti interessa — titolo, azienda, località, stipendio, job key, data di pubblicazione, indicazione remote — è già presente in questo blob JSON. Non serve eseguire JavaScript. Lo schema è , quindi è molto più robusto dei selettori DOM.
Passo 1: scaricare l’HTML della pagina
Usa curl_cffi al posto di requests: imita le impronte TLS di un browser reale, ed è fondamentale per passare oltre Cloudflare:
1from curl_cffi import requests as cffi_requests
2response = cffi_requests.get(
3 "https://www.indeed.com/jobs?q=python+developer&l=Remote&start=0",
4 impersonate="chrome124",
5 headers={
6 "Accept-Language": "en-US,en;q=0.9",
7 "Referer": "https://www.indeed.com/",
8 },
9 timeout=30,
10)
11print(response.status_code, len(response.text))
Perché curl_cffi? È un binding Python basato su curl-impersonate, che riproduce esattamente il ClientHello TLS, il frame HTTP/2 SETTINGS e l’ordine degli header di un browser reale. È l’unico client HTTP Python ancora attivamente mantenuto che riesce a superare in una sola chiamata. I target di impersonificazione supportati includono chrome120, chrome124, chrome131, Safari e varie versioni di Edge.
Passo 2: estrarre il JSON con una regex
Il blob JSON è incorporato in un tag <script>. Estraiamolo con una regex:
1import re, json
2MOSAIC_RE = re.compile(
3 r'window\.mosaic\.providerData\["mosaic-provider-jobcards"\]=(\{.+?\});',
4 re.DOTALL,
5)
6match = MOSAIC_RE.search(response.text)
7if match:
8 data = json.loads(match.group(1))
9 results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
10 print(f"Trovati {len(results)} job nel JSON nascosto")
11else:
12 print("JSON nascosto non trovato: possibile blocco o modifica della pagina")
Passo 3: estrarre i campi delle offerte dal JSON
Ogni elemento di results contiene più informazioni di quante ne vedi nella pagina:
1jobs = []
2for job in results:
3 jobs.append({
4 "jobkey": job["jobkey"],
5 "title": job["title"],
6 "company": job.get("company"),
7 "location": job.get("formattedLocation"),
8 "remote": job.get("remoteLocation"),
9 "salary": (job.get("salarySnippet") or {}).get("text"),
10 "posted": job.get("formattedRelativeTime"),
11 "job_type": job.get("jobTypes"),
12 "easy_apply": job.get("indeedApplyEnabled"),
13 "url": f"https://www.indeed.com/viewjob?jk={job['jobkey']}",
14 })
Il JSON spesso include anche stime salariali, attributi tassonomici (tag di competenze) e valutazioni dell’azienda che non sempre sono visibili nell’HTML renderizzato.
Passo 4: estrarre più pagine
Usa tierSummaries nel JSON per capire il numero totale di risultati, poi cicla:
1import time, random
2all_jobs = []
3for start in range(0, 50, 10): # Prime 5 pagine
4 url = f"https://www.indeed.com/jobs?q=python+developer&l=Remote&start={start}&sort=date"
5 response = cffi_requests.get(
6 url,
7 impersonate="chrome124",
8 headers={"Accept-Language": "en-US,en;q=0.9", "Referer": "https://www.indeed.com/"},
9 timeout=30,
10 )
11 match = MOSAIC_RE.search(response.text)
12 if match:
13 data = json.loads(match.group(1))
14 results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
15 all_jobs.extend([{
16 "jobkey": j["jobkey"],
17 "title": j["title"],
18 "company": j.get("company"),
19 "location": j.get("formattedLocation"),
20 "salary": (j.get("salarySnippet") or {}).get("text"),
21 "url": f"https://www.indeed.com/viewjob?jk={j['jobkey']}",
22 } for j in results])
23 time.sleep(random.uniform(3, 7))
24print(f"Totale: {len(all_jobs)} job estratti")
Perché il JSON nascosto è più robusto
La struttura window.mosaic.providerData cambia meno spesso dei nomi delle classi CSS. Ottieni dati puliti e strutturati senza dover analizzare HTML confuso. Detto questo, serve comunque una mitigazione anti-bot adeguata (header, ritardi, proxy) — ne parliamo tra poco.
Come fare scraping di Indeed con Python usando Selenium
Selenium è l’approccio basato sull’automazione del browser. È utile quando devi interagire con la pagina — ad esempio aprire i pannelli dei dettagli delle offerte, gestire contenuti dietro login o estrarre descrizioni caricate dinamicamente che non sono presenti nell’HTML iniziale.
Quando usare Selenium invece dei client HTTP
- Indeed carica alcuni contenuti in modo dinamico (descrizioni complete nella colonna laterale)
- Devi estrarre pagine che richiedono stato di sessione o login
- Fai scraping su piccola scala, dove la velocità non è critica
Procedura rapida
1from selenium import webdriver
2from selenium.webdriver.common.by import By
3from selenium.webdriver.chrome.options import Options
4import time
5options = Options()
6options.add_argument("--disable-blink-features=AutomationControlled")
7# options.add_argument("--headless=new") # Headless è più rilevabile: usalo con cautela
8driver = webdriver.Chrome(options=options)
9driver.get("https://www.indeed.com/jobs?q=data+engineer&l=New+York")
10time.sleep(3)
11cards = driver.find_elements(By.CSS_SELECTOR, "[data-testid='slider_item']")
12for card in cards:
13 try:
14 title = card.find_element(By.CSS_SELECTOR, "h2.jobTitle").text
15 company = card.find_element(By.CSS_SELECTOR, "[data-testid='company-name']").text
16 location = card.find_element(By.CSS_SELECTOR, "[data-testid='text-location']").text
17 print(f"{title} | {company} | {location}")
18 except Exception:
19 continue
20driver.quit()
Limiti
Selenium è lento: ogni pagina richiede il rendering completo del browser. Chrome in modalità headless è (Cloudflare controlla navigator.webdriver, le stringhe del vendor WebGL, il numero di plugin e altro). Anche undetected-chromedriver ritarda soltanto il rilevamento: non lo elimina in modo permanente. E come per BS4, i tuoi selettori si romperanno quando Indeed aggiorna l’interfaccia.
Per la maggior parte dei casi d’uso, il metodo con JSON nascosto offre gli stessi dati più velocemente e con meno manutenzione. Riserva Selenium agli edge case in cui hai davvero bisogno di un browser.
Come evitare gli errori 403 quando fai scraping di 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 — non DataDome, non PerimeterX. Gli header di risposta lo confermano: server: cloudflare, cf-ray e il cookie di bot management __cf_bm. Cloudflare ispeziona il tuo fingerprint TLS (JA3/JA4), l’ordine degli header HTTP/2, i pattern delle richieste e i segnali comportamentali del browser. Se uno di questi sembra non umano, ottieni un 403, un 429, un 503 o — il caso più subdolo — un 200 OK con una pagina di challenge Turnstile al posto dei dati veri.
Ruotare User-Agent e header delle richieste
Un singolo User-Agent statico è il modo più rapido per farsi bloccare. Ruota tra una serie di stringhe realistiche e aggiornate. Importante: i campi minor di Chrome sono dopo la User-Agent Reduction — non inventare versioni minor diverse da zero, altrimenti gli anti-bot se ne accorgono.
1import random
2USER_AGENTS = [
3 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
4 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
5 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
6 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
7 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
8 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.3800.97",
9 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
10 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
11 "(KHTML, like Gecko) Version/17.4 Safari/605.1.15",
12]
13headers = {
14 "User-Agent": random.choice(USER_AGENTS),
15 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
16 "Accept-Language": "en-US,en;q=0.9",
17 "Accept-Encoding": "gzip, deflate, br, zstd",
18 "Referer": "https://www.indeed.com/",
19 "Sec-Fetch-Dest": "document",
20 "Sec-Fetch-Mode": "navigate",
21 "Sec-Fetch-Site": "same-origin",
22}
Assicurati anche che i Client Hints sec-ch-ua corrispondano alla versione del tuo UA. Un sec-ch-ua: "Chrome";v="131" insieme a un User-Agent che dichiara Chrome 145 è un segnale di allarme immediato.
Inserire ritardi casuali tra le richieste
Intervalli fissi vengono rilevati dai sistemi di pattern detection. Usa un jitter casuale:
1import time, random
2# Tra una richiesta e l'altra
3time.sleep(random.uniform(3, 6))
4# In caso di retry dopo un blocco
5def backoff_sleep(attempt):
6 base = 4
7 sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
8 time.sleep(min(sleep_time, 60))
Il consenso pratico, secondo e , è di 3–6 secondi tra richieste per IP, con un limite rigido di circa 100 richieste per IP per sessione prima di ruotare.
Usare la rotazione dei proxy
Questo è il singolo fattore più importante per avere successo. I proxy datacenter da range AWS/GCP hanno un tasso di successo di circa 5–15% sui target protetti da Cloudflare Enterprise — praticamente inutilizzabili su Indeed. I proxy residential, insieme a un fingerprint TLS corretto, salgono all’80–95% di successo.
1PROXIES = [
2 "http://user:pass@us.residential.example:7777",
3 "http://user:pass@us.residential.example:7778",
4 "http://user:pass@us.residential.example:7779",
5]
6proxy = random.choice(PROXIES)
7response = cffi_requests.get(
8 url,
9 impersonate="chrome124",
10 headers=headers,
11 proxies={"https": proxy},
12 timeout=30,
13)
Nel 2026, il prezzo dei proxy residential è di circa , a seconda del provider e del livello di impegno. Per Indeed, conviene partire con un piccolo pool e poi scalare se serve.
Gestire in modo elegante gli status code 403, 429 e 503
Non limitarti a riprovare in loop. Ogni status code indica una situazione diversa:
1def fetch_with_retry(url, proxy_pool, max_retries=5):
2 for attempt in range(max_retries):
3 proxy = random.choice(proxy_pool)
4 headers["User-Agent"] = random.choice(USER_AGENTS)
5 try:
6 r = cffi_requests.get(
7 url,
8 impersonate=random.choice(["chrome124", "chrome120", "edge101"]),
9 headers=headers,
10 proxies={"https": proxy},
11 timeout=30,
12 )
13 # Controlla anche il caso subdolo del "200 con challenge"
14 if r.status_code == 200 and "cf-turnstile" not in r.text and "Just a moment" not in r.text:
15 return r
16 if r.status_code == 403:
17 print(f"403 — bloccato. Cambio proxy, tentativo {attempt + 1}")
18 elif r.status_code == 429:
19 print("429 — rate limit raggiunto. Rallento.")
20 elif r.status_code == 503:
21 print("503 — server sovraccarico o challenge JS.")
22 backoff_sleep(attempt)
23 except Exception as e:
24 print(f"Errore nella richiesta: {e}")
25 backoff_sleep(attempt)
26 raise RuntimeError(f"Fallito dopo {max_retries} tentativi: {url}")
Il caso del “200 con challenge” è il più insidioso. Controlla sempre il body della risposta alla ricerca di cf-turnstile o Just a moment prima di considerare valido un 200.
L’alternativa più semplice: lascia gestire l’anti-bot a Thunderbit
Per chi non vuole costruire e mantenere pool di proxy, rotazione di header e impersonificazione del fingerprint TLS, il cloud scraping di gestisce automaticamente CAPTCHA, rotazione dei proxy e protezioni anti-bot. Niente configurazione dei proxy, niente curl_cffi, niente librerie per risolvere CAPTCHA. È la strada con meno attrito quando ti servono solo i dati.
Perché il tuo scraper di Indeed continua a rompersi (e come risolvere)
Il muro dei 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 cambia in modo aggressivo i nomi delle classi CSS. La guida di Bright Data 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 differenti. E la struttura del DOM cambia senza preavviso.
La vicenda di JobFunnel è molto istruttiva. Un contributor ha scritto: "CaptchaBuster ha mitigato con successo il captcha, e il motivo per cui lo scraping continua a fallire [è] dovuto a selettori BeautifulSoup ormai obsoleti." Quindi lo scraper non era bloccato: stava semplicemente leggendo 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[] nel 2026. I selettori DOM si rompono ogni mese. L’estrazione JSON, se si rompe, lo fa al massimo una volta all’anno.
Strategia: usare gli attributi data invece dei nomi delle classi
Quando devi comunque interagire con il DOM, punta agli attributi funzionali:
| Selettore | Scopo |
|---|---|
[data-testid="slider_item"] | Contenitore di ciascuna card di lavoro |
[data-testid="job-title"] o h2.jobTitle > a | Link del titolo del job |
[data-testid="company-name"] | Nome del datore di lavoro |
[data-testid="text-location"] | Testo della località |
data-jk="<jobkey>" su ogni card | Il punto di aggancio più stabile in assoluto — invariato dal 2019 |
Aggiungere controlli di validazione per intercettare selettori obsoleti
Non lasciare mai che lo scraper giri in silenzio con zero risultati. Aggiungi un controllo dopo ogni fetch:
1results = parse_hidden_json(html)
2assert len(results) > 0, (
3 f"Indeed ha restituito un set vuoto a start={start} — "
4 "possibile blocco, CAPTCHA o deriva dei selettori. "
5 f"Primi 500 caratteri della risposta: {html[:500]}"
6)
Registra i primi 500–2000 caratteri della risposta grezza in caso di errore. In questo modo capisci subito se hai ricevuto una challenge Turnstile, una schermata di accesso o un cambio di schema. Esegui ogni giorno un smoke test in CI su una query fissa (per esempio q=python&l=remote) che verifichi che il numero di risultati sia diverso da zero.
L’alternativa AI: scraper che non si rompono
L’AI di Thunderbit legge ogni volta la struttura della pagina da zero — non dipende da selettori hardcoded o pattern regex. Quando Indeed cambia l’HTML, Thunderbit si adatta automaticamente. Questo risolve esattamente il problema di manutenzione che gli utenti citano di continuo come principale fonte di frustrazione. Se ti è mai capitato di ricevere un messaggio Slack alle 8 del mattino con scritto “lo scraper restituisce di nuovo righe vuote”, capisci il valore di non doverlo correggere tu.
Fare scraping di Indeed senza scrivere Python: l’alternativa no-code
Tutte le guide concorrenti danno per scontato che tu debba scrivere codice Python. Ma i dati raccontano un’altra storia. Gli utenti scrivono cose come “è semplicemente troppo difficile, tra bug ed errori continui” e alcuni arrivano a suggerire di pagare qualcuno su Fiverr solo per ottenere i dati. Se ti riconosci, questa sezione è la tua via d’uscita.
Come fare scraping di Indeed con Thunderbit (passo dopo passo)
Passo 1: Installa la dal Chrome Web Store. Puoi iniziare gratis.
Passo 2: Apri nel browser una pagina dei risultati di ricerca su Indeed — ad esempio https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.
Passo 3: Clicca l’icona di Thunderbit nella barra del browser, poi seleziona "AI Suggest Fields." L’AI di Thunderbit analizza la pagina e riconosce automaticamente colonne come Job Title, Company, Location, Salary, Job URL e Posted Date. Puoi rivedere e modificare i campi suggeriti — rimuovere quelli inutili o aggiungerne di personalizzati descrivendo in inglese semplice ciò che ti serve.
Passo 4: Clicca "Scrape." Thunderbit estrae i dati dalla pagina e li mostra in una tabella strutturata. Dovresti vedere righe di annunci con i campi configurati.
Arricchire i dati con lo scraping delle sottopagine
Dopo aver estratto la pagina elenco, clicca "Scrape Subpages" per far sì che Thunderbit visiti ciascuna pagina di dettaglio del job. Estrae descrizioni complete, requisiti, benefit e link per candidarsi — senza configurazioni aggiuntive. È l’equivalente di scrivere un secondo scraper Python per visitare ogni URL /viewjob?jk=<jobkey>, ma con un solo clic.
Gestire automaticamente la paginazione
Thunderbit gestisce in automatico la paginazione di Indeed basata sui clic. Non devi costruire manualmente gli URL con offset né scrivere loop di paginazione. Clicca attraverso le pagine e aggrega i risultati.
Esportare nei tuoi strumenti preferiti
Esporta i dati estratti in CSV, Excel, Google Sheets, Airtable o Notion — . Niente codice csv.writer() o pandas.to_csv() da scrivere.
Quando usare Python e quando Thunderbit
| Scenario | Strumento migliore |
|---|---|
| Pipeline dati personalizzate, automazione programmata con cron/Airflow | Python |
| Integrazione in una codebase più ampia | Python |
| Logica di parsing molto personalizzata | Python |
| Ricerca una tantum o analisi di mercato | Thunderbit |
| Il team non tecnico ha bisogno dei dati | Thunderbit |
| Ottenere i dati subito, senza debuggare i 403 | Thunderbit |
| Arricchimento delle sottopagine senza configurazione | Thunderbit |
In termini di tempo, la differenza è netta: configurazione Python + debug anti-bot = da ore a giorni, soprattutto la prima volta. Thunderbit = meno di 2 minuti per ottenere gli stessi dati. Non sto dicendo che Python sia sbagliato — sto dicendo che dipende da ciò che ti serve.
È legale fare scraping di Indeed? Cosa devi sapere
Nessuna delle guide più visibili sullo scraping di Indeed affronta davvero la questione legale, il che sorprende visto quanto spesso nei forum compare la domanda “È legale fare scraping di Indeed?”. Questo non è un parere legale, ma ecco il quadro generale.
I termini di servizio di Indeed
I ToS di Indeed () non contengono una clausola generica “no scraping”. L’unica proibizione esplicita sull’automazione è nella sezione A.3.5, che vieta "l'uso di qualsiasi automazione, scripting o bot per automatizzare il processo Indeed Apply". Il divieto è quindi limitato al flusso di candidatura, non alla lettura passiva di annunci pubblici. Il principale strumento di enforcement di Indeed è tecnico — challenge Cloudflare, ban IP, fingerprinting del dispositivo — non giudiziario.
Precedenti legali rilevanti
Il caso statunitense più citato è hiQ Labs v. LinkedIn. Nel 2022 la 9th Circuit che lo scraping di dati pubblicamente accessibili “probabilmente non viola” il CFAA (Computer Fraud and Abuse Act). Tuttavia, in seguito hiQ è stata ritenuta perché alcuni dipendenti avevano creato profili LinkedIn falsi 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 che i termini di Facebook e Instagram “non vietano lo scraping dei dati pubblici quando non si è autenticati”. Il mese successivo Meta ha ritirato volontariamente le altre pretese.
robots.txt di Indeed
Il di Indeed vieta in generale /jobs/ e /job/ per il User-agent: * predefinito, ma permette esplicitamente a Googlebot e Bingbot di accedere a /viewjob?, cioè alle singole pagine di dettaglio. I crawler usati per l’addestramento AI (GPTBot, CCBot, anthropic-ai) sono fortemente limitati. robots.txt non è vincolante legalmente negli USA, ma rispettarlo è una buona pratica ed è prova di buona fede.
Linee guida pratiche per uno scraping responsabile
- Estrai solo dati pubblicamente accessibili — mai fare login, mai creare account falsi
- Rispetta i rate limit: 1 richiesta ogni 3–6 secondi per IP, con concorrenza a una cifra
- Non ripubblicare i dati estratti come se fosse una tua job board
- Usa i dati per ricerca personale o interna, non per rivendita commerciale senza permesso
- Elimina o hash-a i dati personali non necessari; imposta un limite di conservazione per i dati potenzialmente personali
- Se operi su larga scala o in UE/Regno Unito, consulta un avvocato: gli obblighi di trasparenza dell’Articolo 14 del GDPR si applicano ai dati personali estratti
Il livello di rischio varia: l’automazione della ricerca di lavoro personale è in fondo alla scala; la rivendita commerciale su larga scala dei dati di Indeed è in cima.
Conclusione e punti chiave
Fare scraping di Indeed con Python si può fare, ma non è un progetto da weekend da impostare e dimenticare. La protezione Cloudflare di Indeed, i selettori che cambiano e le misure anti-bot aggressive significano che devi affrontare il problema con gli strumenti giusti e le aspettative giuste.
Ecco cosa mi porto via da tutto questo:
- Indeed è una delle fonti più ricche di dati sul mercato del lavoro sul web — 350 milioni di visite 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 cambiano ogni mese. curl_cfficon impersonificazione del browser è il client HTTP di riferimento nel 2026 per siti protetti da Cloudflare.requestsehttpxstandard vengono bloccati già dal fingerprint TLS.- Usa sempre header rotanti, ritardi casuali e proxy residential per evitare gli errori 403. I proxy datacenter sono quasi inutili contro Cloudflare Enterprise.
- Aggiungi controlli di validazione così capisci subito quando i selettori si rompono o quando ti viene servita una pagina challenge invece dei dati.
- Per utenti non tecnici o per chi vuole solo risultati rapidi, offre un percorso no-code, basato su AI, che si adatta automaticamente ai cambiamenti del sito — senza proxy, senza debug, senza manutenzione.
Se vuoi provare la strada no-code, per testarlo su Indeed senza impegno. Se invece scegli la strada Python, gli esempi di codice qui sopra sono un ottimo punto di partenza — ricordati solo di considerare la robustezza anti-bot come una priorità, non come un dettaglio secondario.
Per saperne di più su approcci e strumenti per il web scraping, consulta le nostre guide su , e . Puoi anche guardare i tutorial sul .
FAQ
Quali librerie Python sono migliori per fare scraping di Indeed?
Per le richieste HTTP, curl_cffi è la scelta più forte nel 2026: imita i fingerprint TLS di un browser reale, cosa essenziale per superare 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 il parsing pesante.
Perché continuo a ricevere errori 403 quando faccio scraping di Indeed?
Indeed usa Cloudflare Bot Management, che controlla il fingerprint TLS (JA3/JA4), l’ordine degli header HTTP/2, i pattern delle richieste e il comportamento del browser. Se usi requests puro, il fingerprint TLS ti identifica subito come script Python — il 403 arriva prima ancora che gli header vengano letti. Per risolvere, passa a curl_cffi con impersonificazione del browser, ruota User-Agent realistici, aggiungi ritardi casuali (3–6 secondi) e usa proxy residential. Verifica anche il caso “200 con challenge Turnstile”: cerca nei body della risposta i marker cf-turnstile.
Posso fare scraping di Indeed senza programmare?
Sì. Strumenti come ti permettono di estrarre gli annunci di Indeed in pochi clic: installi l’estensione Chrome, apri una pagina di ricerca di Indeed, clicchi "AI Suggest Fields" e poi "Scrape." L’AI di Thunderbit riconosce automaticamente campi come titolo, azienda, località e stipendio. Gestisce paginazione, arricchimento delle sottopagine (descrizioni complete) e protezioni anti-bot in automatico. Puoi esportare in CSV, Google Sheets, Airtable o Notion gratuitamente.
Quanto spesso cambia la struttura HTML di Indeed?
Indeed ruota regolarmente i nomi delle classi CSS (per esempio css-1m4cuuf, stringhe hash casuali) e ristruttura gli elementi del DOM senza preavviso. I test A/B fanno sì che utenti diversi vedano layout differenti nello stesso momento. L’approccio con JSON nascosto (window.mosaic.providerData) è molto più stabile: lo schema è rimasto coerente almeno dal 2023. Quando devi usare selettori DOM, punta agli attributi data-testid e data-jk (job key) invece che alle classi CSS.
È legale fare scraping di Indeed?
Lo scraping di URL pubblici di Indeed senza autenticazione difficilmente crea responsabilità CFAA negli USA, in base alla decisione 9th Circuit in hiQ v. LinkedIn (2022) e alla sentenza Meta v. Bright Data (2024). I ToS di Indeed vietano in modo specifico l’automazione del processo di candidatura, non la lettura passiva di annunci pubblici. Detto questo, fai sempre scraping in modo responsabile: non fare login, non creare account falsi, rispetta i rate limit, non ripubblicare i dati come se fossero una tua job board e gestisci con attenzione eventuali dati personali (nomi di recruiter, email) in base a GDPR/CCPA. Per operazioni su scala commerciale, consulta un avvocato.
Scopri di più
