Parmi les jeux de test où une fuite est réellement mesurable, goose3 a obtenu une précision de 1,0000 sur les jetons de contenu et zéro jeton parasite. Pas un seul mot de navigation, de publicité, de barre latérale, de commentaire ou de promotion n’a fini dans sa sortie. Aucun autre outil dans cette comparaison à six bibliothèques n’a fait aussi bien.
En revanche, il affiche le plus mauvais rappel d’article de l’ensemble — 0,8243 sur 22 jeux de test, contre 1,0000 pour Mozilla Readability — parce que sur deux d’entre eux il a renvoyé une chaîne vide.
Ces deux métriques sont liées par la règle de score : une sortie vide n’ajoute rien à la précision conditionnelle, tandis que le rappel enregistre l’échec.
Qu’est-ce que goose3 ?
goose3 est la continuité Python 3 d’une lignée née avec Goose de Gravity Labs en Scala, puis prolongée par python-goose. C’est un extracteur d’articles avec métadonnées, pas un simple récupérateur de texte : on instancie Goose, on appelle extract(), puis on récupère un objet Article avec une vingtaine de champs accessibles — texte nettoyé, titre, auteurs, date de publication, image principale, méta description, tags, liens, tweets, et plus encore.
Référence officielle : dépôt officiel de goose3.

Version testée : 3.1.22, sous licence Apache, 912 étoiles GitHub, dernier push le 2026-07-23 — projet actif au moment du test. Python 3.14.2.
L’API tient en deux appels et une obligation :
from goose3 import Goose
g = Goose()
try:
article = g.extract(raw_html=html)
text = article.cleaned_text
finally:
g.close() # fermer explicitement après usage
Ce close() mérite d’être signalé, car on l’oublie facilement et rien ne vous avertit. Ce test n’a pas exécuté de boucle de surveillance pour quantifier les sessions, connexions ou la mémoire retenues lorsque la fermeture est omise ; parler de « fuite de ressources » irait donc plus loin que les preuves disponibles. Considérez la fermeture explicite comme une exigence de cycle de vie telle qu’imposée ici par l’usage de l’API.
Le compromis, mesuré
Six extracteurs, un jeu de fixtures annotées, un scoreur unique. Chaque bloc de chaque fixture est étiqueté article ou boilerplate et porte un jeton sentinelle unique ; ainsi, « a-t-il récupéré cette unité ? » se réduit à une appartenance exacte à une sous-chaîne, et non à un score de similarité.
| Bibliothèque | Rappel d’article (22 au total) | Fuite de bruit | Précision sur les jetons de contenu | Jetons contaminants | Sortie produite |
|---|---|---|---|---|---|
| Readability | 1.0000 | 0.2353 | 0.9109 | 35 | 22/22 |
| trafilatura | 0.9865 | 0.0588 | 0.9411 | 4 | 22/22 |
| newspaper4k | 0.9865 | 0.0000 | 0.9452 | 0 | 22/22 |
| resiliparse | 0.9054 | 0.0588 | 0.9381 | 7 | 22/22 |
| jusText | 0.8378 | 0.4706 | 0.8760 | 74 | 19/22 |
| goose3 | 0.8243 | 0.0000 | 1.0000 | 0 | 20/22 |
sixway-scores.json. Le rappel est calculé sur les 22 fixtures ; le taux de fuite et la précision sur les 11 qui contiennent à la fois des unités d’article et de boilerplate.
Lisez toujours la colonne de précision en même temps que la dernière colonne. Ici, la précision est conditionnelle à la production d’une sortie : une bibliothèque qui renvoie une chaîne vide sur une fixture n’ajoute rien ni au numérateur ni au dénominateur, donc se dérober ne coûte rien dans cette moyenne. Le numérateur est l’intersection multiensemble entre les jetons extraits hors stop words et les jetons d’article balisés ; le dénominateur correspond à tous les jetons extraits hors stop words. Les « jetons contaminants » sont plus ciblés : ils ne comptent que les chevauchements avec les jetons de boilerplate balisés. Un jeton extrait supplémentaire qui ne correspond ni à l’article balisé ni au boilerplate balisé fait baisser la précision sans augmenter ce compteur de contamination ; des répétitions au-delà du multiensemble de l’article peuvent produire le même effet. Voilà pourquoi newspaper4k peut afficher 0 jeton contaminant tout en restant sous 1,0000 de précision. goose3 a obtenu 1,0000 sur 10 des 11 fixtures ; Readability, trafilatura, newspaper4k et resiliparse ont été évalués sur 11 sur 11.
Le 1,0000 reste utile dans ce corpus synthétique. Sur les dix fixtures scorées, goose3 n’a produit aucun des jetons de boilerplate balisés ; Readability en a produit 35 sur les mêmes pages. Si un modèle consomme la sortie, cela signifie zéro jeton dépensé sur les étiquettes de boilerplate présentes dans ces dix fixtures. Cela ne démontre pas l’absence totale de gaspillage sur de vraies pages, et une sortie vide peut déclencher ailleurs dans le pipeline des coûts de repli ou de relance.
Les deux silences, et ce qu’ils signifient
goose3 n’a rien renvoyé sur exactement deux fixtures. L’une est défendable, l’autre révèle une vraie limite.
Le document quasi vide. Une page avec une seule unité d’article de 32 caractères. goose3 refuse. jusText aussi. Dans ce jeu de test, Readability a produit une sortie sur les 22 pages, donc son résultat ne soutient pas le silence de goose3 ici. Que le refus d’un document aussi minuscule soit acceptable dépend du contrat minimal de contenu attendu par l’appelant.

L’article composé uniquement d’éléments <li>. Six unités d’article, aucune dans une balise <p>. goose3 renvoie une chaîne vide.
Ce second cas m’a intrigué, car la configuration par défaut de goose3 indique parse_lists=True. J’ai donc poussé le test — trois configurations comparées à un contrôle fonctionnel, parce qu’un seul essai infructueux ne suffit pas à conclure sur une bibliothèque :
| Configuration | Page composée uniquement de listes | Contrôle avec <p> |
|---|---|---|
| valeurs par défaut | 0 caractères | 937 caractères |
strict=False | 0 caractères | 937 caractères |
parse_lists=True (explicite) | 0 caractères | 937 caractères |
Zéro dans les trois cas, tandis que le contrôle renvoie 937 caractères dans les trois cas. Donc parse_lists=True décide si les listes sont conservées à l’intérieur d’un article déjà détecté par goose3 — cela ne permet pas au scoreur de candidats de traiter une liste comme l’article lui-même. Le scoring des nœuds de goose3 a besoin de blocs de forme paragraphe pour repérer le corps, et une page dont le corps est une liste n’en possède pas.
Le résultat supporté est plus étroit : un corps au format de cette fixture synthétique — six unités d’article, toutes en <li>, sans candidat paragraphe — a renvoyé une chaîne vide. Les changelogs, références d’API, recettes, pages FAQ et articles comparatifs sont des cas de risque plausibles pour une relecture sur des pages réelles, car ils peuvent être très orientés listes, mais cette seule fixture ne prouve pas que ces catégories échouent de manière générale.
La chaîne vide n’est détectable automatiquement que si l’appelant valide explicitement qu’il ne s’agit pas d’une sortie vide. Elle est plus facile à bloquer qu’un texte crédible mais vide de contenu utile, cependant elle reste un échec silencieux si la surveillance repose uniquement sur les exceptions. Un appel en production doit donc prévoir un contrôle de volume minimal et un mécanisme de repli ou un enregistrement explicite de l’échec de page.
Réalité d’installation
pip install goose3 installe 16 paquets et 44,3 MiB en environ 6 à 9 secondes. Import à froid mesuré dans un sous-processus neuf : 2,181 s.
Référence officielle : goose3 sur PyPI.
| Bibliothèque | Paquets | site-packages | Import à froid | Extraction p50 |
|---|---|---|---|---|
| resiliparse | 5 | 21.0 MiB | 0.015 s | 0.06 ms |
| jusText | 3 | 22.4 MiB | 0.777 s | 0.56 ms |
| goose3 | 16 | 44.3 MiB | 2.181 s | 1.85 ms |
| newspaper4k | 22 | 47.5 MiB | 2.812 s | 2.69 ms |
| trafilatura | 17 | 69.9 MiB | 1.584 s | 0.51 ms |
install-and-import.json. Chaque bibliothèque était dans son propre virtualenv vide, donc aucun héritage de poids d’un voisin.
Poids moyen, vitesse moyenne. L’installation et l’import se sont déroulés sans problème sur Python 3.14.2, ce qui n’est pas universel dans cette catégorie.
Trois valeurs par défaut à connaître avant le déploiement

La lecture de l’objet Configuration livré avec la bibliothèque, plutôt que de la documentation, fait apparaître dix-neuf paramètres. Trois d’entre eux surprendront forcément quelqu’un.
Il s’identifie lui-même. browser_user_agent vaut par défaut Goose/3.1.22. Si vous laissez goose3 effectuer lui-même les requêtes, chaque serveur contacté voit le nom de la bibliothèque et sa version exacte dans les logs. C’est transparent, mais aussi une empreinte numérique. Définissez-la volontairement, ou récupérez vous-même le HTML et passez raw_html.
Il pointe vers un binaire MacPorts. imagemagick_convert_path vaut par défaut /opt/local/bin/convert et imagemagick_identify_path /opt/local/bin/identify. Sur ma machine, aucun des deux n’existe — /opt/local correspond à MacPorts, que la plupart des gens n’ont pas ; Homebrew installe plutôt ses binaires dans /opt/homebrew. Ce réglage est inactif tant que vous n’activez pas la récupération d’images (enable_image_fetching est False par défaut, ce qui est cohérent), mais si vous l’activez en pensant que l’extraction de l’image principale fonctionnera, c’est ici que cela échouera sans bruit.
Il suppose l’anglais. target_language vaut en par défaut avec use_meta_language=True, donc il suivra la langue déclarée par la page lorsqu’elle existe et reviendra à l’anglais sinon. Très bien pour des contenus anglophones, mais à définir explicitement pour autre chose.
Le reste est raisonnable : parser_class est lxml, http_timeout à 30 secondes, strict activé, log_level sur ERROR, parse_headers et keep_footnotes activés, images_min_bytes à 4 000.
Mémoire, et effet du HTML cassé
Deux éléments que chaque test de cette série indiquait comme non testés sont maintenant mesurés.
Le contexte plus large du stress-test figure dans la comparaison mémoire et HTML malformé pour dix bibliothèques.
Pic de mémoire résidente, via /usr/bin/time -l, un processus neuf par cellule — le plancher d’import correspond au coût de la bibliothèque chargée mais inactive, 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 inclus dans les deux.
goose3 a un plancher de 44,1 MiB et atteint 398,5 MiB sur un document de 10 Mo. Son plancher d’import est le troisième plus élevé parmi les bibliothèques Python présentées, ce qu’il faut garder en tête pour un déploiement sensible au temps de démarrage.
HTML cassé. Douze documents, chacun cassant exactement une chose — balises non fermées, imbrication incorrecte d’éléments inline, attributs non quotés contenant des espaces, balises de fermeture orphelines, absence totale de <html>, attributs dupliqués, document tronqué au milieu d’une balise, entités invalides, <script> non fermé, déclaration de charset 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 renvoyé » ne dit quelque chose sur la malformation que si la bibliothèque reste silencieuse aussi sur un document propre de même taille.
goose3 a levé une exception dans 0 cas sur 14 et n’a rien renvoyé dans 10 cas, récupérant 2/33 jetons sentinelles sur les fixtures cassées (malformed-results.json). Une fixture est exclue de ce comptage : selon HTML5, tout ce qui suit un <script> non fermé fait partie du script ; le perdre est donc correct, tandis que le récupérer serait l’écart.
Avantages et inconvénients
Ce qui joue en sa faveur. Zéro jeton de boilerplate balisé sur les dix fixtures de fidélité de contenu où il a produit une sortie. Environ vingt-huit champs d’article sont disponibles, même si leur exactitude a été inventoriée plutôt que notée. Valeur par défaut raisonnable pour la récupération d’images (désactivée). Installation propre sur Python 3.14. Projet actif. Apache-2.0. Une sortie vide se bloque facilement si l’appelant la vérifie explicitement.
Ce qui joue contre lui. Le plus faible rappel d’article du lot, à 0,8243, dû entièrement au fait de ne rien renvoyer du tout plutôt qu’à renvoyer la mauvaise chose. Une page dont l’article est une liste produit une chaîne vide, quelle que soit la configuration. 44,3 MiB et un import à froid de 2,2 secondes, c’est lourd face aux 21,0 MiB et 15 ms de resiliparse. Nécessite close(). Deux paramètres par défaut pointent vers des emplacements faux sur la plupart des machines.
Qui devrait l’utiliser, et qui devrait s’en abstenir
Utilisez goose3 comme candidat lorsque le texte extrait alimente un modèle ou une base de données où le boilerplate balisé coûte cher, et que les pages sont des articles classiques en paragraphes. Sur ce jeu de fixtures, il n’a produit aucun jeton de boilerplate balisé lorsqu’il a répondu. La couche métadonnées est disponible mais n’a pas été validée ici ; l’exactitude du titre, des auteurs, de la date et de l’image nécessite des fixtures de vérité terrain distinctes avant d’entrer dans un choix d’outil.
Évitez-le si votre corpus est riche en listes — vous obtiendrez des chaînes vides sans explication. Évitez-le si le coût de démarrage à froid compte, car resiliparse s’importe 145× plus vite. Et évitez-le si vous avez besoin d’une réponse sur chaque page, car « pas de réponse » est bien un résultat ici : deux pages sur 22, et toutes deux silencieusement, au sens où une chaîne vide n’est pas une exception.
Une combinaison à tester : goose3 en premier recours avec une solution de repli lorsque cleaned_text est vide ou sous votre seuil minimal de contenu. Readability a récupéré toutes les unités d’article dans ce jeu de 22 fixtures, y compris les deux cas vides de goose3. Ce résultat synthétique valide le schéma d’architecture, pas la promesse qu’un fallback ne ratera jamais rien sur des pages réelles.
Où une API managée trouve sa place
Ce benchmark a testé la voie d’extraction raw_html de goose3 : le HTML était déjà acquis avant que goose3 ne le voie. goose3 possède aussi sa propre voie de récupération réseau, comme le montre son paramètre User-Agent, mais cette voie n’a pas été testée ici. Le rendu JavaScript et les mécanismes anti-bot n’ont pas été testés non plus.
Pour les mêmes fixtures sur les six extracteurs, voir la comparaison d’extraction sur six bibliothèques.
Un service managé de récupération/rendu/extraction, y compris notre propre Thunderbit, couvre un autre périmètre de responsabilité. Thunderbit n’a pas été benchmarké dans ce test. La distinction pertinente est celle entre extraction d’article à partir de HTML fourni et service hébergé qui récupère puis traite une URL ; cet article ne fournit aucune comparaison de performance ou de qualité sur les mêmes métriques.
La comparaison équitable : l’ensemble de champs de goose3 est figé et orienté article, ce qui est parfait quand vos pages sont des articles et inadapté lorsqu’il s’agit de fiches produit. Si vous avez déjà le HTML et que vos pages sont des articles, goose3 est gratuit et très propre.
Pour la partie hébergée, notre tour d’horizon des API de web scraping donne une vision plus large ; pour les alternatives open source auto-hébergées, voir le pilier des extracteurs open source. Si le texte doit alimenter un modèle, convertir du HTML en Markdown en Python montre où la fidélité se perd réellement.
Essayer Thunderbit pour l’extraction de données web
Faut-il utiliser goose3 ?
Oui, si les articles en forme de paragraphes correspondent à votre charge de travail et si l’appelant traite une sortie vide comme un échec d’extraction, non comme un succès.
Sur ce jeu de fixtures, goose3 n’a produit aucun jeton de boilerplate balisé lorsqu’il a répondu et a renvoyé deux chaînes vides. L’une concernait une page quasi vide, l’autre le corps synthétique composé uniquement de listes. C’est un compromis entre précision et couverture, pas une preuve d’un tempérament propre à tout le produit.
Si le rappel compte davantage, testez un fallback avec une garde explicite sur la taille minimale de sortie. Readability a récupéré toutes les unités d’article dans ces 22 fixtures ; newspaper4k a affiché zéro fuite de boilerplate balisé et 0,9865 de rappel tout en produisant une sortie sur les 22. Ces résultats classent ce corpus synthétique selon les réglages par défaut, pas selon des charges de production inconnues.
goose3 mérite sa place lorsque le coût d’un mot erroné dépasse celui d’une page manquante.
Essayer Thunderbit pour l’extraction de données web Get Started Free
FAQ
La précision parfaite de goose3 est-elle réelle, ou simplement liée au fait qu’il se retire ? Les deux, et on peut les distinguer. Il a été évalué sur 10 des 11 fixtures contenant du boilerplate, donc une fixture manque à la moyenne — cette part vient bien de son refus de répondre. Mais sur ces dix-là, il a renvoyé zéro jeton contaminant face à un boilerplate délibérément hostile, alors que Readability en a laissé passer 35. La précision est réelle sur les pages auxquelles il répond ; le rappel, lui, reflète les cas où il se retire.
Pourquoi goose3 ne renvoie-t-il rien quand l’article est une liste ?
Son scoreur de candidats a besoin de blocs en forme de paragraphe pour localiser le corps de l’article, et une page construite avec des <li> n’en a pas. Le réglage par défaut parse_lists=True ne change pas cela — je l’ai testé explicitement, ainsi que strict=False, et j’ai obtenu zéro caractère dans les trois configurations, alors qu’un contrôle basé sur <p> renvoyait 937 dans les trois cas. parse_lists décide seulement si les listes sont conservées à l’intérieur d’un article déjà trouvé.
Dois-je vraiment appeler close() ?
Oui, fermez-la explicitement avec try/finally, comme montré plus haut. Ce test n’a pas mesuré ce qui s’accumule quand on omet la fermeture, donc il ne prétend pas à une fuite de boucle quantifiée ; en revanche, il établit que Goose a un cycle de vie que l’appelant doit gérer.
Quel User-Agent goose3 envoie-t-il ?
Goose/3.1.22 par défaut — le nom de la bibliothèque et sa version exacte. Cela ne s’applique que lorsque vous le laissez récupérer lui-même les pages, et le passage de raw_html contourne complètement ce comportement. Si vous laissez goose3 effectuer les requêtes, définissez le User-Agent volontairement ; la valeur par défaut indique à chaque serveur contacté précisément qui appelle.
Qu’est-ce que cette étude n’a pas testé ?
Les pages réelles, aucune — seulement des fixtures contrôlées. L’extraction multilingue, malgré le fait que target_language soit un paramètre de premier rang. Les champs de métadonnées (titre, auteurs, date, image principale) ont été recensés mais pas évalués en précision. La récupération d’images, désactivée par défaut et dont les chemins ImageMagick pointent vers un gestionnaire de paquets que la plupart des machines n’ont pas. Le comportement mémoire en charge concurrente ou soutenue, ainsi que le débit sous pression ; le tableau mémoire ne mesurait qu’un processus neuf traitant un seul document.


