Chaque article sur le « parseur HTML Python le plus rapide » cite tôt ou tard selectolax, et tous s’arrêtent à « bien plus rapide que BeautifulSoup ». Cette partie est vraie. Ce que personne n’explique jusqu’au bout, c’est ce qui se passe quand on compare selectolax à lxml — parce qu’à ce moment-là, le mot « plus rapide » mérite un astérisque.
J’ai donc fait le benchmark sérieusement : selectolax (avec ses deux backends) face à lxml, BeautifulSoup avec html.parser et avec lxml, ainsi que parsel, sur cinq tailles de pages allant de 1 KB à 10 MB, chaque mesure étant la médiane de trois exécutions distinctes dans des processus séparés. selectolax a largement battu BeautifulSoup et a fait jeu égal avec lxml brut — puis a perdu sur l’étape de parsing pur face à lxml. Tous les chiffres ci-dessous sont provisoires et proviennent d’une seule machine (macOS arm64, Python 3.14.2) ; les scripts sont commités, donc testez-les sur votre machine avant de me citer.
Ce qu’est vraiment selectolax — et ce qu’il n’est pas
selectolax est un binding Python vers deux moteurs C — Modest et Lexbor — qui analysent du HTML5 et l’interrogent avec des sélecteurs CSS. Ce n’est ni un crawler, ni un navigateur, ni un « scraper » au sens où on cliquerait sur un bouton. C’est l’outil auquel vous passez un bloc HTML une fois la page déjà récupérée. La formule du mainteneur, en une ligne, est : « A fast HTML5 parser with CSS selectors, written in Cython, using Modest and Lexbor engines. »
Il existe deux backends, et la différence compte plus que ne le laisse penser la documentation :
LexborHTMLParser(moteur Lexbor) — celui que le README recommande d’utiliser en 2024.HTMLParser(moteur Modest) — l’original, dont la bibliothèque C sous-jacente « n’est plus maintenue », selon ce même README.
Avant d’entrer dans la vitesse, quelques points utiles. Au snapshot du dépôt pris le 2026-07-10, selectolax compte 1 653 étoiles et sa dernière version est la v0.4.10 (mai 2026) ; PyPI le limite à Python >=3.9,<3.15. L’installation est la partie la moins spectaculaire de tout ce test : pip install selectolax a téléchargé une wheel précompilée cp314 de 2,3 MB et a fonctionné immédiatement sous Python 3.14 — pas de téléchargement de navigateur, pas d’étape doctor, pas de compilation. C’est l’avantage discret d’un parseur pur par rapport à un outil adossé à un navigateur. On l’importe, et il fonctionne.
Petit point de licence à noter tout de suite : le binding Python est sous MIT, mais la wheel embarque les moteurs compilés, qui ont leurs propres licences — Modest est sous LGPL-2.1, Lexbor sous Apache-2.0. Dire que « selectolax est MIT » est donc vrai pour le code Python, mais incomplet pour le binaire que vous distribuez réellement. Si votre service juridique surveille les composants redistribués, c’est ce détail qu’il faut signaler.
La question de la vitesse, avec des chiffres concrets
Voici la tâche chronométrée : analyser la chaîne HTML, extraire tout le texte des <h3 class="title">, puis tous les href des <a>. Latence médiane en millisecondes, calculée comme médiane de trois exécutions distinctes ; l’écart entre les runs est resté sous ~5 % pour les parseurs reposant sur du C à la plupart des tailles. Avant de mesurer chaque cellule, j’ai réduit la sortie de chaque parseur à un hash de contenu pour détecter et exclure un parseur qui ferait silencieusement moins de travail — sur ces pages, les six résultats concordaient à chaque taille, donc il s’agit bien d’une comparaison équitable. Les données complètes se trouvent dans bench_parse.json.

| Page | selectolax (Lexbor) | selectolax (Modest) | lxml | parsel | BS (lxml) | BS (html.parser) |
|---|---|---|---|---|---|---|
| 1 KB | 0.027 | 0.029 | 0.036 | 0.044 | 0.281 | 0.323 |
| 10 KB | 0.160 | 0.171 | 0.166 | 0.228 | 1.705 | 2.049 |
| 100 KB | 1.464 | 1.565 | 1.423 | 2.020 | 16.5 | 20.6 |
| 1 MB | 14.901 | 16.9 | 14.177 | 20.9 | 181.9 | 232.6 |
| 10 MB | 159.9 | 247.9 | 172.9 | 231.9 | 2261.6 | 2788.7 |
Face à BeautifulSoup : environ 12 à 17 fois plus rapide, et la légende sous-estime encore l’écart
Si l’on convertit ces chiffres en ratios, selectolax-Lexbor ressort environ 12 fois plus rapide que BeautifulSoup(html.parser) sur une page de 1 KB, jusqu’à environ 17 fois à 10 MB, et environ 10 à 14 fois plus rapide que BeautifulSoup(lxml) sur la même plage. Le chiffre qu’on voit circuler en ligne — « selectolax est environ 4 à 5 fois plus rapide que BeautifulSoup » — est trop faible face à html.parser et à peu près juste uniquement face à BeautifulSoup adossé à lxml. Le multiplicateur réel dépend de la version de BeautifulSoup dont on parle et de la quantité de données extraites par page.
Cela concorde aussi avec le benchmark du README, qui suggère un avantage de 25,5x sur BeautifulSoup(html.parser). Aucun des deux chiffres n’est faux. La tâche du README (titre, liens, scripts et métadonnées sur de petites pages d’accueil) effectue moins d’extraction sur des pages plus petites, ce qui pèse davantage sur l’overhead par parsing de BeautifulSoup. En pratique, on peut résumer ainsi : selectolax est environ 10 à 15 fois plus rapide que BeautifulSoup sur un travail réaliste d’analyse et d’extraction, avec un avantage plus marqué sur les petites pages et les extractions légères.
Si votre principal goulot d’étranglement est aujourd’hui une masse de code BeautifulSoup qui traite des pages, c’est une migration qui se rentabilise vite. Ce cas-là ne fait pas débat. Le suivant, si.
Face à lxml : égalité — et lxml gagne la partie que tout le monde oublie d’isoler
Revenons aux lignes 100 KB et 1 MB. Lexbor et lxml sont à moins de 5 % l’un de l’autre, leurs intervalles par exécution se chevauchent, et selon ma méthodologie c’est une égalité — pas de vainqueur, pas de « plus rapide ». Le seul point où selectolax prend un avantage net, c’est sur la page de 10 MB (159,9 ms contre 172,9 ms, soit un écart de 8,1 % avec intervalles non chevauchants). Sur la tâche complète, selectolax rejoint donc lxml et ne le dépasse réellement que sur les très gros documents.

J’ai ensuite séparé la construction de l’arbre de l’interrogation CSS, et le résultat change de sens d’une manière que beaucoup d’articles ratent. Pour le parsing pur, sans aucune requête, lxml était systématiquement environ 33 à 34 % plus rapide que selectolax-Lexbor sur cette machine — 77,9 ms contre 116,6 ms sur la page de 10 MB. Sur la tâche complète, les deux se rejoignent de toute façon, et mon hypothèse de travail (pas quelque chose que j’ai prouvé par une expérience d’attribution) est que, sur ces pages, la requête CSS ne représente qu’une petite part du temps total, si bien que l’avance de lxml sur la phase de parsing se dilue jusqu’à convergence des totaux.
C’est l’affirmation la plus attaquable de tout ce test, et je préfère être transparent sur les raisons. Elle contredit la sagesse courante, et le seul benchmark publié que j’ai trouvé et qui isole le parsing pur — aows.jpt.sh — rapporte l’inverse, avec selectolax environ 4 fois plus rapide. J’ai donc cadré le test : résultat limité à une seule plateforme (macOS arm64, Python 3.14, wheels cp314 précompilées — un build Linux x86_64 ou depuis les sources n’a pas été testé), vérifié sur quatre tailles de page et stable à chaque fois, puis revalidé avec deux API lxml différentes pour écarter un artefact d’API. Les deux API lxml battent selectolax-Lexbor à toutes les tailles. Je ne présente pas « lxml parse plus vite » comme une vérité définitive — je présente ce que mon bench a produit, script à l’appui, en contradiction avec la plupart des chiffres publiés. À vous de le rejouer chez vous.
Un autre angle : sur 100 000 <a> dans une page plate, lxml et selectolax-Modest font jeu égal (33,30 ms contre 34,19 ms, intervalles qui se chevauchent), tandis que selectolax-Lexbor est derrière les deux d’environ 15 %. Les trois moteurs C ont en commun d’être 5 à 7 fois plus rapides que parsel ou BeautifulSoup pour les sélections massives, car le modèle « un objet Python par nœud » ralentit vraiment les choses. Donc l’affirmation « selectolax est le plus rapide pour les sélections CSS massives » ne tient pas non plus — Modest fait simplement jeu égal avec lxml, et Lexbor lui est légèrement inférieur.
La conclusion que je défendrais vraiment : l’avantage de selectolax sur lxml n’est pas une supériorité nette sur toute la ligne. Il ne l’emporte que sur les plus grandes pages. Son intérêt repose sur d’autres points — ergonomie de l’API, comportement sur les entrées sales, CSS moderne — et c’est là que se situe la suite de cette analyse.
Mémoire et démarrage à froid : classez selon le RSS, pas selon votre profiler
La mémoire est le point où je dois corriger mes propres chiffres antérieurs, et cette correction est justement instructive. Mesurée comme delta RSS sur la page de 10 MB avec tracemalloc désactivé, BeautifulSoup consomme environ 1,5 à 1,8 fois plus de mémoire que selectolax ou lxml — l’écart va de 1,51x (BS-lxml à 218,4 MB contre Lexbor à 144,6 MB) jusqu’à 1,75x en haut de la fourchette. selectolax et lxml se situent tous deux dans la catégorie légère ; lxml est le plus sobre en RSS.

Un précédent passage indiquait « ~3x », et ce chiffre était faux pour une raison utile à connaître : il avait été mesuré avec tracemalloc actif, et la comptabilité par allocation de tracemalloc double à peu près le RSS apparent du parseur qui alloue le plus. Donc, rappel pour quiconque mesure la mémoire d’un parseur : classez selon le RSS, profiler éteint. Classer les parseurs via le pic tracemalloc désordonne précisément les moteurs C : cela faisait paraître selectolax-Lexbor plus lourd que Modest alors qu’en RSS réel ils sont proches. BeautifulSoup est bien le plus gourmand ici ; simplement, il ne l’est pas dans l’ordre de grandeur 3x que mon instrument contaminé laissait croire.
Le démarrage à froid compte moins, mais il est réel : selectolax s’importe en environ 14 ms, donc à peu près au niveau d’lxml et environ 2,3x plus vite que bs4 ou parsel. Si vous livrez un outil CLI ou une fonction serverless où le temps d’import fait partie de chaque appel, cet écart mérite d’être noté.
Couverture des sélecteurs CSS : solide, avec quelques vraies lacunes
La couverture CSS a été testée sur une matrice de 41 cas, chaque sélecteur étant confronté à un jeu de réponses connues, avec en plus une phase volontairement agressive conçue pour faire tomber le moteur Lexbor. Chaque cas a été exécuté dans son propre sous-processus, ce qui s’est avéré nécessaire — l’un d’eux fait planter tout l’interpréteur. Résultats :

| Moteur | PASS | FAUX | NON PRIS EN CHARGE | PROCESS_ABORT |
|---|---|---|---|---|
| soupsieve | 41 | 0 | 0 | 0 |
| selectolax Lexbor | 39 | 0 | 2 | 0 |
| lxml (cssselect) | 37 | 1 | 3 | 0 |
| parsel (cssselect) | 37 | 1 | 3 | 0 |
| selectolax Modest | 35 | 3 | 2 | 1 |
Une fois des sélecteurs hostiles introduits, Lexbor n’est pas le vainqueur absolu — c’est soupsieve, avec un 41/41 propre contre 39/41 pour Lexbor. Les deux échecs de Lexbor sont :lang(en) et :dir(rtl), qu’il rejette avec une erreur de parsing. Il est parfait sur le reste, y compris :has(), :is(), :where() et les attributs insensibles à la casse.
Là où Lexbor brille vraiment, c’est face à la pile cssselect. Le sélecteur vedette du README — div > :nth-child(2n+1):not(:has(a)) — renvoie le bon ensemble avec les deux moteurs selectolax et avec soupsieve, mais le mauvais ensemble avec lxml et parsel, sans aucune erreur. Un scraper qui recopie ce sélecteur dans Scrapy ou parsel obtient des résultats silencieusement faux. Pour être précis : cssselect sait parser :has() depuis la version 1.2.0 (2022), et j’ai testé la 1.4.0 ; il s’agit donc ici de « supporté mais mal évalué dans le sélecteur composé », pas de « non supporté ». Ce comportement silencieusement faux sur ce composé particulier ne figure pas dans le tracker cssselect, qui documente les limites de :has() comme des erreurs levées. Lexbor gère aussi le drapeau d’attribut insensible à la casse [data-role="LEAD" i], que cssselect refuse purement et simplement.
Deux limites décident toutefois des migrations. selectolax ne prend pas du tout XPath en charge — aucun backend n’expose xpath() — et il ne gère pas non plus les pseudo-éléments ::text / ::attr(), qui relèvent d’une extension de parsel/Scrapy et non du vrai CSS. Si vos scrapers actuels s’appuient sur XPath, c’est là que se trouve le principal obstacle : vous réécrirez les sélecteurs, au lieu de simplement remplacer une bibliothèque. À l’inverse, Lexbor fournit une pseudo-classe :lexbor-contains("text" i) pour la recherche de texte insensible à la casse, que ni lxml, ni parsel, ni le CSS standard n’offrent, et elle fonctionne comme documenté.
Robustesse face au HTML sale, là où selectolax justifie vraiment son existence
Le scraping réel consiste à donner du HTML cassé à un parseur en espérant qu’il ne s’écroule pas. J’ai lancé 18 entrées adversariales, et c’est dans cette catégorie que le dossier de selectolax contre lxml est le plus solide.
Si vous donnez à lxml.html.fromstring une chaîne vide ou des espaces, il lève ParserError("Document is empty"). Les deux moteurs selectolax renvoient, eux, un arbre vide valide. Pour un scraper qui parcourt une liste d’URLs dont certaines réponses reviennent vides, c’est un try/except de moins à entourer partout. selectolax a aussi encaissé 100 000 éléments sans débordement de pile.
L’imbrication profonde a produit l’écart le plus net. Sur 1 000 et 5 000 niveaux de <div> imbriqués, lxml supprime silencieusement le contenu le plus profond tandis que selectolax le conserve. libxml2 plafonne la profondeur de parsing autour de 256 niveaux et tronque l’arbre sans signalement, donc le texte le plus profond devient tout simplement inaccessible. Les deux moteurs selectolax renvoient l’arbre complet. C’est l’image inversée du piège <template> que j’aborde juste après : ici Lexbor perd du contenu que les autres gardent ; là, lxml perd du contenu que selectolax garde.
Tous les cas n’étaient pas gagnés. Le backend Modest fait s’arrêter tout l’interpréteur Python avec un SIGABRT lorsqu’il rencontre :dir() — pas une exception levée que l’on peut attraper, mais un arrêt brutal du processus. C’est une vraie réserve de robustesse pour quiconque utilise encore le backend historique, et exactement le genre de défaut invisible jusqu’au jour où il fait tomber un job de production à 3 h du matin.
Deux pièges de perte silencieuse à connaître avant de passer en production
Aucune de ces deux choses n’est une découverte — elles sont toutes deux documentées en amont — mais elles peuvent faire perdre des données bien réelles, silencieusement, et elles ne sautent pas aux yeux dans le README.
Lexbor ignore les <a> à l’intérieur de <template>
Sur la page MDN en production que j’ai testée, selectolax-Lexbor a trouvé 497 liens, alors que lxml, les deux backends BeautifulSoup, et même le backend Modest de selectolax en ont trouvé 508. Les onze manquants étaient un sélecteur de langue et un lien vers les discussions placés dans des éléments <template> (la page utilise des composants web Lit).

La cause est légitime : selon la spécification HTML5, le contenu de <template> est analysé dans un fragment inert séparé, et non dans le DOM normal, et Lexbor s’y conforme strictement — tree.css("a") ne descend pas dans le contenu des templates. lxml, les deux backends BeautifulSoup et Modest aplatissent le contenu du template dans l’arbre principal, donc ils retrouvent ces liens. C’est un problème ouvert documenté (selectolax#146, avec la cause racine côté moteur dans lexbor#170), et les deux lectures se défendent — Lexbor est probablement la plus conforme au standard. Mais un développeur qui utilise le backend recommandé rate ces données en silence, sans erreur. Il faut aussi dire l’inverse : les autres parseurs exposent du contenu inert que le navigateur ne rendrait jamais, donc ils peuvent vous fournir des données fantômes qu’un utilisateur ne peut pas voir. La solution fiable, pour cette page précise, est le backend Modest ou une autre bibliothèque.
Des octets non UTF-8 corrompent silencieusement .text()
Si vous passez à selectolax des bytes qui ne sont pas du UTF-8 valide, le parsing réussit — la corruption n’apparaît qu’ensuite, et c’est pire qu’un crash net. Sur "<p>café éè</p>".encode("latin-1"), .text() de Lexbor renvoie des caractères de remplacement, .text() de Modest supprime silencieusement les octets fautifs, et les deux moteurs ne lèvent UnicodeDecodeError que lorsque vous consultez .html. Le binding décode en UTF-8 strict au moment de la lecture, pas au moment du parsing. Cela se rattache à un problème connu de selectolax concernant la stricte symétrie encode/décode.
La correction tient en une ligne et devrait devenir un réflexe : décoder vous-même les bytes au préalable — LexborHTMLParser(resp.content.decode("latin-1")) — et les deux moteurs renvoient correctement 'café éè'. En pratique, donnez toujours à selectolax une str, jamais des bytes bruts non UTF-8. Le README ne le précise pas.
Dimensions de production (une seule observation, donc à prendre comme indications)
Les résultats suivants ont été mesurés une seule fois, et non sur trois runs, donc je les présente comme des signaux plutôt que comme des valeurs figées.
Le plus intéressant concerne l’échelle avec les threads. En analysant une page de 1 MB 48 fois sur quatre threads, selectolax a affiché un gain de temps réel de ~3,5 à 3,9x — la signature empirique d’une bibliothèque qui libère le GIL pendant le parsing C — tandis que BeautifulSoup(lxml) devenait plusieurs fois plus lent en mode multithread, ce qui ressemble à un travail sérialisé par le GIL. lxml se situe entre les deux et reste non concluant. À l’ère du free-threading vers laquelle Python se dirige, un selectolax qui parallélise le parsing sur plusieurs threads là où BeautifulSoup ne le fait pas constitue un avantage réel, même s’il reste provisoire. Il s’agit d’un seul nombre de threads, sur une seule taille de page, et le mécanisme reste une hypothèse, pas quelque chose que j’ai confirmé en instrumentant le code C.
Côté fuites mémoire : sur 2 000 itérations analyse-extraction-retrait à 1 MB, aucun des trois parseurs n’a montré la montée linéaire du RSS qui signalerait une fuite — chacun est resté dans une bande de mémoire bornée. Je fais confiance à ce résultat en particulier parce que j’ai soumis au même instrument un sujet de calibration connu pour fuir, et il est monté à +198 MB comme prévu, preuve que l’instrument savait voir une fuite et n’en a simplement trouvé aucune dans ces parseurs. Et un handle de nœud conservé en vie après la sortie de portée de son arbre n’a posé aucun problème, sans segfault. Tout cela repose sur une seule observation, pas sur un test de plusieurs heures.
Où selectolax s’insère — et où il passe la main
Tout ce qui précède concerne une seule tâche : transformer du HTML déjà récupéré en données structurées, vite. selectolax excelle dans cette tâche. Ce qu’il ne fait volontairement pas, c’est récupérer la page, rendre le JavaScript, faire tourner des proxies, résoudre des CAPTCHAs ou décider quels éléments vous voulez. Tout cela relève encore de votre code. selectolax est la couche de parsing, et il ne prétend pas être davantage.
C’est là qu’un service d’extraction managée se situe au-dessus d’un parseur plutôt qu’à sa place. Si vous préférez ne pas construire et maintenir vous-même la chaîne fetch-render-anti-bot-extract, Thunderbit l’expose via une API, un serveur MCP et une CLI — POST /distill transforme une page en Markdown propre et POST /extract renvoie un JSON structuré conforme au schéma, avec rendu JS et protection anti-bot pris en charge pour vous. C’est une autre couche du problème : vous utiliserez selectolax lorsque vous avez déjà le HTML et que vous voulez de la vitesse de parsing brute sous votre propre contrôle, et plutôt une solution comme l’API, le serveur MCP ou la CLI de Thunderbit lorsque vous voulez déléguer la récupération et l’extraction pour ne récupérer que des données structurées. Pas un remplacement — un autre niveau dans la même pile.
Essayez Thunderbit pour l’extraction de données web
Avantages, limites et profil des utilisateurs concernés
Là où selectolax gagne :
- Environ 12 à 17 fois plus rapide que BeautifulSoup sur un travail réaliste d’analyse et d’extraction, de façon stable sur trois ordres de grandeur de taille de page.
- Mémoire légère (niveau lxml, environ 1,5 à 1,8x plus économe que BeautifulSoup) et import d’environ 14 ms.
- Robuste sur les entrées qui font échouer lxml — pages vides, espaces et imbrication pathologiquement profonde.
- CSS moderne incluant
:has(),:is(),:where(), les attributs insensibles à la casse et le:lexbor-contains()propre à Lexbor. - Un DOM sûr avec
None: les éléments manquants renvoientNoneou[]au lieu de lever une exception, et l’on peut réellement modifier puis réexporter l’arbre. - Maintenance active (v0.4.10, mi-2026) et installation triviale.
Là où il ne gagne pas :
- Pas nettement plus rapide qu’lxml — égalité sur la tâche complète, et perte sur le parsing pur dans mon bench.
- Pas d’XPath, ni de
::text/::attr()— mur de migration net pour les scrapers basés sur XPath. - Deux pièges de perte silencieuse : contenu
<template>avec Lexbor, et bytes non UTF-8 via.text(). - Le backend Modest est historique et peut provoquer un SIGABRT sur
:dir(). - Tous les chiffres ici sont limités à une seule plateforme (macOS arm64, Python 3.14) et restent provisoires.
Faut-il utiliser selectolax ? Oui, si vous voulez des performances de parsing du niveau d’lxml avec une API plus agréable, sûre face à None, et un comportement nettement meilleur sur les entrées vides ou mal formées — à condition d’accepter de rester dans un univers CSS uniquement. Si votre base de code repose sur XPath, le coût de réécriture est réel et doit être pesé honnêtement. Et si vous cherchez « le parseur le plus rapide », la réponse exacte issue de ce bench est que selectolax et lxml sont assez proches pour que le critère décisif soit l’ergonomie et la robustesse, pas la vitesse brute. C’est déjà une excellente raison de choisir un outil.
Essayez Thunderbit pour l’extraction de données web Get Started Free
FAQ
selectolax est-il plus rapide que BeautifulSoup ?
Oui, clairement — environ 12 à 17 fois plus rapide que BeautifulSoup(html.parser) et 10 à 14 fois plus rapide que BeautifulSoup(lxml) sur une tâche réaliste d’analyse et d’extraction, avec des résultats stables de 1 KB à 10 MB (macOS arm64, Python 3.14). Le chiffre souvent cité de « 4 à 5x » sous-estime l’écart face à html.parser.
selectolax est-il plus rapide qu’lxml ? Pas de façon générale. Sur la tâche complète d’analyse et d’extraction, ils sont à égalité à 100 KB et 1 MB, et selectolax ne gagne que sur la page de 10 MB. Sur le parsing pur sans requête, lxml était en réalité environ 33 à 34 % plus rapide sur ma machine — un résultat à contre-courant que je limite à une seule plateforme, donc à vérifier sur votre propre matériel.
Faut-il utiliser le backend Lexbor ou Modest ?
Lexbor, dans presque tous les cas — c’est le moteur maintenu et complet recommandé par le README, avec une meilleure couverture CSS. La seule exception est une page qui cache du contenu dans des éléments <template>, où le comportement conforme de Lexbor supprime ce contenu alors que Modest le conserve par hasard. Modest a aussi des arêtes vives, dont un crash brutal de l’interpréteur sur :dir().
selectolax prend-il en charge XPath ?
Non. Aucun des deux backends n’expose de méthode xpath() — selectolax est limité au CSS. Si vos scrapers reposent sur XPath, migrer implique de réécrire vos sélecteurs, et c’est le coût principal du passage depuis une pile basée sur lxml ou parsel.
Pourquoi ma sortie selectolax est-elle corrompue ou incomplète ?
Deux suspects habituels. Si le texte revient avec des caractères de remplacement ou des accents manquants, vous avez probablement passé des bytes bruts non UTF-8 — décodez-les d’abord en str (resp.content.decode("latin-1")) avant de parser. Si des liens ou éléments manquent sur un site moderne, ils se trouvent peut-être dans des balises <template> que le backend Lexbor ne parcourt pas ; basculez vers Modest ou un autre parseur pour cette page.


