Sur une machine, cheerio dans Node a analysé et extrait les titres ainsi que les hrefs d’un document HTML synthétique de 10 Mo en 2 927,89 ms. selectolax, exécuté via CPython avec un parseur appuyé en C, a réalisé la même extraction de champs en 158 ms. Les textes des titres triés et les hachages des hrefs concordaient. Il s’agit ici d’une comparaison de bout en bout entre piles techniques et environnements d’exécution, pas d’un verdict isolé sur un algorithme de parsing.
Sur une page de 10 Ko, l’écart n’est que de 2×, et personne ne le remarquerait. La vraie question est de savoir où vos pages se situent sur cette courbe.
Ce qu’est cheerio
cheerio est le parseur HTML à syntaxe jQuery pour Node, et c’est la réponse par défaut dans cet écosystème pour de bonnes raisons : 30 449 étoiles GitHub, licence MIT, et un push sur le dépôt la veille de mon test. Version testée : 1.2.0.
Référence officielle : présentation officielle de 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();
Si vous avez déjà écrit du jQuery, vous connaissez déjà l’API. C’est précisément cette familiarité qui explique en grande partie son succès dans son écosystème.
En dessous, il ne s’agit pas d’un seul parseur, mais d’une pile : htmlparser2 et parse5 pour l’analyse, domhandler et domutils pour l’arborescence, cheerio-select pour les sélecteurs, ainsi que undici, encoding-sniffer et d’autres — onze dépendances directes, qui se traduisent en 22 paquets de premier niveau et 9,0 MiB sur disque. Ces paquets apportent des capacités d’analyse et d’encodage, mais ils alourdissent aussi l’empreinte des dépendances. Ce test a vérifié le comportement sur entrées mal formées, mais n’a pas testé la justesse de l’encodage ni isolé l’un ou l’autre backend de parsing.
La mesure, et pourquoi elle est fiable
La base de recherche disposait déjà d’un banc d’essai pour parseurs : cinq tailles de page de 1 Ko à 10 Mo, 50 itérations, trois exécutions indépendantes, et — le point crucial — une barrière de parité qui hache le contenu extrait, c’est-à-dire les titres triés plus les hrefs triés, en les comparant à un parseur de référence. Un parseur qui saute discrètement une partie du travail ne peut pas afficher un temps flatteur.
Ajouter cheerio à ce banc nécessitait deux vérifications avant que le moindre chiffre compte.
La référence était-elle toujours au même endroit ? selectolax a été relancé dans la même session, sur les mêmes fixtures. Son hash de contenu s’est reproduit sur 5 tailles sur 5, et son p50 se situait entre 0,989× et 1,079× de la valeur publiée. Il s’agit donc bien de la même machine que celle ayant produit le tableau initial.
cheerio produisait-il les mêmes champs scorés ? Son hash de contenu — calculé dans Node avec la même règle, à savoir SHA-256 sur les titres triés et les hrefs triés — correspondait à la référence sur 5 tailles sur 5. Cela prouve la parité pour ces champs triés sur ces fixtures, mais pas la forme du DOM, l’ordre du document, les attributs, la normalisation du texte ou la récupération d’erreurs.
Ce n’est qu’à partir de là que les timings ont du sens.
| Taille de page | selectolax | lxml | PyQuery | cheerio (Node) | cheerio vs selectolax |
|---|---|---|---|---|---|
| 1 Ko | 0.0286 ms | 0.0508 | 0.0456 | 0.1147 ms | 4.0× |
| 10 Ko | 0.1725 ms | 0.1802 | 0.1728 | 0.3490 ms | 2.0× |
| 100 Ko | 1.4855 ms | 1.4145 | 1.4093 | 3.8399 ms | 2.6× |
| 1 Mo | 14.97 ms | 15.03 | 14.96 | 59.37 ms | 4.0× |
| 10 Mo | 158.10 ms | 165.25 | 162.86 | 2 927.89 ms | 18.5× |
p50 en millisecondes, médiane de trois exécutions. parser-bench.json. Les trois parseurs Python tournaient dans un seul processus ; cheerio a tourné dans Node 22, ce qui constitue aussi bien une frontière d’exécution qu’une frontière de bibliothèque — voir plus bas.
Lire ce tableau honnêtement

La ligne 1 Ko est du bruit. Sur les trois parseurs Python, l’écart à cette taille est de 77,6 %, et les exécutions individuelles se chevauchent fortement — selectolax allait de 0,0267 à 0,0404 ms sur ses trois passages. À 28 microsecondes, la résolution du minuteur et l’ordonnancement dominent. Je ne classerais rien sur une page de 1 Ko, cheerio compris.
Le milieu du tableau n’a rien d’extraordinaire. 2× à 4× sur des pages de 10 Ko à 1 Mo. Pour un scraper qui traite quelques centaines de pages, cela représente 45 millisecondes par page au lieu de 15, et cela restera invisible.
La ligne 10 Mo n’est pas du bruit. Les trois exécutions de cheerio ont donné 2 839, 2 928 et 2 954 ms — serrées, et nettement séparées des autres lignes. Le résultat de bout en bout sur 10 Mo s’écarte fortement du schéma observé sur les petites tailles. Cinq points de mesure ne suffisent pas à établir une complexité asymptotique ni à identifier quelle couche — runtime, parseur, sélecteur, allocation ou garbage collector — provoque ce saut.
On est dans la même zone que BeautifulSoup. Le banc publié mesurait quatre autres parseurs sur la même fixture de 10 Mo, et placer les 2 927,89 ms de cheerio à côté d’eux est l’élément le plus utile de cet article :
| Parseur (10 Mo) | 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 |
Les quatre lignes Python proviennent des valeurs publiées dans bench_parse.json ; celle de cheerio provient de cette exécution. Le parseur de référence s’est reproduit entre 0,989× et 1,079× entre les deux passages, il faut donc considérer comme relevant de cette incertitude tout écart inférieur à environ 8 % — cheerio face au backend html.parser de BeautifulSoup (écart de 5 %) s’y trouve, cheerio face à selectolax (18×) n’y est pas.
BeautifulSoup est la bibliothèque que l’on choisit lorsqu’on veut la simplicité tout en acceptant qu’elle soit lente — c’est celle que tous les fils de discussion sur la performance en Python recommandent de remplacer. Sur un document de 10 Mo, cheerio se situe au bas de cette même zone, et non dans celle des parseurs appuyés en C auxquels on le compare souvent.
La question du remplaçant côté Node reste ouverte dans cet article. Des alternatives Node plus récentes n’ont pas été testées, donc ce résultat ne permet ni d’affirmer qu’un changement de bibliothèque est impossible, ni de les écarter comme moins éprouvées. Il montre seulement le chemin mesuré de cheerio face aux piles Python listées.
C’est aussi une comparaison de runtime, pas seulement de bibliothèque. Les millisecondes de cheerio incluent le JIT et le garbage collector de Node ; les autres incluent CPython appelant des parseurs appuyés en C. Le hash de contenu prouve que le même travail a été effectué, et ces deux chiffres sont bien ce que ressent un développeur qui choisit une pile — mais personne ne devrait lire cela comme « l’algorithme de cheerio est 18× pire que celui de selectolax ». C’est ce qui s’est passé, sur cette machine, dans le runtime natif de chaque bibliothèque.
Réalité de l’installation
| Bibliothèque | Paquets | Disque | Licence | Étoiles | Dernier 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 |
Référence officielle : documentation de configuration de Cheerio.
metadata-snapshot.json, récupéré le jour de la rédaction.
npm install cheerio a pris moins de deux secondes et a téléchargé 9,0 MiB. L’import à froid a été mesuré à 0,056 s lors d’un autre passage de conversion sur la même machine.
Onze dépendances directes, c’est beaucoup pour un parseur, et c’est bon à savoir si vous auditez votre arbre de dépendances : htmlparser2, parse5, parse5-htmlparser2-tree-adapter, parse5-parser-stream, domhandler, domutils, dom-serializer, cheerio-select, encoding-sniffer, undici et whatwg-mimetype. Deux implémentations complètes de parseur sont incluses, parce que cheerio peut utiliser l’une ou l’autre selon ce que vous lui demandez.
Trente mille étoiles et un push la veille du test constituent, dans cette catégorie, un signal de maintenance aussi sain qu’on puisse l’espérer.
Mémoire, et ce que le HTML cassé lui fait subir
L’empreinte mémoire et le comportement face au HTML mal formé ont un impact sur le déploiement et la gestion des erreurs ; ils sont donc mesurés séparément ici.
Le contexte plus large des tests de stress figure dans la comparaison mémoire et HTML mal formé de dix bibliothèques.
Mémoire résidente maximale, via /usr/bin/time -l, un processus neuf par cellule — le plancher d’import correspond au coût de la bibliothèque une fois chargée et au repos, les pics incluent le document.
| Bibliothèque | Runtime | Plancher d’import | Pic à 226 Ko | Pic à 10 Mo |
|---|---|---|---|---|
| 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. Les bases Python et Node ne sont pas comparables entre elles ; l’interpréteur est présent dans les deux.
cheerio affiche le plancher d’import le plus élevé de ce tableau mixte avec 66,8 MiB, runtime Node et dépendances compris. Son processus a culminé à 398,5 MiB sur la fixture de 10 Mo. Les autres lignes mêlent parseurs, convertisseurs et extracteurs d’articles qui n’effectuent pas le même travail principal ; il faut donc les lire comme un contexte d’empreinte des processus, pas comme un classement de performance entre pairs. La ligne turndown, dans le même runtime, a culminé bien plus haut, mais elle fait de la conversion plutôt que le contrat d’extraction titres + hrefs mesuré pour cheerio.
HTML cassé. Douze documents cassant chacun exactement une chose — balises non fermées, éléments inline mal imbriqués, attributs sans guillemets contenant des espaces, fermetures parasites, absence totale de <html>, attributs dupliqués, document tronqué en plein tag, entités invalides, <script> non fermé, déclaration d’encodage mensongère, commentaire contenant du balisage, et 600 niveaux d’imbrication — plus deux contrôles bien formés à tailles équivalentes, car « il n’a rien retourné » ne dit quelque chose sur le caractère mal formé que si la bibliothèque ne reste pas muette non plus sur un document propre de même taille.
cheerio a levé une erreur dans 0 cas sur 14 et n’a renvoyé aucun résultat dans 0 cas, tout en récupérant 11 des 22 sentinelles scorées sur les fixtures mal formées (malformed-results.json). Pour les parseurs, le score vérifie les sentinelles de titres et de liens sur onze documents mal formés scorables ; il ne score pas la sentinelle de paragraphe, et la fixture <script> non fermé est exclue. Sans référence au même contrat dans cette section, 11/22 n’est pas un classement de qualité. La conclusion supportée est que cheerio a renvoyé une sortie non vide sans lever d’erreur sur les quatorze entrées mal formées et de contrôle, tout en retrouvant la moitié des marqueurs scorés.
Avantages et inconvénients
En sa faveur. Syntaxe jQuery familière. MIT. Un signal de maintenance horodaté de 30 449 étoiles et une activité de dépôt la veille du test. Deux backends de parsing et des paquets liés à l’encodage sont présents, même si la récupération selon le backend et la précision de l’encodage n’ont pas été isolées ici. Le hash trié titres + hrefs a correspondu à la référence pour chaque taille de fixture.
Contre lui. 18,5× plus lent que selectolax sur un document de 10 Mo, et 4× sur 1 Mo. Onze dépendances directes, dont deux implémentations complètes de parseur. Seulement Node. Et rien dans sa documentation n’indique une taille à partir de laquelle il cesserait d’être le choix évident.
Qui devrait l’utiliser, et qui ne devrait pas
Utilisez cheerio si vous êtes dans Node, si l’API façon jQuery vous est utile, et si vos pages représentatives ressemblent aux tailles testées jusqu’à 1 Mo. Un mégaoctet est le plus grand point testé avant le saut brutal observé à 10 Mo ; cet article n’établit pas la frontière entre les deux ni la part du web qui se situe en dessous.
Benchmarkez avant de l’adopter pour des documents HTML très volumineux comme des rapports générés, des exports de catalogue ou de longues pages de listes. Le comportement XML n’a pas été testé. Sur la fixture HTML de 10 Mo, 2,9 secondes par document constituent un coût réel qui se cumule.
Si vous êtes en Python, cette comparaison dit autre chose : selectolax, lxml et PyQuery sont pratiquement à égalité à partir de 10 Ko (dans une fourchette de 0,5 % à 5,4 %, avec des plages de résultats qui se chevauchent), donc choisissez selon l’API plutôt que selon la vitesse. L’écart de cheerio avec ces trois-là est le chiffre intéressant, pas les différences entre eux.
Où se place une API managée
cheerio analyse le HTML que vous avez déjà. Il ne récupère pas les pages, n’exécute pas JavaScript et ne gère pas de couche anti-bot — et, sur bien des cibles réelles, c’est la moitié la plus difficile du travail.
Un service managé de récupération/rendu/extraction, y compris notre propre Thunderbit, se situe à une autre frontière de responsabilité. Thunderbit n’a pas été benchmarké ici. La distinction pertinente est celle entre analyser un HTML fourni à l’aide de sélecteurs et externaliser l’acquisition, le rendu et l’extraction ; cet article ne fournit aucune comparaison de qualité, de latence ou de coût sur un même métrique.
En toute honnêteté : si vous avez le HTML et connaissez vos sélecteurs, cheerio est gratuit et agréable à utiliser. Si vous récupérez des pages à grande échelle, ou si vous préférez décrire les données plutôt que le DOM, il s’agit d’un autre achat.
Pour une vision plus large, notre comparatif des API de scraping web couvre les options hébergées, et le pilier open source des scrapers couvre les solutions auto-hébergées. Si la sortie analysée est destinée à un modèle, convertir du HTML en Markdown en Python explique où la fidélité se perd.
Essayez Thunderbit pour l’extraction de données web
Faut-il utiliser cheerio ?
Oui, dans Node, lorsque l’adéquation de l’API compte et que les documents représentatifs restent proches de la plage testée, du petit format jusqu’à 1 Mo.
La familiarité avec l’API et les signaux de maintenance actuels sont des critères de sélection valables. Le benchmark ne prouve ni qu’une réponse de support donnée existe, ni que la distribution des tailles de pages testée correspond à un corpus de production.
Le chiffre à retenir est celui de 10 Mo. Quelque part entre 1 Mo et 10 Mo, le coût de cheerio cesse de suivre les autres et commence à se multiplier — 4× devient 18,5×. Si votre corpus contient des documents de cette taille, benchmarkez avant de vous engager, car rien dans la bibliothèque ne vous en avertira.
Essayez Thunderbit pour l’extraction de données web Get Started Free
FAQ
Comparer cheerio à des parseurs Python est-il équitable ? C’est une comparaison de piles techniques, pas d’algorithmes. Les quatre ont produit exactement les mêmes titres triés et les mêmes hrefs triés selon la règle de hachage sur 5 tailles de page sur 5. Cela ne prouve pas une équivalence totale des parseurs. Les timings de cheerio incluent le comportement du runtime Node, tandis que les autres incluent CPython appelant des parseurs appuyés en C ; la comparaison décrit ces choix de bout en bout.
Pourquoi la ligne 1 Ko n’est-elle pas classée ? Parce qu’à 28 microsecondes, la mesure est dominée par le bruit. Sur trois exécutions, les parseurs Python se sont répartis sur 77,6 %, avec des exécutions individuelles qui se chevauchent. Tout ordre à cette taille serait un artefact. À partir de 10 Ko, la lecture est suffisamment stable.
Qu’est-ce qui provoque le saut à 10 Mo ? Ce test ne le dit pas. Ce qu’il établit, c’est que ce saut est réel et ne relève pas du bruit : les trois exécutions de cheerio se sont situées à 2 839, 2 928 et 2 954 ms, bien à l’écart de tout le reste, alors que l’écart à 1 Mo n’était que de 4×. Isoler la cause supposerait de profiler séparément les backends du parseur cheerio, ce qui sortait du cadre ici.
Combien de dépendances a-t-il réellement ?
Onze directes, 22 paquets de premier niveau après résolution, 9,0 MiB sur disque. Deux d’entre elles sont des implémentations complètes de parseur — htmlparser2 et parse5 — parce que cheerio peut utiliser l’une ou l’autre. C’est le prix à payer pour gérer à la fois un parsing tolérant et un parsing conforme aux standards, et c’est utile à savoir si vous auditez des arbres de dépendances.
Qu’est-ce qui n’a pas été testé ici ?
Les tests couvraient le pic mémoire du processus sur un document de 226 Ko et un document de 10 Mo, ainsi qu’un ensemble de quatorze entrées mal formées et de contrôle où cheerio n’a levé aucune erreur, a renvoyé une sortie non vide dans tous les cas, et a récupéré 11 des 22 sentinelles scorées. Ils ne couvraient pas le flux via parse5-parser-stream, la justesse de l’encodage, la récupération spécifique au backend, l’emplacement exact du saut de performance entre 1 et 10 Mo, le parsing XML, ni les alternatives Node plus récentes.


