Op één machine heeft cheerio in Node titels en hrefs uit een synthetisch HTML-document van 10 MB geparseerd en uitgelezen in 2.927,89 ms. selectolax, draaiend via CPython met een parser die op C is gebaseerd, rondde exact dezelfde veldextractie af in 158 ms. De gesorteerde titeltekst en href-hashes kwamen overeen. Dit is een end-to-end vergelijking van volledige stacks en runtimes, niet een losse beoordeling van één parser-algoritme.
Op een pagina van 10 KB is dat verschil 2×, en dat merkt niemand. De echte vraag is dus waar jouw pagina’s op die curve zitten.
Wat cheerio is
cheerio is de HTML-parser voor Node met jQuery-syntaxis, en dat is niet voor niets het standaardantwoord binnen dat ecosysteem: 30.449 GitHub-stars, MIT-licentie, en een commit naar de repository op de dag vóór mijn test. Geteste versie: 1.2.0.
Officiële referentie: de officiële introductie van 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();
Als je ooit met jQuery hebt gewerkt, ken je de API al. Die vertrouwdheid is een van de belangrijkste redenen waarom het in zijn ecosysteem zo goed aansloeg.
Onder de motorkap is het niet één parser, maar een hele stack: htmlparser2 en parse5 voor parsing, domhandler en domutils voor de boomstructuur, cheerio-select voor selectors, plus undici, encoding-sniffer en meer — elf directe dependencies, die samen uitkomen op 22 top-level pakketten en 9,0 MiB op schijf. Die pakketten leveren parsing- en encodingmogelijkheden, maar dragen ook bij aan de omvang van de afhankelijkheden. Deze review testte de output bij foutieve input, maar niet de juistheid van encoding en ook niet afzonderlijk beide parser-backends.
De meting, en waarom die betrouwbaar is
De onderzoeksbasis had al een parser-benchmark: vijf paginagroottes van 1 KB tot 10 MB, 50 iteraties, drie onafhankelijke runs, en — het belangrijkste onderdeel — een pariteitscontrole die de geëxtraheerde content hasht, gesorteerde titels plus gesorteerde hrefs, tegenover een referentieparser. Een parser die stiekem werk overslaat, kan geen snelle tijd neerzetten.
Cheerio toevoegen vereiste twee controles voordat een getal mocht meetellen.
Kwam de referentie weer uit op dezelfde plek? selectolax werd in dezelfde sessie opnieuw gedraaid op dezelfde fixtures. De content-hash reproduceerde op 5 van de 5 groottes, en de p50 lag tussen 0,989× en 1,079× van de gepubliceerde waarde. Dit is dus dezelfde machine die de oorspronkelijke tabel produceerde.
Leverde cheerio dezelfde gescoorde velden op? De content-hash — berekend in Node met exact dezelfde regel, SHA-256 over gesorteerde titeltekst en gesorteerde hrefs — kwam op 5 van de 5 groottes overeen met de referentie. Dat bewijst pariteit voor die gesorteerde velden op deze fixtures, maar niet voor DOM-structuur, documentvolgorde, attributen, tekstnormalisatie of foutafhandeling.
Pas daarna krijgen de timings betekenis.
| Paginaformaat | 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 milliseconden, mediaan van drie runs. parser-bench.json. De drie Python-parsers draaiden in één proces; cheerio draaide in Node 22, wat dus ook een runtimegrens is, niet alleen een bibliotheekgrens — zie hieronder.
Die tabel eerlijk lezen

De rij van 1 KB is ruis. Tussen de drie Python-parsers was de spreiding op die grootte 77,6%, en individuele runs overlapten sterk — selectolax liep uiteen van 0,0267 tot 0,0404 ms over zijn drie runs. Bij 28 microseconden domineren timerresolutie en scheduling. Ik zou op 1 KB niets rangschikken, inclusief cheerio.
Het midden van de tabel is onopvallend. 2× tot 4× op pagina’s tussen 10 KB en 1 MB. Voor een scraper die een paar honderd pagina’s verwerkt, betekent dat 45 milliseconden per pagina in plaats van 15, en dat ga je nooit merken.
De rij van 10 MB is geen ruis. cheerio’s drie runs kwamen uit op 2.839, 2.928 en 2.954 ms — strak bij elkaar en duidelijk los van de kleinere formaten. Het end-to-end resultaat op 10 MB wijkt scherp af van het patroon bij kleinere pagina’s. Vijf meetpunten zijn niet genoeg om asymptotische complexiteit vast te stellen of te bepalen welke laag de sprong veroorzaakt: runtime, parser, selector, allocatie of garbage collection.
Het valt in dezelfde band als BeautifulSoup. De gepubliceerde benchmark mat nog vier parsers op dezelfde 10 MB-fixture, en cheerio’s 2.927,89 ms naast die resultaten zetten is hier het nuttigste:
| 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 |
De vier Python-rijen komen uit de gepubliceerde cijfers van bench_parse.json; die van cheerio komt uit deze run. De referentieparser reproduceerde binnen 0,989×–1,079× tussen de twee runs, dus verschillen kleiner dan ongeveer 8% vallen binnen die onzekerheid — cheerio versus de html.parser-backend van BeautifulSoup (5% verschil) valt daarbinnen, cheerio versus selectolax (18×) niet.
BeautifulSoup is de bibliotheek waar mensen naar grijpen als ze gemak willen en bewust accepteren dat het traag is — precies degene die in elke Python-performance-discussie vervangen moet worden. Op een document van 10 MB zit cheerio helemaal onderaan in diezelfde band, niet in de band van de C-gestuurde parsers waarmee het vaak op één hoop wordt gegooid.
De vraag naar een Node-alternatief blijft in dit artikel open. Nieuwere Node-alternatieven zijn niet getest, dus dit resultaat zegt niet dat overstappen onmogelijk is of dat die alternatieven minder volwassen zijn. Het laat alleen het gemeten cheerio-pad zien tegenover de genoemde Python-stacks.
Het is zowel een runtimevergelijking als een bibliotheekvergelijking. cheerio’s milliseconden komen uit Node’s JIT en garbage collector; de andere uit CPython dat C-code aanroept. De content-hash bewijst dat hetzelfde werk is uitgevoerd, en beide cijfers zijn wat een ontwikkelaar in de praktijk ervaart bij het kiezen van een stack — maar niemand moet dit lezen als: "cheerio’s algoritme is 18× slechter dan dat van selectolax". Dit is wat er op deze machine gebeurde, in de native runtime van elke bibliotheek.
Hoe de setup eruitziet
| Bibliotheek | Pakketten | Schijf | Licentie | Stars | Laatste 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 |
Officiële referentie: documentatie voor Cheerio-configuratie.
metadata-snapshot.json, opgehaald op de dag van schrijven.
npm install cheerio duurde minder dan twee seconden en haalde 9,0 MiB binnen. Een koude import werd in een aparte converter-run op dezelfde machine gemeten op 0,056 s.
Elf directe dependencies zijn veel voor een parser, en het is goed om te weten als je je dependency tree controleert: htmlparser2, parse5, parse5-htmlparser2-tree-adapter, parse5-parser-stream, domhandler, domutils, dom-serializer, cheerio-select, encoding-sniffer, undici en whatwg-mimetype. Daar zitten twee complete parser-implementaties in, omdat cheerio afhankelijk van de situatie beide kan gebruiken.
Dertigduizend stars en een push op de dag vóór de test is in deze categorie zo gezond als onderhoudssignalen maar kunnen zijn.
Geheugen, en wat kapotte HTML daarmee doet
Geheugenverbruik en gedrag bij foutieve HTML zijn belangrijk voor deployment en foutafhandeling, dus beide worden hier apart gemeten.
De bredere stresstestcontext staat in de vergelijking van tien libraries op geheugen en foutieve HTML.
Piek resident memory, gemeten via /usr/bin/time -l, één vers proces per cel — de importvloer is wat de bibliotheek kost als hij geladen is en niets doet, de pieken bevatten ook het document.
| Bibliotheek | Runtime | Importvloer | Piek bij 226 KB | Piek bij 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. Python- en Node-baselines zijn onderling niet goed vergelijkbaar; de interpreter zit in beide inbegrepen.
cheerio heeft in deze gemengde context de hoogste importvloer met 66,8 MiB, inclusief Node-runtime en dependencies. Het proces piekte op 398,5 MiB bij de 10 MB-fixture. De andere rijen bevatten parsers, converters en article extractors die ander primair werk doen, dus gebruik ze als context voor procesfootprint en niet als directe prestatieranglijst. De turndown-rij in dezelfde runtime piekte veel hoger, maar die voert conversie uit in plaats van de titel-en-href-selector-taak die hier voor cheerio is gemeten.
Kapotte HTML. Twaalf documenten waarin telkens precies één ding kapot is: niet-afgesloten tags, verkeerd geneste inline-elementen, niet-gequote attributen met spaties, losse sluit-tags, helemaal geen <html>, dubbele attributen, een document dat halverwege een tag afbreekt, foute entiteiten, een niet-afgesloten <script>, een misleidende charset-declaratie, een comment met markup, en 600 niveaus nesting — plus twee netjes gevormde controles op overeenkomende groottes, omdat "het gaf niets terug" alleen iets zegt over malformedness als de bibliotheek ook op een schoon document van dezelfde grootte stil blijft.
cheerio gooide op 0 van 14 een fout en gaf op 0 gevallen niets terug, en herstelde 11/22 gescoorde ankers op de foutieve fixtures (malformed-results.json). Voor parsers controleert de scorer heading- en link-ankers over elf scorbare foutieve documenten; de paragrafen-sentinel wordt niet gescoord, en de fixture met een niet-afgesloten <script> is uitgesloten. Zonder baseline met hetzelfde contract is 11/22 geen kwaliteitsranglijst. De ondersteunde conclusie is dat cheerio op alle veertien foutieve-plus-control-inputs niet-leeg uitvoer gaf zonder een fout te gooien, terwijl het de helft van de gescoorde markers herstelde.
Voor- en nadelen
In zijn voordeel. Vertrouwde jQuery-syntaxis. MIT. Een onderhoudssignaal van 30.449 stars en repository-activiteit op de dag vóór de test. Twee parser-backends en encoding-gerelateerde pakketten zijn aanwezig, al zijn backend-recovery en encodingnauwkeurigheid hier niet apart getest. De gesorteerde hash van titel plus href kwam op elke fixturegrootte overeen met de referentie.
Tegen. 18,5× trager dan selectolax op een document van 10 MB, en 4× op 1 MB. Elf directe dependencies, waaronder twee complete parser-implementaties. Alleen Node. En in de documentatie staat niets dat suggereert bij welke grootte het niet langer de vanzelfsprekende keuze is.
Wie het wel moet gebruiken, en wie niet
Gebruik cheerio als je in Node werkt, de jQuery-achtige API belangrijk vindt, en je representatieve pagina’s lijken op de geteste groottes tot 1 MB. Eén megabyte is het grootste geteste punt vóór de scherpe sprong naar 10 MB; dit artikel stelt niet vast waar precies de grens ligt, en zegt ook niet hoeveel van het web daaronder valt.
Benchmark vóór gebruik als je met heel grote HTML-documenten werkt, zoals gegenereerde rapporten, catalogusdumpen of lange lijstpagina’s. Gedrag voor XML-sitemaps is niet getest. Op de HTML-fixture van 10 MB is 2,9 seconden per document een relevante kostenpost die zich opstapelt.
Als je in Python zit, zegt deze vergelijking iets anders: selectolax, lxml en PyQuery zijn vanaf 10 KB effectief aan elkaar gewaagd (binnen 0,5% tot 5,4%, met overlappende run-ranges), dus kies op API in plaats van op snelheid. Het interessante getal is cheerio’s achterstand op alle drie, niet de onderlinge verschillen tussen die drie.
Waar een managed API past
cheerio parseert HTML die je al hebt. Het haalt de pagina niet op, rendert geen JavaScript en handelt geen anti-botlaag af — en op veel echte targets is juist dát de moeilijkere helft van het werk.
Een beheerde fetch/render/extract-service, waaronder onze eigen Thunderbit, zit op een andere verantwoordelijkheidsgrens. Thunderbit is hier niet gebenchmarkt. Het relevante onderscheid is: HTML die je aanlevert parsen met selectors versus het uitbesteden van acquisitie, rendering en extractie; dit artikel biedt geen vergelijking op dezelfde meetlat voor kwaliteit, latency of kosten.
De eerlijke formulering: als jij de HTML al hebt en je selectors kent, is cheerio gratis en prettig in gebruik. Als je pagina’s op schaal ophaalt, of liever de data beschrijft dan de DOM, dan koop je iets anders.
Voor het bredere veld bespreekt onze overzichtspagina van webscraping-API’s gehoste opties en de open-source scraper-pijler de self-hosted varianten. Als de geparseerde output uiteindelijk naar een model gaat, behandelt HTML naar Markdown converteren in Python waar fidelity verloren gaat.
Probeer Thunderbit voor webdata-extractie
Moet je cheerio gebruiken?
Ja, in Node, wanneer API-fit belangrijk is en representatieve documenten in de buurt blijven van het geteste bereik van klein tot 1 MB.
De vertrouwdheid van de API en de huidige onderhoudssignalen zijn legitieme selectiecriteria. De benchmark bewijst niet dat er een bepaald supportantwoord bestaat of dat de geteste verdeling van paginagroottes overeenkomt met je productiecorpus.
Het getal om te onthouden is dat van 10 MB. Iets tussen 1 MB en 10 MB zorgt ervoor dat cheerio’s kosten niet langer met de rest mee oplopen, maar gaan vermenigvuldigen — 4× wordt 18,5×. Als je corpus zulke documenten bevat, benchmark dan vóór je beslist, want de bibliotheek waarschuwt je daar niet voor.
Probeer Thunderbit voor webdata-extractie Get Started Free
Veelgestelde vragen
Is cheerio vergelijken met Python-parsers eerlijk? Het is een stackvergelijking, geen algoritmevergelijking. Alle vier leverden onder de hash-regel identieke gesorteerde titeltekst en hrefs op op 5 van de 5 paginagroottes. Dat bewijst niet dat alle parsers volledig equivalent zijn. De timings van cheerio bevatten het runtimegedrag van Node, terwijl de andere CPython gebruiken met C-gestuurde parsers; de vergelijking beschrijft die end-to-end keuzes.
Waarom wordt de rij van 1 KB niet gerangschikt? Omdat de meting bij 28 microseconden wordt gedomineerd door ruis. Over drie runs was de spreiding bij de Python-parsers 77,6%, met overlappende individuele runs. Elke volgorde op die schaal zou een artefact zijn. Vanaf 10 KB is de meting stabiel genoeg om te lezen.
Waardoor komt de sprong bij 10 MB? Dat zegt deze test niet. Wat wel vaststaat, is dat de sprong echt is en geen ruis: cheerio’s drie runs eindigden op 2.839, 2.928 en 2.954 ms, ver uit elkaar van de rest, terwijl het verschil op 1 MB 4× was. De oorzaak isoleren zou betekenen dat je de parser-backends van cheerio apart profileert, en dat viel buiten deze scope.
Hoeveel dependencies heeft het echt?
Elf direct, 22 top-level na resolutie, 9,0 MiB op schijf. Twee daarvan zijn complete parser-implementaties — htmlparser2 en parse5 — omdat cheerio beide kan gebruiken. Dat is de prijs voor zowel vergevingsgezinde als standards-compliant parsing, en goed om te weten als je dependency trees controleert.
Wat is hier niet getest?
Deze versie testte wel het piekgeheugen van het proces op één document van 226 KB en één van 10 MB, en een set van veertien foutieve-plus-control-inputs waarbij cheerio nergens een fout gooide, overal niet-lege output gaf en 11/22 gescoorde ankers herstelde. Niet getest zijn streaming via parse5-parser-stream, de juistheid van encoding, herstel per backend, de exacte locatie van de prestatiesprong tussen 1 en 10 MB, XML-parsing of nieuwere Node-alternatieven. De ruwe relatieve artifact-links vereisen bovendien dezelfde publieke directorystructuur op het moment van publicatie; anders zijn duurzame openbare URL’s of een commitreferentie nodig.


