Su una macchina, cheerio in Node ha analizzato ed estratto titoli e href da un documento HTML sintetico da 10 MB in 2.927,89 ms. selectolax, eseguito tramite CPython con un parser supportato in C, ha completato la stessa estrazione dei campi in 158 ms. I titoli ordinati e gli hash degli href coincidevano. Si tratta di un confronto end-to-end dello stack tra runtime, non di un giudizio isolato sul solo algoritmo del parser.
Su una pagina da 10 KB il divario è 2×, e nessuno se ne accorgerebbe. La vera domanda è dove si collocano le tue pagine su quella curva.
Che cos'è cheerio
cheerio è il parser HTML con sintassi jQuery per Node, ed è la risposta predefinita in questo ecosistema per una buona ragione: 30.449 stelle su GitHub, licenza MIT e un push al repository il giorno prima del test. Versione testata: 1.2.0.
Riferimento ufficiale: introduzione ufficiale di Cheerio.
import * as cheerio from "cheerio";
const $ = cheerio.load(html);
const titles = $("h3.title").map((_, e) => $(e).text()).get();
const hrefs = $("a").map((_, e) => $(e).attr("href")).get();
Se hai già usato jQuery, conosci già l'API. Ed è proprio questa familiarità una delle principali ragioni del suo successo nel proprio ecosistema.
Sotto il cofano non c'è un solo parser, ma una pila: htmlparser2 e parse5 per il parsing, domhandler e domutils per l'albero DOM, cheerio-select per i selettori, oltre a undici, encoding-sniffer e altri — undici dipendenze dirette, che diventano 22 pacchetti di primo livello e 9,0 MiB su disco. Questi pacchetti forniscono funzionalità di parsing e gestione della codifica, ma contribuiscono anche al peso della dipendenza. Questa analisi ha verificato l'output su input malformati, ma non la correttezza della codifica né ha isolato uno dei due backend del parser.
La misurazione e perché è affidabile
La base di ricerca aveva già un benchmark sui parser: cinque dimensioni di pagina da 1 KB a 10 MB, 50 iterazioni, tre esecuzioni indipendenti e — la parte davvero importante — un controllo di parità che calcola l'hash del contenuto estratto, titoli ordinati più href ordinati, rispetto a un parser di riferimento. Un parser che salta silenziosamente del lavoro non può pubblicare un tempo veloce.
Aggiungere cheerio ha richiesto due verifiche prima di considerare valido qualsiasi numero.
Il riferimento era ancora nello stesso punto? selectolax è stato rieseguito nella stessa sessione sugli stessi fixture. Il suo hash del contenuto si è riprodotto su 5 su 5 delle dimensioni, e il suo p50 è rimasto tra 0,989× e 1,079× rispetto al valore pubblicato. Quindi questa è la stessa macchina che aveva prodotto la tabella originale.
cheerio ha prodotto gli stessi campi valutati? Il suo hash del contenuto — calcolato in Node con la stessa regola, SHA-256 su titoli ordinati e href ordinati — ha coinciso con il riferimento su 5 su 5 dimensioni. Questo dimostra la parità per quei campi ordinati su questi fixture, non la forma del DOM, l'ordine del documento, gli attributi, la normalizzazione del testo o il recupero dagli errori.
Solo a questo punto i tempi hanno un significato.
| Dimensione pagina | selectolax | lxml | PyQuery | cheerio (Node) | cheerio vs selectolax |
|---|---|---|---|---|---|
| 1 KB | 0,0286 ms | 0,0508 | 0,0456 | 0,1147 ms | 4,0× |
| 10 KB | 0,1725 ms | 0,1802 | 0,1728 | 0,3490 ms | 2,0× |
| 100 KB | 1,4855 ms | 1,4145 | 1,4093 | 3,8399 ms | 2,6× |
| 1 MB | 14,97 ms | 15,03 | 14,96 | 59,37 ms | 4,0× |
| 10 MB | 158,10 ms | 165,25 | 162,86 | 2.927,89 ms | 18,5× |
p50 in millisecondi, mediana di tre esecuzioni. parser-bench.json. I tre parser Python sono stati eseguiti in un solo processo; cheerio è stato eseguito in Node 22, che rappresenta anche un confine di runtime oltre che di libreria — vedi sotto.
Leggere quella tabella con onestÃ

La riga da 1 KB è rumore. Tra i tre parser Python, a quella dimensione la differenza è del 77,6% e le singole esecuzioni si sovrappongono ampiamente — selectolax è andato da 0,0267 a 0,0404 ms nelle tre run. A 28 microsecondi, contano più la risoluzione del timer e la schedulazione che altro. Non classificherei nulla a 1 KB, cheerio incluso.
La parte centrale della tabella non dice nulla di speciale. Tra 10 KB e 1 MB il divario è tra 2× e 4×. Per uno scraper che processa poche centinaia di pagine, significa 45 millisecondi a pagina invece di 15, e non te ne accorgerai mai.
La riga da 10 MB non è rumore. Le tre run di cheerio sono state 2.839, 2.928 e 2.954 ms — strette e ben distinte dalle altre righe. Il risultato end-to-end a 10 MB si discosta nettamente dal pattern delle dimensioni inferiori. Cinque punti di misura non bastano per stabilire la complessità asintotica o per identificare quale livello — runtime, parser, selettore, allocazione o garbage collection — provochi il salto.
Si colloca nella fascia di BeautifulSoup. Il benchmark pubblicato ha misurato altri quattro parser sullo stesso fixture da 10 MB, e affiancare il 2.927,89 ms di cheerio a quei dati è la cosa più utile in questo articolo:
| Parser (10 MB) | p50 |
|---|---|
| selectolax (lexbor) | 159,93 ms |
| lxml | 172,93 ms |
| parsel | 231,85 ms |
| selectolax (modest) | 247,95 ms |
| BeautifulSoup + lxml | 2.261,56 ms |
| BeautifulSoup + html.parser | 2.788,75 ms |
| cheerio | 2.927,89 ms |
Le quattro righe Python sono i valori pubblicati in bench_parse.json; quello di cheerio proviene da questa esecuzione. Il parser di riferimento si è riprodotto tra 0,989× e 1,079× nelle due prove, quindi considera differenze inferiori a circa l'8% come rientranti in quell'incertezza — cheerio contro il backend html.parser di BeautifulSoup (5% di differenza) ci rientra, cheerio contro selectolax (18×) no.
BeautifulSoup è la libreria a cui si ricorre quando si vuole comodità e si accetta consapevolmente la lentezza — è quella che in ogni discussione Python sulle performance viene consigliato di sostituire. Su un documento da 10 MB, cheerio si trova in fondo a quella stessa fascia, non in quella dei parser supportati in C con cui viene spesso associato.
La questione della sostituzione lato Node resta aperta in questo articolo. Non sono state testate alternative Node più recenti, quindi questo risultato non può dire che cambiare libreria sia impossibile né liquidarle come meno mature. Mostra soltanto il percorso misurato di cheerio rispetto agli stack Python elencati.
È anche un confronto tra runtime, non solo tra librerie. I millisecondi di cheerio dipendono dal JIT e dal garbage collector di Node; gli altri dipendono da CPython che invoca parser supportati in C. L'hash del contenuto prova che il lavoro svolto è lo stesso, e entrambi i numeri riflettono ciò che sperimenta davvero uno sviluppatore che sceglie uno stack — ma nessuno dovrebbe leggerlo come "l'algoritmo di cheerio è 18× peggiore di quello di selectolax". È ciò che è successo, su questa macchina, nel runtime nativo di ciascuna libreria.
La realtà dell'installazione
| Libreria | Pacchetti | Disco | Licenza | Stelle | Ultimo push |
|---|---|---|---|---|---|
| cheerio | 22 (npm) | 9,0 MiB | MIT | 30.449 | 2026-08-11 |
| PyQuery | 3 (pip) | 20,1 MiB | BSD | 2.380 | 2026-07-27 |
Riferimento ufficiale: documentazione di configurazione di Cheerio.
metadata-snapshot.json, recuperato il giorno della stesura.
npm install cheerio ha richiesto meno di due secondi e ha scaricato 9,0 MiB. L'importazione a freddo ha richiesto 0,056 s in una conversione separata eseguita sulla stessa macchina.
Undici dipendenze dirette sono tante per un parser, ed è utile saperlo se fai audit dell'albero delle dipendenze: htmlparser2, parse5, parse5-htmlparser2-tree-adapter, parse5-parser-stream, domhandler, domutils, dom-serializer, cheerio-select, encoding-sniffer, undici e whatwg-mimetype. Sono incluse due implementazioni complete del parser, perché cheerio può usare una o l'altra a seconda di ciò che gli viene richiesto.
Trentamila stelle e un push il giorno prima del test sono segnali di manutenzione molto solidi in questa categoria.
Memoria e cosa fa all'HTML rotto
L'ingombro in memoria e il comportamento con input malformati influenzano deployment e gestione dei guasti, quindi qui vengono misurati separatamente.
Il contesto più ampio del test di stress è nel confronto su memoria e HTML malformato di dieci librerie.
Picco di memoria residente, via /usr/bin/time -l, un processo nuovo per ogni cella — il floor di import è il costo della libreria una volta caricata e inattiva, i picchi includono il documento.
| Libreria | Runtime | Floor import | Picco 226 KB | Picco 10 MB |
|---|---|---|---|---|
| html2text | python3.14 | 18,7 | 19,9 | 71,2 |
| pyquery | python3.14 | 30,3 | 33,9 | 172,5 |
| resiliparse | python3.14 | 20,5 | 25,1 | 225,1 |
| markdownify | python3.14 | 23,9 | 28,9 | 278,5 |
| goose3 | python3.14 | 44,1 | 52,4 | 398,5 |
| cheerio | node22 | 66,8 | 76,5 | 398,5 |
| justext | python3.14 | 30,3 | 36,6 | 431,2 |
| newspaper4k | python3.14 | 52,6 | 61,8 | 668,5 |
| trafilatura | python3.14 | 52,5 | 64,8 | 927,1 |
| turndown | node22 | 47,8 | 68,4 | 2947,1 |
memory-results.json. I baseline di Python e Node non sono direttamente confrontabili tra loro; il runtime è incluso in entrambi.
cheerio ha il floor di import più alto in questa tabella mista, con 66,8 MiB, includendo runtime Node e dipendenze. Il processo ha raggiunto un picco di 398,5 MiB sul fixture da 10 MB. Le altre righe comprendono parser, converter ed estrattori di articoli che svolgono compiti primari diversi, quindi usale come contesto sull'ingombro di processo e non come classifica tra pari. La riga di turndown nello stesso runtime è arrivata molto più in alto, ma svolge conversione e non il contratto di selezione titoli+href misurato per cheerio.
HTML rotto. Dodici documenti, ognuno con un solo difetto — tag non chiusi, elementi inline annidati male, attributi senza virgolette e con spazi, tag di chiusura fuori posto, assenza totale di <html>, attributi duplicati, un documento troncato a metà tag, entità errate, uno <script> non chiuso, una dichiarazione di charset falsa, un commento che contiene markup e 600 livelli di annidamento — più due controlli ben formati di dimensioni corrispondenti, perché "non ha restituito nulla" dice qualcosa sulla malformazione solo se la libreria non è silenziosa anche su un documento pulito della stessa dimensione.
cheerio ha generato un'eccezione in 0 su 14 casi e non ha restituito nulla in 0 casi, recuperando 11/22 sentinelle valutate nei fixture malformati (malformed-results.json). Per i parser, il valutatore verifica le sentinelle di heading e link su undici documenti malformati valutabili; non assegna punteggio alla sentinella del paragrafo, e il fixture con <script> non chiuso è escluso. Senza un baseline con lo stesso contratto in questa sezione, 11/22 non è una classifica di qualità . La conclusione supportata è che cheerio ha restituito un output non vuoto senza andare in errore su tutti e quattordici gli input malformati più i controlli, recuperando però metà dei marker valutati.
Pro e contro
A favore. Sintassi jQuery familiare. MIT. Un segnale di manutenzione con timestamp di 30.449 stelle e attività sul repository il giorno prima del test. Due backend di parsing e pacchetti legati alla codifica sono presenti, anche se il recupero dei backend e l'accuratezza della codifica non sono stati isolati qui. L'hash ordinato di titoli più href coincideva con il riferimento a ogni dimensione del fixture.
Contro. 18,5× più lento di selectolax su un documento da 10 MB, e 4× su 1 MB. Undici dipendenze dirette, incluse due implementazioni complete del parser. Solo Node. E nella documentazione non c'è nulla che suggerisca una dimensione oltre la quale smetta di essere la scelta ovvia.
Chi dovrebbe usarlo e chi no
Usa cheerio se lavori in Node, l'API in stile jQuery ti è utile e le pagine di riferimento assomigliano alle dimensioni testate fino a 1 MB. Un megabyte è il punto massimo testato prima dell'impennata netta a 10 MB; questo articolo non stabilisce il confine tra i due né afferma quanta parte del web si trovi sotto quel limite.
Fai un benchmark prima di usarlo per documenti HTML molto grandi, come report generati, dump di cataloghi o pagine con liste molto lunghe. Il comportamento con sitemap XML non è stato testato. Sul fixture HTML da 10 MB, 2,9 secondi per documento sono un costo concreto che si accumula rapidamente.
Se lavori in Python, questo confronto dice altro: selectolax, lxml e PyQuery risultano sostanzialmente pari da 10 KB in su (entro lo 0,5%–5,4%, con intervalli di run sovrapposti), quindi scegli in base all'API e non alla velocità . Il divario di cheerio rispetto a tutti e tre è il numero interessante, non le differenze tra loro.
Dove si inserisce un'API gestita
cheerio analizza HTML che hai già a disposizione. Non recupera pagine, non esegue JavaScript e non gestisce un livello anti-bot — e su molti target reali quella è la metà più difficile del lavoro.
Un servizio gestito di fetch/render/extraction, incluso il nostro Thunderbit, si colloca su un piano di responsabilità diverso. Thunderbit non è stato benchmarkato qui. La distinzione rilevante è tra analizzare HTML fornito con selettori e delegare acquisizione, rendering ed estrazione; questo articolo non offre un confronto omogeneo su qualità , latenza o costo.
In termini pratici: se hai già l'HTML e conosci i tuoi selettori, cheerio è gratuito e piacevole da usare. Se devi estrarre pagine su scala, o preferisci descrivere i dati invece del DOM, stai facendo un acquisto diverso.
Per un quadro più ampio del settore, il nostro rassegna delle API di web scraping copre le opzioni hosted e il pillar sugli scraper open source quelle self-hosted. Se l'output analizzato deve andare a un modello, convertire HTML in Markdown in Python spiega dove si perde fedeltà .
Prova Thunderbit per l'estrazione di dati dal Web
Dovresti usare cheerio?
Sì, in Node, quando conta l'aderenza dell'API e i documenti rappresentativi restano vicini all'intervallo testato tra piccolo e 1 MB.
La familiarità dell'API e gli attuali segnali di manutenzione sono criteri di scelta legittimi. Il benchmark non dimostra che esista una risposta di supporto specifica né che la distribuzione delle dimensioni delle pagine testate corrisponda a un corpus di produzione.
Il numero da tenere a mente è quello da 10 MB. Da qualche parte tra 1 MB e 10 MB, il costo di cheerio smette di seguire gli altri e inizia a moltiplicarsi — 4× diventa 18,5×. Se il tuo corpus contiene documenti di quelle dimensioni, fai un benchmark prima di impegnarti, perché la libreria non ti avviserà .
Prova Thunderbit per l'estrazione di dati dal Web Get Started Free
FAQ
È corretto confrontare cheerio con parser Python? È un confronto tra stack, non tra algoritmi. Tutti e quattro hanno prodotto titoli ordinati e href identici secondo la regola dell'hash su 5 su 5 dimensioni di pagina. Questo non prova l'equivalenza totale dei parser. I tempi di cheerio includono il comportamento del runtime Node, mentre gli altri includono CPython che invoca parser supportati in C; il confronto descrive quelle scelte end-to-end.
Perché la riga da 1 KB non è classificata? Perché a 28 microsecondi la misurazione è dominata dal rumore. Su tre run, i parser Python hanno mostrato uno spread del 77,6%, con esecuzioni che si sovrapponevano tra loro. Qualsiasi ordinamento a quella dimensione sarebbe un artefatto. Dalla riga da 10 KB in poi la lettura è abbastanza stabile.
Cosa provoca il salto a 10 MB? Questo test non lo dice. Quello che stabilisce è che il salto è reale e non rumore: le tre run di cheerio si sono fermate a 2.839, 2.928 e 2.954 ms, ben distanti da tutto il resto, mentre il divario a 1 MB era di 4×. Isolarne la causa vorrebbe dire profilare separatamente i backend del parser di cheerio, cosa che era fuori dallo scopo di questo lavoro.
Quante dipendenze ha davvero?
Undici dirette, 22 di primo livello dopo la risoluzione, 9,0 MiB su disco. Due di queste sono implementazioni complete del parser — htmlparser2 e parse5 — perché cheerio può usare l'una o l'altra. È il prezzo da pagare per gestire sia il parsing permissivo sia quello conforme alle specifiche, ed è utile saperlo quando si controllano gli alberi di dipendenze.
Cosa non è stato testato qui?
I test hanno coperto il picco di memoria del processo su un documento da 226 KB e uno da 10 MB, più un insieme di quattordici input malformati e di controllo in cui cheerio non ha generato eccezioni, ha restituito output non vuoto in tutti i casi e ha recuperato 11/22 sentinelle valutate. Non hanno coperto lo streaming via parse5-parser-stream, la correttezza della codifica, il recupero specifico del backend, il punto esatto del salto prestazionale tra 1 e 10 MB, il parsing XML o alternative Node più recenti.


