Ciò che rende davvero utile Crawlee è il modo in cui orchestra le scansioni, perché supporta sia l’analisi HTTP sia l’esecuzione nel browser. La stessa URL può quindi restituire risultati diversi a seconda del crawler scelto e della condizione di readiness.
Nella pagina pubblica Quotes to Scrape JS, CheerioCrawler ha trovato 0 quote target, mentre PlaywrightCrawler ne ha trovate 10 dopo aver atteso .quote. Il ciclo di vita della crawl è simile, ma non si trattava di un semplice cambio di classe in una riga: l’handler Cheerio usava $, mentre l’handler Playwright usava page, un wait esplicito ed estrazione lato browser.
Cos’è davvero Crawlee
Crawlee (il progetto apify/crawlee, versione 3.17.0) è una libreria di web scraping e automazione del browser per Node.js e TypeScript. Supporta crawling HTTP basato su Cheerio o JSDOM e crawling nel browser basato su Playwright o Puppeteer. Il progetto è distribuito con licenza Apache-2.0; verifica le note e gli obblighi di attribuzione prima di redistribuirlo.
Il modello mentale importante è separare “scaricare la pagina” dal “leggere la pagina”. Un percorso scarica l’HTML grezzo e non esegue JavaScript. L’altro avvia Chromium e può eseguire gli script della pagina, ma ha comunque bisogno di una condizione di readiness adeguata e può perdere contenuti protetti da interazione, caricati in lazy load, presenti nel shadow DOM, bloccati da API fallite o filtrati da sistemi anti-bot. Crawlee espone concetti di lifecycle coerenti tra questi percorsi, non primitive DOM intercambiabili.
Questo è l’aspetto che vale la pena capire prima di scrivere anche solo un selettore, perché la scelta tra i due motori decide se il tuo scraper restituisce dati oppure nulla su un determinato sito.
Funzionalità chiave: due motori, una sola API

CheerioCrawler scarica l’HTML e lo analizza con Cheerio; PlaywrightCrawler controlla Chromium e può acquisire screenshot. Entrambi usano un requestHandler, espongono run() e condividono concetti di crawling come code e scoperta dei link. I loro contesti di handler però differiscono: nel test, il percorso Cheerio estraeva con $, mentre il percorso browser usava page, waitForSelector e $$eval. La struttura di code e lifecycle può restare familiare, ma il codice di estrazione potrebbe richiedere un adattatore o una riscrittura.
Sotto questa superficie, Crawlee offre tutta la componentistica necessaria a una crawl reale. Un RequestQueue gestisce il fronte degli URL da visitare, elimina i duplicati e tiene traccia di ciò che è già stato fatto. enqueueLinks scopre e accoda nuovi URL (con filtro per selettore e hostname) così che una crawl possa espandersi da sola. Un Dataset raccoglie i record estratti per l’esportazione. Per impostazione predefinita, Crawlee persiste tutto questo in una directory locale storage/ su disco — comoda per riprendere da dove si era interrotto, e un po’ fastidiosa la prima volta che trovi una cartella storage/ non richiesta nel progetto (nel mio harness l’ho reindirizzata a una cartella temporanea e ho disattivato la persistenza per tenere pulito il test).
I singoli pezzi, presi da soli, non sono eccezionali. Il punto è che sono condivisi tra entrambi i motori, quindi coda, scoperta dei link e dataset funzionano allo stesso modo sia via HTTP sia tramite browser. Impari una sola API e ottieni due strategie di acquisizione.
Setup: il browser installato separatamente
Nell’installazione testata, dopo aver installato i pacchetti non era presente un eseguibile Chromium.

npm install crawlee playwright si è installato senza problemi: 85 pacchetti, 0 vulnerabilità, nessun intoppo. Se ti fermi qui e avvii un CheerioCrawler, tutto funziona, perché il crawling HTTP non richiede un browser.
In questo ambiente, Chromium ha dovuto essere installato separatamente con npx playwright install chromium; senza quel passaggio, PlaywrightCrawler non riusciva ad avviarsi. Il payload browser osservato era di circa 82 MiB, ma gli appunti originali non indicano se si trattasse di dimensione di trasferimento o su disco. Si tratta di un’osservazione specifica della macchina, non di una proprietà fissa del prodotto. I percorsi della documentazione e il comportamento dei pacchetti possono cambiare, quindi questo articolo non afferma che l’omissione sia universale o permanentemente non documentata.
Pianifica il setup testato in due passaggi: installa i pacchetti Node, poi installa il browser usato dal percorso Playwright. Verifica di nuovo le istruzioni aggiornate di setup di Crawlee e Playwright in base alle versioni e alla piattaforma che distribuisci.
Prova pratica: la stessa pagina, due risposte molto diverse

Il test principale ha inviato lo stesso fixture renderizzato in JavaScript a entrambi i crawler. URL e campi target erano condivisi; non lo erano le primitive di estrazione.
Nel fixture locale, CheerioCrawler ha restituito 0 card target perché non erano presenti nell’HTML grezzo. PlaywrightCrawler ha atteso #dynamic-products article.product-card, poi ha restituito tutte le 8 card previste e ha catturato uno screenshot. Questo risultato conferma la completezza a livello di fixture per i campi selezionati dopo quel wait; non significa che un browser veda ogni possibile stato della pagina. I file grezzi e lo screenshot si trovano nel repository del benchmark.

Nella pagina pubblica Quotes to Scrape JS, CheerioCrawler ha trovato 0 quote target, mentre PlaywrightCrawler ha atteso .quote prima di estrarre 10 elementi. Questo conferma la stessa barriera HTTP-vs-browser su un target pubblico, mentre classe del crawler, contesto dell’handler, condizione di wait e primitiva di estrazione differiscono tra i due rami.
La conclusione utile è più limitata: verifica i campi richiesti dopo il percorso HTTP e passa a un browser crawler quando la risposta grezza non li contiene. L’handler browser deve inoltre attendere una condizione legata a quei campi.
Il percorso HTTP ha prodotto tutti i record attesi sui fixture statici controllati di catalogo e articoli, ha decodificato tutti gli otto elementi attesi da una risposta JSON diretta, ha visitato un grafo limitato di 11 pagine e ha instradato una risposta 500 a failedRequestHandler. Si tratta di verifiche separate delle capacità, non di un unico punteggio di accuratezza. Contro la pagina pubblica Books to Scrape, il selettore configurato ha restituito 20 prodotti come smoke test.
| Test | Motore | Risultato |
|---|---|---|
| Estrazione statica: catalogo + paginazione | CheerioCrawler | 12/12 prodotti attesi |
| Estrazione articoli | CheerioCrawler | titolo + 3/3 paragrafi |
| Trasporto: risposta JSON diretta | CheerioCrawler | 8/8 prodotti attesi |
| Traversal: grafo di link interni | CheerioCrawler | 11 pagine, profondità {0:1, 1:3, 2:7} |
| Instradamento errori: HTTP 500 | CheerioCrawler | stato inviato al failure handler |
| Rendering: fixture locale | CheerioCrawler | 0 card target nell’HTML grezzo |
| Rendering: fixture locale | PlaywrightCrawler | 8/8 dopo wait sul selettore target |
| Rendering: Quotes JS | CheerioCrawler | 0 quote target nell’HTML grezzo |
| Rendering: Quotes JS | PlaywrightCrawler | 10 dopo wait sul selettore target |
Tempi completi e numeri per singolo test sono disponibili in results/crawlee-test-summary.json.
Ora i caveat, detti con onestà, perché una sola macchina e una sola esecuzione hanno dei limiti e non farò finta del contrario. Questi sono tempi, non benchmark: una macchina, una sola esecuzione per test, quindi considera il costo per pagina più alto del browser come “sensibilmente più lento dei run Cheerio sotto il secondo”, non come un numero pubblicato. E ci sono molte cose che non ho testato in questa passata: rotazione proxy, pool di sessioni, esecuzioni su larga scala da centinaia o migliaia di pagine, persistenza e ripresa di RequestQueue dopo un crash, il motore Puppeteer e l’ergonomia di export di Dataset/KeyValueStore (qui ho scritto gli export manualmente). Posso confermare la storia dei due motori e l’accuratezza a livello di fixture. Non posso confermare la scalabilità o il comportamento anti-blocco, quindi non lo farò.
Cosa è condiviso e cosa deve cambiare

La superficie comune è l’orchestrazione della crawl. Entrambe le classi di crawler accettano un requestHandler ed espongono run(). Code, metadati delle richieste, scoperta dei link, hook di errore e concetti di storage possono essere organizzati in modo coerente attorno a entrambi i percorsi di esecuzione. Questo riduce la quantità di infrastruttura che un team deve reimparare quando un target richiede un browser.
La superficie di accesso alla pagina non è comune. Un handler di CheerioCrawler riceve accessi orientati a Cheerio come $ e può lavorare sui body della risposta senza browser. L’handler di PlaywrightCrawler testato riceve page; attende un selettore ed esegue query sul DOM del browser. Anche quando entrambi gli handler producono lo stesso schema di record, ci arrivano tramite API diverse. Un adattatore riutilizzabile potrebbe nascondere parte di questa differenza, ma questo harness non ne ha implementato né dimostrato uno.
Questa distinzione conta per le stime. Cambiare classe di crawler può lasciare intatti queue, dataset e policy sugli URL, ma selettori, controlli di readiness, screenshot, passaggi di interazione e gestione degli errori possono comunque cambiare. L’articolo quindi considera “plumbing di crawling condiviso” come il vantaggio verificato e respinge l’idea di una “migrazione in una riga” come promessa non supportata.
Un flusso pratico per scegliere il motore
Usa prima il percorso HTTP quando l’HTML restituito o una risposta JSON diretta contengono i campi richiesti. Definisci un contratto di completezza — chiavi obbligatorie, numero minimo di elementi o un selettore target — e fallisci in modo esplicito quando non è rispettato. Un array vuoto non è la prova che la pagina non abbia dati; nei due casi JavaScript qui presenti significava che la rappresentazione selezionata non conteneva gli elementi target.
| Condizione target | Parti con | Passa a un browser quando |
|---|---|---|
| I campi richiesti sono nell’HTML restituito | CheerioCrawler | Mancano selettori o campi richiesti |
| Una risposta JSON riproducibile contiene i dati | CheerioCrawler | La richiesta dipende da stato disponibile solo nel browser |
| La pagina inserisce gli elementi target dopo l’esecuzione | PlaywrightCrawler | Non applicabile; definisci un controllo di readiness specifico per il target |
| Il mix di target è sconosciuto | Prima HTTP con validazione di completezza | La validazione fallisce con un risultato tipizzato “rappresentazione incompleta” |
Trasforma quel fallimento tipizzato in un handler browser quando l’esecuzione è necessaria. In questo harness la pagina locale attendeva #dynamic-products article.product-card, mentre la pagina pubblica delle quote attendeva .quote. Queste condizioni fanno parte del contratto di estrazione. Un generico evento di load non dimostrerebbe che i dati dell’applicazione sono arrivati, e il test non supporta una regola di wait universale.
Dopo l’escalation, mantieni stabile lo schema di output anche se le primitive DOM cambiano. Registra quale motore ha prodotto il risultato, quale condizione di readiness è passata e se la validazione dei campi richiesti è riuscita. Così il fallback da HTTP a browser resta osservabile invece di trasformare silenziosamente i campi mancanti in record accettati.
Infine, considera l’installazione del browser e il suo costo operativo come input di deployment. L’osservazione dei circa 82 MiB è utile solo come ordine di grandezza locale; misura il build esatto del browser, la piattaforma, il comportamento della cache e l’impatto dell’immagine nel tuo ambiente. Rotazione proxy, sessioni, persistenza, recupero dopo crash e concorrenza sostenuta richiedono ancora test dedicati prima che questo fixture possa orientare una scelta su scala produzione.
Pro e contro
Pro:
- I crawler HTTP e browser condividono concetti di lifecycle pur esponendo contesti di estrazione specifici per motore.
- Estrazione HTTP corretta al 100% su cataloghi statici, articoli e API JSON.
- Plumbing condiviso tra i due motori:
RequestQueue,enqueueLinkscon controllo della profondità,Dataset. - Il percorso browser ha eseguito gli script del fixture e recuperato tutti gli elementi target attesi nei due test renderizzati in JavaScript.
- Gestione pulita degli errori: l’HTTP 500 è emerso senza far crashare il processo.
- Licenza Apache-2.0; chi redistribuisce dovrebbe verificare gli obblighi di note e attribuzione.
Contro:
- Nell’ambiente testato il motore browser richiedeva un’installazione separata di Chromium; senza di essa
PlaywrightCrawlernon si avviava. - Il percorso HTTP non può esporre elementi target assenti nell’HTML grezzo; senza validazione di completezza questo può sembrare un risultato vuoto ma valido.
- Il percorso browser comporta un binario aggiuntivo e un costo locale per pagina più alto in questa esecuzione; dimensioni e tempi variano in base a build e piattaforma.
- I run predefiniti lasciano una directory
storage/su disco. - Solo Node/TypeScript — inutile se il tuo stack è Python.
A chi è adatto — e chi dovrebbe evitarlo
Crawlee è adatto a team Node o TypeScript che hanno bisogno sia di crawling HTTP sia di crawling nel browser con concetti condivisi di coda e lifecycle. Un flusso pratico consiste nel provare prima il crawler HTTP, validare i campi richiesti e poi passare un fallimento tipizzato di completezza a un handler browser con una condizione di readiness specifica per il target. Il codice di accesso DOM dell’handler è specifico del motore anche quando plumbing di coda e link discovery è condiviso.
Rivedi le aspettative, oppure guarda altrove, se lavori in un team Python (Crawlee è per Node/TS — esiste un porting Python separato, ma questo pacchetto ha testato la libreria Node), se tutti i tuoi target sono statici e preferisci uno scraper HTTP più leggero e monouso, oppure se ti servono comportamenti provati su scala — rotazione proxy, pool di sessioni, ripresa dopo crash — che questa prova pratica non ha coperto. E se scegli PlaywrightCrawler, installa prima Chromium, altrimenti non partirà proprio.
Alternative, incluso il ruolo di Thunderbit
Crawlee è software open source che esegui e mantieni da solo. Non ha costi per chiamata imposti da un vendor, ma compute del browser, banda, proxy, storage, osservabilità e sviluppo restano costi operativi. Decidi tu il crawler, il binario del browser, lo stato dello storage e la logica di readiness.
Recensione correlata: scrapy-playwright review.
Un servizio di estrazione gestito sposta su un vendor la responsabilità di acquisizione e definizione dello schema. Noi costruiamo Thunderbit, ma non l’abbiamo eseguito su questi fixture, quindi questo articolo non supporta alcun confronto su qualità, latenza, parità di funzionalità o costi. La scelta rilevante è se il tuo team preferisce il controllo in-process di Crawlee oppure un confine di servizio per chiamata.
Recensioni comparative correlate: il confronto completo degli scraper open source, Playwright vs Puppeteer sulle stesse pagine e la recensione di Scrapy senza request replay del browser.
Prova Thunderbit per l’estrazione di dati web
Verdetto
Crawlee è un’ottima scelta per team Node o TypeScript che vogliono un’orchestrazione condivisa del crawling tra HTTP ed esecuzione nel browser. Gli handler testati non erano intercambiabili: passando a Playwright servivano page, un wait sul selettore target ed estrazione lato browser. Proxy, sessioni, persistenza, ripresa e comportamento su larga scala restano questioni aperte.
Prova Thunderbit per l’estrazione di dati web Get Started Free
FAQ
Qual è davvero la differenza tra i due crawler di Crawlee?
CheerioCrawler scarica l’HTML via HTTP e non esegue JavaScript. PlaywrightCrawler controlla Chromium e può eseguire gli script della pagina e catturare screenshot, con un costo locale per pagina più alto. Condividono i concetti di lifecycle, ma non i contesti dell’handler: in questo harness il percorso HTTP usava $, mentre il percorso Playwright usava page, un wait sul selettore target e valutazione lato browser.
Perché PlaywrightCrawler non parte dopo aver installato Crawlee?
Nell’ambiente testato, l’installazione del pacchetto non forniva un eseguibile browser. L’installazione di Chromium con npx playwright install chromium ha risolto il problema di avvio. Il payload osservato era di circa 82 MiB, ma la misurazione originale non specificava se si trattasse di trasferimento o dimensione su disco, quindi conviene rimisurarlo per la tua piattaforma e build.
CheerioCrawler può estrarre pagine renderizzate in JavaScript?
Non può eseguire il JavaScript della pagina. Può però richiedere un endpoint JSON accessibile usato dal client, come mostra il fixture con risposta diretta. Quando i dati richiesti esistono solo dopo l’esecuzione nel browser, usa un browser crawler e una condizione di readiness collegata a quei campi.
Crawlee è accurato per l’estrazione statica normale? Sui fixture controllati, gli handler hanno prodotto 12/12 prodotti catalogo attesi, 3/3 paragrafi attesi negli articoli e 8/8 elementi attesi dalla JSON diretta. Si tratta di controlli di completezza del fixture, non di un punteggio generale di accuratezza su siti non testati.
Crawlee è gratuito per uso commerciale? È pubblicato sotto licenza Apache-2.0. Verifica la licenza corrente nel repository e controlla gli obblighi di note e attribuzione per la tua distribuzione.
Prima di adottarlo in produzione, testa gli aspetti che questo fixture lascia aperti: concorrenza ripetuta su pagine rappresentative, comportamento di proxy e sessioni, recupero persistente della coda dopo un’interruzione, cleanup del processo browser ed export del dataset in caso di errore. Conserva con quei risultati anche la versione del browser risolta e il percorso di installazione. Le due classi di crawler riducono la divergenza nell’orchestrazione, ma non eliminano la necessità di controlli di readiness specifici per il motore, budget di risorse e gestione operativa degli errori.


