BeautifulSoup en 2026 : l’analyseur HTML le plus convivial est aussi le plus lent (de 12 à 17x)

Dernière mise à jour le July 17, 2026
BeautifulSoup en 2026 : l’analyseur HTML le plus convivial est aussi le plus lent (de 12 à 17x)
Résumé IA
Cette analyse évalue BeautifulSoup comme l’analyseur HTML Python le plus accessible et quantifie précisément le coût de cette simplicité. Elle compare bs4 à des parseurs optimisés en C sur la vitesse, la tolérance au HTML mal formé, la couverture des sélecteurs CSS, la rétention des objets et la récupération des encodages. L’article montre que BeautifulSoup est nettement plus lent, souvent de 12 à 17 fois, tout en expliquant pourquoi les développeurs continuent de le choisir : API lisible, parsing indulgent, excellente prise en charge de soupsieve et très bonne ergonomie pour des tâches de scraping ponctuelles et désordonnées. C’est un guide pratique pour savoir quand cette taxe de vitesse reste acceptable et quand un parseur plus rapide est le meilleur choix technique.

BeautifulSoup est la bibliothèque vers laquelle presque tout le monde se tourne pour analyser une page web en Python pour la première fois, et c’est effectivement la plus lente des vrais analyseurs HTML. Ces deux affirmations sont vraies, et aucune n’est une critique. Ce qui est intéressant, c’est que ce « plus lent » se traduit par un chiffre précis, mesurable, et donc achetable — pas seulement une impression.

J’ai passé bs4 (c’est-à-dire beautifulsoup4, version 4.15.0, publiée en juin 2026, sous licence MIT) à la fois à travers de nouveaux tests de fonctionnalité et à travers des données de timing réutilisées du même banc de test, et le constat est cohérent : vous échangez une pénalité de vitesse d’environ un ordre de grandeur contre l’API la plus agréable à utiliser et la meilleure tolérance aux erreurs du secteur. Est-ce un bon échange ? Tout dépend de votre charge de travail, et cette analyse garde donc les deux côtés sur la table.

Ce qu’est vraiment BeautifulSoup — et ce qu’il n’est pas

La plupart des tutoriels sautent la partie la plus importante : BeautifulSoup ne parse pas le HTML. C’est une couche d’enrobage. En interne, il remet votre document à l’un des trois vrais parseurs — le html.parser intégré à Python, lxml ou html5lib — puis encapsule l’arbre obtenu dans une API unique, très simple pour naviguer et rechercher. Le rôle de bs4 n’est pas d’analyser. Son rôle est de rendre le résultat agréable à parcourir.

Son propre auteur le décrit comme une « bibliothèque de screen scraping », et la promesse a toujours été la même : lui donner un HTML si mal formé qu’un navigateur grimacerait, et il ira quand même récupérer les données demandées. Cette réputation est méritée, avec une réserve dont nous parlerons plus loin.

Quelques faits utiles à fixer avant d’aller plus loin :

ChampValeur
Packagebeautifulsoup4 (importé comme bs4)
Version testée4.15.0 (mise en ligne le 2026-06-07)
Compatibilité Python>=3.7.0
LicenceMIT
Site officielcrummy.com/software/BeautifulSoup
Code source + suivi des bugsLaunchpadpas GitHub
MaintenanceActive (4.15.0 en juin 2026, six versions sur les douze derniers mois)

Cette mention « pas GitHub » compte plus qu’il n’y paraît. bs4 est une bibliothèque vieille de 20 ans, hébergée sur crummy.com et Launchpad, donc le réflexe habituel du nombre d’étoiles GitHub n’a pas vraiment de sens ici. Il vaut mieux juger sa santé à la fréquence des versions — et, de ce point de vue, elle se porte très bien.

Petit point licence, pour celles et ceux qui doivent répondre à une équipe conformité : le wrapper est sous MIT, mais ce que « utiliser bs4 » ajoute réellement à votre arborescence de dépendances dépend du backend choisi. html.parser fait partie de la bibliothèque standard Python (licence PSF, zéro dépendance supplémentaire). lxml est sous BSD, mais s’appuie sur libxml2/libxslt — une dépendance C externe à compiler ou à installer via un wheel précompilé. html5lib est en pur Python et sous MIT. Si vous voulez l’empreinte de dépendances la plus légère, le html.parser intégré est le meilleur choix — qui, justement, est aussi le backend avec le plus gros piège. J’y reviens tout de suite.

Le coût de vitesse, chiffré

Posons le chiffre tout de suite, parce que c’est l’élément principal, et le cacher serait malhonnête. Sur une tâche réaliste de type « parser puis extraire » — analyser la chaîne, récupérer tous les <h3 class="title"> et tous les <a href> — BeautifulSoup est le parseur le plus lent de cette comparaison, et ce n’est pas serré.

BeautifulSoup speed tax: 232 ms versus 15 ms C parsers

Ces temps sont repris du banc de test de selectolax (même machine, même méthodologie en 3 passes, état au 2026-07-13) ; cette analyse ne relance aucun benchmark de timing elle-même, afin d’éviter la contention CPU et le travail en double. Latence médiane p50, en millisecondes :

Taille de pagebs4 (html.parser)bs4 (lxml)selectolax-Lexborlxmlbs4-hp plus lentbs4-lxml plus lent
1 KB0.3230.2810.0270.03612.0x10.5x
10 KB2.0491.7050.1600.16612.8x10.7x
100 KB20.59916.5401.4641.42314.1x11.3x
1 MB232.558181.85514.90114.17715.6x12.2x
10 MB2788.7462261.557159.935172.93317.4x14.1x

Donc bs4(html.parser) est environ 12 à 17x plus lent qu’un parseur C comme selectolax-Lexbor, et passer au backend lxml ne le ramène qu’à 10,5 à 14x — toujours un ordre de grandeur derrière. La raison est structurelle, pas un bug : quel que soit le backend utilisé pour parser, bs4 crée un objet Python complet (Tag ou NavigableString) pour chaque nœud. Cette couche de matérialisation des objets est une taxe que les parseurs C ne paient tout simplement pas.

Remarquez que le multiplicateur augmente avec la taille des pages — 12,0x à 1 KB, 17,4x à 10 MB. Cela montre qu’il ne s’agit pas d’un surcoût fixe de démarrage que l’on pourrait amortir. C’est une taxe par nœud, qui croît linéairement avec le nombre de nœuds créés.

Maintenant, changeons de perspective, parce que « 10x plus lent » fait plus peur que dans la pratique. Sur une page de 1 MB, on parle de 232 ms contre 15 ms. Si votre travail consiste à « extraire quelques centaines à quelques milliers de pages de quelques centaines de KB chacune », cette différence absolue est imperceptible — vous ne la sentirez pas, et l’optimiser ne vous apportera rien. Si votre travail est un pipeline d’un million de pages, le même ratio fait toute la différence entre un job qui termine et un job qui n’aboutit pas. Même nombre, verdict opposé. Il faut donc le juger à l’échelle réelle de votre volume, pas à l’échelle du benchmark.

Non, changer de backend ne règle pas le problème

On entend souvent qu’il suffit de donner à bs4 le backend lxml pour obtenir la vitesse de lxml. Ce n’est pas vrai, et il est utile de comprendre pourquoi. Sur une requête CSS en batch de 100 000 nœuds (sélectionner chaque <a> et lire son href, arbre déjà construit), l’écart de débit est très net :

ParseurQuery p50Nœuds/sec
lxml33.30 ms3,002,646
selectolax-Modest34.19 ms2,924,550
selectolax-Lexbor39.46 ms2,534,027
bs4 (lxml)250.56 ms399,111

bs4(lxml) atteint environ 399 000 nœuds/seconde — soit environ 6,3 à 7,5x plus lent que les trois moteurs C, alors même que son propre backend est lxml. Le backend accélère la construction de l’arbre. En revanche, les requêtes et la traversée passent toujours par soupsieve, puis par des objets Tag de bs4, et chaque nœud sélectionné est toujours emballé dans un objet Python. Donc l’idée « donnez-lui lxml et bs4 devient aussi rapide qu’lxml » est fausse : le backend accélère une phase, et la phase la plus lente n’est pas celle-là.

La mémoire et le démarrage à froid complètent le coût. Sur un document de 10 MB, bs4 utilise environ 1,5 à 1,75x la mémoire résidente de selectolax ou lxml (218–226 MB contre 129–145 MB) — même cause racine : un objet Python par nœud. Et importer bs4 prend environ 33,4 ms contre 14,1 ms pour lxml.html, donc c’est 2,36x plus lent à importer. Ce dernier point est négligeable dans un processus long, mais pour un outil CLI ou une fonction serverless qui redémarre souvent à froid, c’est un petit coût réel qu’il faut connaître.

Pourquoi ajouter des threads ne vous sauvera pas

Si votre réflexe face à une tâche lente et liée au CPU est « ajoutons des threads », bs4 va punir ce réflexe. Sur une page de 1 MB analysée 48 fois, en mono-thread puis avec quatre threads :

Parseur1 thread4 threadsAccélération
selectolax-Lexbor0.563 s0.159 s3.54x
lxml0.459 s0.378 s1.21x
bs4 (lxml)6.945 s26.842 s0.26x

Lisez bien la dernière ligne deux fois. Quatre threads rendent bs4 environ 3,9x plus lent, pas plus rapide. Le signal empirique est clair : la bibliothèque semble conserver le GIL. La construction d’arbre de bs4 est en pur Python, donc elle se sérialise sous le Global Interpreter Lock ; empiler des threads ne fait qu’ajouter du coût de planification à un travail qui ne peut pas réellement s’exécuter en parallèle. selectolax obtient son gain d’environ 3,5x parce que son cœur C libère le verrou ; bs4, lui, ne le peut pas.

Pour l’ère du free-threading, le conseil pratique est donc simple : si vous devez paralléliser BeautifulSoup, utilisez le multiprocessing (ProcessPoolExecutor), pas les threads. selectolax et lxml peuvent mieux tirer parti des threads ; bs4 non. Petite réserve méthodologique : il s’agit d’une seule observation avec un seul nombre de threads (4) sur une seule taille de page (1 MB), et le mécanisme « conserve le GIL » reste une hypothèse déduite du comportement en temps réel, pas quelque chose que j’ai confirmé en instrumentant précisément le chemin de code concerné. La direction est claire ; le mécanisme exact reste provisoire.

Le backend par défaut est le piège. Lisez ceci en premier.

Si vous ne retenez qu’une chose de cet article, retenez celle-ci. Un simple BeautifulSoup(html) sans second argument utilise html.parser, et html.parser n’implémente pas les règles HTML5 des balises de fin optionnelles. Cela semble théorique… jusqu’au moment où vos données sont silencieusement corrompues.

BeautifulSoup backend tolerance matrix: html.parser 12/15, lxml and html5lib 15/15

J’ai fait passer 15 échantillons HTML volontairement mal formés dans les trois backends, avec une assertion structurelle agnostique au backend, prédéfinie avant l’exécution pour chacun (afin que personne ne choisisse le gagnant après coup). Résultats :

BackendConforme à l’attente / 15
lxml15
html5lib15
html.parser12

Les trois échecs ont la même cause racine. Prenez un tableau non fermé : <table><tr><td>a<td>b<tr><td>c<td>d</table>. Avec html.parser, le texte extrait des cellules devient ['abcd','bcd','cd','d'] — chaque <td> avale tout ce qui suit, parce que le parseur imbrique les cellules au lieu de les fermer. lxml et html5lib renvoient correctement ['a','b','c','d']. Les listes non fermées se comportent pareil : <li>a<li>b<li>c donne ['abc','bc','c'] avec html.parser, et le résultat propre ['a','b','c'] avec les deux autres. Les attributs dupliqués changent aussi : <div id="first" id="second"> conserve "second" sous html.parser mais "first" sous lxml/html5lib, et la spécification HTML5 dit bien de garder le premier.

Pourquoi est-ce dangereux, et pas seulement agaçant ? Parce que cela se produit sans lever d’erreur. Un scraper qui fait tranquillement BeautifulSoup(html) et tombe sur un tableau ou une liste non fermée — ce qui arrive tristement souvent sur de vieux sites, du HTML écrit à la main ou des templates qui ont oublié une balise de fermeture — va mélanger du texte de cellules adjacentes dans un seul champ, vous renvoyer des données sales, et ne se plaindre à aucun moment. La correction tient en un argument : BeautifulSoup(html, "lxml") ou BeautifulSoup(html, "html5lib").

Pour être juste avec html.parser, les 12 autres cas mal formés sur 15 sortaient identiques dans les trois backends — imbrication incorrecte de balises comme <b><i></b></i>, absence de squelette html/body, attributs non quotés, balises de fermeture orphelines, commentaires non fermés, formulaires imbriqués, casse mélangée, et plus encore. La tolérance de bs4 est réellement forte dans l’ensemble ; les divergences se concentrent presque entièrement sur la famille des balises de fin optionnelles. Et rien de tout cela n’est une découverte : la documentation officielle de bs4, dans « Differences between parsers », dit déjà en termes simples que html.parser est « less lenient » (moins permissif). Ce que la matrice de HTML mal formé apporte, ce sont les cas précis et reproductibles où « moins permissif » se transforme en mauvais résultat.

Ce que vous ne perdez pas : l’API et le CSS sont son grand point fort

Donc bs4 est lent, monothread, et son backend par défaut peut piéger. Les gens continuent malgré tout à l’utiliser, parce que la moitié « conviviale » de l’échange est bien réelle — et les tests le confirment.

BeautifulSoup soupsieve CSS coverage wins with 41/41 plus 20/20

J’ai lancé 29 tests d’API couvrant la recherche, le CSS, la navigation dans l’arbre, l’extraction de texte et la modification du DOM. Les 29 ont réussi, avec un résultat validé en comparant la valeur réelle à une valeur attendue, et non à l’œil nu. Deux de ces capacités sont des avantages ergonomiques que les parseurs C n’offrent tout simplement pas :

  • Prédicats fonctionnels dans find / find_all. Vous pouvez écrire soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])) et exprimer une condition complexe en une seule ligne Python — sans devoir faire un « tout sélectionner, puis filtrer » en deux étapes.
  • Navigation d’arbre nommée et bidirectionnelle. .parent, .next_sibling, .find_parent, .stripped_strings, .descendants — les parcours se lisent presque comme de l’anglais et vont dans les deux sens. selectolax demande parfois plusieurs étapes pour certaines de ces opérations, ou ne les propose pas du tout.

Voilà la partie « ça vous fait gagner du temps de développement » rendue concrète. Ce n’est pas du marketing ; ce sont 29 validations vertes.

Deux pièges à noter, parce qu’un vrai audit mentionne les deux côtés. Premièrement, les attributs booléens : <input disabled> renvoie une chaîne vide "" pour disabled dans bs4 (selectolax renvoie None). Les deux sont falsy, donc if node.get("disabled") manque silencieusement un attribut booléen qui est pourtant bien présent dans les deux bibliothèques — le test sûr est "disabled" in tag.attrs. Deuxièmement, get_text(strip=True) concatène le texte des nœuds sans séparateur après suppression des espaces, donc "...with " + "link1" devient "withlink1". Il faut passer separator=" " si vous avez besoin de conserver les frontières entre les mots. Aucun de ces pièges n’est spécifique à bs4 ; ce sont des pièges qui existent dans plusieurs bibliothèques.

Et maintenant la surprise : choisir bs4 ne vous fait pas perdre la couverture CSS. Son moteur CSS, soupsieve, est le plus complet de toute cette comparaison. Sur la matrice de base en 41 cas (réutilisée depuis le banc de test selectolax), soupsieve obtient 41/41 — le seul score parfait du lot, devant les 39/41 de selectolax-Lexbor et les 37/41 de cssselect (lxml/parsel). J’ai ensuite exécuté 20 cas supplémentaires avancés, explicitement documentés par soupsieve, et il a fait 20/20, y compris des sélecteurs que Lexbor refuse purement et simplement : :lang(en), le :-soup-contains('featured') propre à soupsieve, :is(), :where() et :has(> a). Les seuls vrais manques sont XPath (soupsieve est limité au CSS) et les pseudo-éléments ::text / ::attr() de parsel, qui sont des extensions Scrapy. Si votre monde est XPath, la migration fera mal.

Le verdict de cette section est clair : ce que vous sacrifiez en choisissant BeautifulSoup, c’est la vitesse. Ce n’est ni l’ergonomie de l’API, ni la couverture CSS.

Deux pièges de production à prévoir dans le budget

Au-delà du backend par défaut, deux comportements vous poseront problème dans les charges longues ou les jeux de données non UTF-8.

Cycles de référence : appelez decompose() dans les boucles longues

Chaque Tag bs4 garde une référence vers son parent et vers ses enfants, ce qui forme un cycle de références. Le comptage de références de CPython ne peut pas libérer un cycle tout seul — c’est le travail du ramasse-miettes générationnel. Pour voir à quel point cela compte, j’ai construit puis supprimé un arbre 300 fois avec le GC désactivé, puis j’ai compté les objets Tag encore présents en mémoire :

BeautifulSoup reference cycles retain 120,900 objects with GC off and 0 with GC on

ScénarioTags conservés après del
GC désactivé120,900 (300 cycles, rien n’a été récupéré)
GC activé26,598 (le GC générationnel a déclenché pendant la boucle)
Après gc.collect() forcé0 (tout a été récupéré)
Contrôle sans cycle (liste de chaînes, GC désactivé)delta 0

Avec le GC désactivé, del soup ne récupère rien — les 120 900 objets restent en mémoire, parce que le cycle de références contourne le comptage de références. Un seul gc.collect() les a tous libérés. Le groupe de contrôle sans cycle (une simple liste de chaînes, connue pour ne pas former de cycle) affiche un delta nul, ce qui prouve que l’accumulation vient bien du cycle de bs4 et non d’un bruit de mesure. La documentation de bs4 dit d’ailleurs elle-même que les objets sont « densely interconnected ... exactly the sort a garbage collector would have trouble with » ; ce comportement est donc documenté, et le test ajoute ici le nombre d’objets retenus ainsi que la preuve que collect() remet le compteur à zéro.

La règle pratique : dans un pipeline qui analyse de nombreuses grosses pages dans une boucle serrée, si votre code (ou un réglage de très haut débit) désactive le GC ou ne le déclenche pas assez souvent, les arbres bs4 vont s’attarder en mémoire et la consommation va grimper. Appelez soup.decompose() après chaque page — bs4 le fournit précisément pour casser le cycle et libérer plus tôt. Les arbres C de selectolax et lxml n’ont pas ce problème.

Encodage : UnicodeDammit est l’avantage discret de bs4

bs4 intègre un composant que les parseurs rapides n’ont pas : UnicodeDammit, qui détecte l’encodage d’un document et le convertit automatiquement en Unicode. Je lui ai donné une matrice de 8 cas « encodage déclaré vs réel » :

BeautifulSoup UnicodeDammit recovers 5 of 8 encoding cases

CasEncodage réelDevine UnicodeDammitRécupéré ?
utf8_no_declutf-8utf-8Oui
utf16_bomutf-16utf-16leOui
gbk_chinesegbkgb18030Oui (superset)
shiftjisshift_jiscp932Oui (superset)
latin1_declared_utf8latin-1 (déclaré utf-8)iso-8859-1Oui (a ignoré le mensonge)
latin1_no_decllatin-1cp720Non
cp1252_no_declcp1252cp862Non
utf8_declared_latin1utf-8 (déclaré latin-1)iso-8859-1Non (a suivi le mensonge)

Cinq cas sur huit ont été récupérés. UTF-8, UTF-16 avec BOM, GBK, Shift-JIS, et même un latin-1 mal étiqueté sont revenus correctement, et les hypothèses sur-ensembles (GBK→gb18030, Shift-JIS→cp932) restent décodables. Deux modes d’échec sont importants à connaître : les courts échantillons latin-1/cp1252 sont parfois pris à tort pour des pages de code pages DOS, parce que le détecteur statistique n’est pas fiable sur de petits échantillons et que les caractères de dessin de boîtes DOS recoupent les points de code de Latin-1 ; et lorsqu’une déclaration <meta charset> est simplement fausse, UnicodeDammit fait confiance à cette déclaration. La documentation de bs4 signale les deux — un échantillon peut être « so short that Unicode, Dammit can't get a lock on it », et plus il y a de données, meilleure est l’estimation.

Face à selectolax, qui corrompt silencieusement les octets non UTF-8 et vous laisse le soin de les décoder vous-même, c’est un vrai avantage : bs4 essaie au moins de deviner et y parvient souvent. Mais ce n’est pas une garantie. Pour un encodage connu, ne jouez pas au devin et soyez explicite : BeautifulSoup(bytes, from_encoding="...").

Les backends divergent-ils vraiment sur des pages réelles ?

La matrice de HTML mal formé montre les backends diverger sur des entrées volontairement cassées. La question suivante est évidente : est-ce que cela se voit dans le monde réel ? J’ai donc fait passer les trois backends sur 11 pages réelles récupérées — BBC, Wikipedia, Craigslist, MDN, old.reddit, la documentation Python, Hacker News, Books to Scrape, webscraper.io, whitehouse.gov et une page de citations rendue en JavaScript — en comparant les comptes de liens, de titres et d’images.

Les trois ont été d’accord sur les 11 pages. Zéro divergence. Ce qui signifie que le désaccord entre backends de la section piège n’apparaît que sur du HTML volontairement mal formé ; quand un site de production moderne est suffisamment bien structuré — même s’il est un peu « sale » — le choix du backend ne change pas ce que vous extrayez. En pratique : pour les sites courants et bien formés, html.parser fait parfaitement l’affaire et évite une dépendance. Ce n’est que lorsque vous scrapez du HTML manifestement non standard, écrit à la main ou ancien que le choix du backend commence à influencer les résultats — et c’est là qu’il faut passer à lxml ou html5lib.

Petite parenthèse sur cette série, parce que c’est un vrai cas limite. La page MDN contient un élément <template>, et tous les backends bs4 ont renvoyé 508 liens — ce qui signifie que bs4 a aplati le contenu de <template> dans l’arbre principal. bs4 se place donc du même côté que lxml, et à l’opposé de selectolax-Lexbor, qui suit strictement la spécification HTML5 (un <template> est un DocumentFragment inerte) et n’en renvoie que 497, en ignorant silencieusement les 11 liens contenus dans le template. bs4 va donc bien capturer des données à l’intérieur d’un <template> — utile, mais aussi susceptible de récupérer du contenu « fantôme » qu’un navigateur n’affichera jamais. Aucun comportement n’est faux ; ce sont simplement des interprétations différentes de la spécification, et il faut savoir laquelle vous obtenez.

Où BeautifulSoup s’insère — et où il ne s’insère pas

Plutôt que de réduire tout cela à un score unique sur 100 (ce qui masquerait justement les arbitrages importants), voici un tableau par dimension, avec une réserve sur chaque ligne :

DimensionCe que les tests ont montréRéserve pour le lecteur
Installation / premier lancementSimple wrapper, pas de navigateur ni de configuration ; html.parser sans dépendance ; wheels précompilésLe backend lxml ajoute une dépendance C
Vitesse vs parseurs C12–17x plus lent (html.parser) / 10,5–14x (backend lxml), toutes tailles confonduesUn seul banc ; données selectolax réutilisées
Débit des requêtes CSS~6–7,5x plus lent sur 100k nœuds ; le backend lxml ne le sauve pasRéutilisé ; paie la taxe Python par nœud
Mémoire1,5–1,75x selectolax/lxml ; le plus lourdRéutilisé ; mesuré en RSS
Démarrage à froid / import2,36x plus lent (33,4 vs 14,1 ms)Réutilisé ; impact modeste
Passage à l’échelle avec threadsbs4-lxml ~3,9x plus lent avec 4 threads (conserve le GIL)Une seule observation ; utilisez multiprocessing
Ergonomie de l’API29/29 tests ; find à prédicat fonctionnel + navigation bidirectionnellePièges : attribut booléen en chaîne vide et strip sans séparateur
Couverture CSSsoupsieve est le plus complet : 41/41 de base + 20/20 avancés ; prend en charge :langPas de XPath, pas de ::text
Tolérance entre 3 backendslxml/html5lib 15/15 ; html.parser 12/15Divergence uniquement sur HTML mal formé
Cohérence sur pages réelles3 backends d’accord sur 11/11 ; tous aplatisent <template> (508)Sites bien formés : le backend n’a pas d’effet
GC des cycles de référenceL’arbre forme un cycle ; 300 boucles ont conservé 120 900 objets, collect() les a supprimésLes boucles longues ont besoin de decompose()
EncodageUnicodeDammit récupère 5/8 ; se trompe sur les petits échantillons, suit les mauvaises déclarationsUne seule observation
MaintenanceActive (4.15.0, juin 2026) ; MITHébergé sur crummy/Launchpad, pas GitHub

Alors, pour qui BeautifulSoup est-il fait ? Pour toute personne qui valorise une API lisible et un parsing indulgent plus que le débit brut, sur des volumes modérés — prototypes, scrapers ponctuels, outils internes, équipes où le temps développeur coûte plus cher que le temps d’exécution. Qui devrait regarder ailleurs ? Les pipelines d’un million de pages où la taxe de vitesse finit par représenter de vrais coûts, les charges qui nécessitent du parallélisme au niveau des threads, et toute personne mariée à XPath.

Un mot sur sa place dans une vraie chaîne de scraping, et sur l’endroit où notre propre outil intervient. BeautifulSoup suppose que vous avez déjà le HTML. Il ne récupère pas les pages, ne rend pas le JavaScript, et ne fait rien contre les défenses anti-bot ou les CAPTCHAs — c’est un autre problème, et un problème réellement difficile sur le web moderne. C’est ici qu’une API de scraping IA se place à une autre couche : la stack développeur de Thunderbit — une API REST, un serveur MCP et une CLI — gère la récupération, le rendu JavaScript et le problème anti-bot, puis renvoie soit du Markdown propre (POST /distill), soit du JSON structuré aligné sur un schéma (POST /extract), sans que vous ayez à écrire de sélecteurs. Les deux ne sont pas concurrents ; ils sont complémentaires. bs4 parse le HTML que vous avez déjà en main ; l’API, MCP et CLI de Thunderbit vous apportent le HTML auquel vous n’accédez pas facilement au départ. Si votre goulot d’étranglement est le parsing, bs4 est une bonne réponse. Si votre goulot d’étranglement est l’acquisition, c’est une autre couche.

Essayer Thunderbit pour l’extraction de données web

En résumé

BeautifulSoup vous offre l’API la plus conviviale, la meilleure tolérance au HTML mal formé et le moteur CSS le plus complet de cette comparaison — en échange d’une taxe de vitesse d’environ un ordre de grandeur et de l’empreinte mémoire la plus élevée. C’est l’arbitrage complet, dit sans détour. Le backend html.parser par défaut est le vrai piège : il déforme silencieusement les tableaux et listes non fermés, donc passez "lxml" ou "html5lib" dès que vos entrées risquent d’être sales. Les threads ne l’accéléreront pas — le multiprocessing, oui. Et dans les boucles longues, appelez decompose() sur chaque page pour éviter l’accumulation des cycles de référence.

Deux limites pour terminer. Tout ici a été mesuré sur une seule plateforme (macOS arm64, Python 3.14, wheels précompilés), et les multiplicateurs de timing sont repris du banc de test selectolax (même bench, état au 2026-07-13) plutôt que relancés — ils héritent donc de cette limite de plateforme unique, et un environnement Linux x86_64 ou une compilation depuis les sources pourrait faire bouger les chiffres exacts. Et rien dans ces résultats n’est une découverte nouvelle : bs4 est une bibliothèque vieille de 20 ans, donc tous les comportements testés sont soit documentés, soit déjà publiquement connus. La valeur n’est pas le scoop. C’est le fait de mettre un vrai chiffre sur des compromis que la documentation décrit seulement qualitativement.

Questions fréquentes

BeautifulSoup est-il lent ? Oui, et de façon mesurable. Sur une tâche de type parser + extraction, il tourne environ 12 à 17x plus lentement qu’un parseur C comme selectolax-Lexbor avec le backend html.parser par défaut, et 10,5 à 14x plus lentement avec le backend lxml, parce qu’il crée un objet Python pour chaque nœud. L’importance de ce point dépend de l’échelle : sur une page de 1 MB, on parle de 232 ms contre 15 ms, ce qui est invisible pour quelques milliers de pages mais décisif pour un pipeline d’un million de pages.

Quel parseur BeautifulSoup dois-je utiliser — html.parser, lxml ou html5lib ? Pour des sites classiques et bien formés, le html.parser par défaut suffit et n’ajoute aucune dépendance. Mais il n’implémente pas les balises de fin optionnelles HTML5, donc sur des tableaux ou listes non fermés, il mélange le texte adjacent sans signaler d’erreur. Si vos entrées peuvent être mal formées, écrites à la main ou anciennes, passez explicitement "lxml" ou "html5lib" — les deux ont obtenu un parfait 15/15 sur une matrice de HTML mal formé, là où html.parser s’est arrêté à 12/15.

BeautifulSoup peut-il analyser en parallèle avec des threads ? Non. La construction d’arbre de bs4 est en pur Python et conserve le GIL, donc ajouter des threads le rend plus lent, pas plus rapide — lors des tests, quatre threads ont rendu une analyse d’une page de 1 MB environ 3,9x plus lente qu’un seul thread. Pour paralléliser bs4, utilisez le multiprocessing (ProcessPoolExecutor). Les bibliothèques avec un cœur C, comme selectolax et lxml, sont celles qui profitent réellement du parallélisme par threads.

BeautifulSoup gère-t-il bien le HTML cassé ? Globalement oui — sur un ensemble de cas mal formés (balises mal imbriquées, squelette manquant, attributs non quotés, etc.), les trois backends ont retrouvé un résultat propre. Le seul point faible concerne html.parser et les balises de fin optionnelles : les <td> / <li> non fermés sont imbriqués au lieu d’être fermés, ce qui corrompt le texte extrait. Passez au backend lxml ou html5lib et ce type de problème disparaît.

BeautifulSoup vs lxml — lequel est meilleur ? Ce sont deux outils différents. lxml est beaucoup plus rapide pour la construction de l’arbre et pour les requêtes, et il prend en charge XPath. BeautifulSoup enveloppe lxml (entre autres) dans une API bien plus conviviale et dispose en réalité d’une meilleure couverture CSS via soupsieve. N’espérez simplement pas que le backend lxml rende bs4 aussi rapide qu’lxml — le backend n’accélère que le parsing, tandis que les requêtes et la traversée paient toujours le coût d’un objet Python par nœud, ce qui le laisse environ 6 à 7,5x plus lent sur de grandes sélections en batch.

Essayer Thunderbit pour l’extraction de données web Get Started Free

Ke
Ke
CTO chez Thunderbit | Data Scientist senior et expert en ML Forte d’une expérience de près de dix ans en machine learning et en data science, Ke Shen est diplômé de Columbia University et ancien Data Scientist senior chez Walmart Labs. Grâce à une expertise approfondie, reconnue par ses pairs, en Python, R, Java et statistiques, il partage des analyses éprouvées sur le passage d’algorithmes d’IA complexes de la théorie à une architecture prête pour la production.
Table des matières
Thunderbit · Agent de données web IA

Extrayez des données de n'importe quelle page en 1 clic

Approuvé par plus de 250 000 utilisateurs
plan gratuit disponible
Extraire des données avec l'IA
Transférez facilement des données vers Google Sheets, Airtable ou Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week