Nei fixture in cui la perdita è davvero definibile, goose3 ha raggiunto una precisione dei token di contenuto pari a 1,0000 e zero token contaminanti. Nel risultato non è comparsa nemmeno una parola di navigazione, pubblicità, sidebar, commenti o promo. Nessun altro strumento, nel confronto tra sei librerie, ha toccato lo stesso livello.
Ha però anche il peggior richiamo degli articoli del gruppo — 0,8243 su tutti i 22 fixture, contro 1,0000 di Mozilla Readability — perché in due casi ha restituito una stringa vuota.
Queste metriche sono legate dalla regola di valutazione: un risultato vuoto non entra nella precisione condizionale, mentre il richiamo registra la mancata estrazione.
Cos'è goose3
goose3 è la continuazione in Python 3 di una linea che parte da Goose di Gravity Labs in Scala e passa per python-goose. È un estrattore di articoli con metadati, non un semplice dump di testo: si crea un Goose, si chiama extract() e si ottiene un oggetto Article con circa ventotto campi accessibili — testo ripulito, titolo, autori, data di pubblicazione, immagine principale, meta description, tag, link, tweet e altro ancora.
Riferimento ufficiale: repository ufficiale di goose3.

Versione testata: 3.1.22, licenza Apache, 912 stelle GitHub, ultimo push 2026-07-23 — mantenuto attivamente al momento del test. Python 3.14.2.
L'API richiede due chiamate e un obbligo:
from goose3 import Goose
g = Goose()
try:
article = g.extract(raw_html=html)
text = article.cleaned_text
finally:
g.close() # chiudere esplicitamente dopo l'uso
Vale la pena segnalare close() perché è facile dimenticarsene e non arriva alcun avviso. Questa recensione non ha eseguito un test a ciclo continuo per quantificare sessioni, connessioni o memoria trattenute quando la chiusura viene saltata, quindi parlare di “memory leak” sarebbe più forte di quanto mostrino le prove. Considerate la chiusura esplicita come requisito di ciclo di vita indicato dall'uso dell'API qui mostrato.
Il compromesso, misurato
Sei estrattori, un set di fixture annotato, un valutatore. Ogni blocco di ogni fixture è etichettato article o boilerplate e porta un token sentinella univoco, quindi il "l'ha recuperata o no" è una verifica esatta di appartenenza a sottostringa, non un punteggio di somiglianza.
| Libreria | Richiamo articoli (tutti i 22) | Perdita di boilerplate | Precisione token contenuto | Token contaminanti | Output prodotto |
|---|---|---|---|---|---|
| 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. Il richiamo è calcolato su tutti i 22 fixture; il tasso di perdita e la precisione sui 11 che contengono sia articoli sia boilerplate.
Leggete sempre insieme la colonna della precisione e l'ultima colonna. Qui la precisione è condizionata alla produzione di output — una libreria che restituisce una stringa vuota su una fixture non entra né nel numeratore né nel denominatore, quindi rinunciare è “gratis” in questa media. Il numeratore è la sovrapposizione tra i token non-stopword estratti e i token dell'articolo etichettati; il denominatore è ogni token non-stopword estratto. I “token contaminanti” sono una misura più stretta: sovrapposizione solo con i token di boilerplate etichettati. Un token estratto in più che non corrisponde né all'articolo etichettato né al boilerplate etichettato abbassa la precisione senza aumentare quel conteggio di contaminazione; anche i token ripetuti oltre il multiset dell'articolo possono fare lo stesso. Ecco perché newspaper4k può mostrare 0 token contaminanti e una precisione sotto 1.0000. Per goose3, il 1.0000 è stato calcolato su 10 degli 11 fixture; Readability, trafilatura, newspaper4k e resiliparse sono stati valutati su 11 su 11.
Il 1.0000 resta comunque utile dentro questo set sintetico. Sui dieci fixture valutati, goose3 non ha emesso alcun token di boilerplate etichettato; Readability ne ha emessi 35 sulle stesse pagine. Se un modello consuma l'output, questo significa nessuna spesa di token per il boilerplate registrato in quei dieci fixture. Non prova però assenza di spreco su pagine reali, e un risultato vuoto può aggiungere costi di fallback o retry in altre parti della pipeline.
I due silenzi, e cosa significano
goose3 non ha restituito nulla su esattamente due fixture. Uno è difendibile, l'altro è un limite reale.
Il documento quasi vuoto. Una pagina con un solo blocco articolo da 32 caratteri. goose3 non lo prende. Anche jusText fa lo stesso. In questo set di fixture, Readability ha prodotto output su tutte le 22 pagine, quindi il suo risultato non supporta il silenzio di goose3 in questo caso. Se rifiutare un documento così piccolo sia accettabile dipende dal contratto minimo di contenuto richiesto dal chiamante.

L'articolo composto interamente da elementi <li>. Sei blocchi articolo, nessuno dentro un tag <p>. goose3 restituisce una stringa vuota.
Questo secondo caso mi ha incuriosito, perché la configurazione predefinita di goose3 indica parse_lists=True. Così l'ho verificato — tre configurazioni contro un controllo funzionante, perché un singolo run improduttivo non basta per giudicare una libreria:
| Configurazione | Pagina solo liste | Controllo con <p> |
|---|---|---|
| impostazioni predefinite | 0 caratteri | 937 caratteri |
strict=False | 0 caratteri | 937 caratteri |
parse_lists=True (esplicito) | 0 caratteri | 937 caratteri |
Zero in tutte e tre, mentre il controllo restituisce 937 caratteri in tutte e tre. Quindi parse_lists=True governa se le liste vengono mantenute dentro un articolo che goose3 ha già individuato — non permette al selettore candidato di trattare una lista come articolo. Il punteggio dei nodi di goose3 ha bisogno di blocchi dalla forma di paragrafi per trovare il corpo, e una pagina il cui corpo è una lista non ne ha.
Il risultato supportato è più ristretto: un corpo modellato come questa fixture sintetica — sei blocchi articolo, tutti <li>, senza alcun candidato-paragrafo — ha restituito una stringa vuota. Changelog, riferimenti API, ricette, FAQ e articoli di confronto sono campioni di rischio sensati per una replica su pagine reali, perché possono essere ricchi di liste, ma questa singola fixture non dimostra che quelle categorie falliscano in generale.
La stringa vuota è rilevabile dalla macchina solo se il chiamante valida che l'output non sia vuoto. È più facile da intercettare rispetto a un testo plausibile che però non contiene nulla dell'articolo, ma resta comunque un fallimento silenzioso se il monitoraggio si basa solo sulle eccezioni. In produzione serve un controllo sulla lunghezza minima dell'output e un fallback o un record esplicito di pagina fallita.
La realtà dell'installazione
pip install goose3 scarica 16 pacchetti e 44,3 MiB in circa 6-9 secondi. Import freddo misurato in un subprocess pulito: 2,181 s.
Riferimento ufficiale: goose3 su PyPI.
| Libreria | Pacchetti | site-packages | Import freddo | p50 estrazione |
|---|---|---|---|---|
| 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. Ogni libreria in un virtualenv vuoto separato, quindi nessun ingombro ereditato da una sorella.
Peso nella media, velocità nella media. Si è installato e importato senza problemi su Python 3.14.2, cosa non scontata in questa categoria.
Tre impostazioni predefinite da conoscere prima del deploy

Leggere l'oggetto Configuration incluso nel pacchetto, invece della documentazione, fa emergere diciannove impostazioni. Tre di queste potrebbero sorprendere qualcuno.
Si identifica da solo. browser_user_agent ha come valore predefinito Goose/3.1.22. Se lasciate che goose3 effettui il download in autonomia, ogni server contattato registra il nome della libreria e la sua versione esatta. È trasparente, ma anche un'impronta digitale. Impostatelo in modo esplicito o scaricate voi l'HTML e passate raw_html.
Punta a un binario MacPorts. imagemagick_convert_path predefinito è /opt/local/bin/convert e imagemagick_identify_path è /opt/local/bin/identify. Sul mio sistema non esistono — /opt/local è MacPorts, che la maggior parte delle persone non ha; Homebrew mette i binari in /opt/homebrew. Il valore predefinito non dà problemi finché non attivate il recupero immagini (enable_image_fetching è False di default, giustamente), ma se lo attivate aspettandovi che l'estrazione dell'immagine principale funzioni, qui semplicemente non lo farà.
Presume l'inglese. target_language ha come default en con use_meta_language=True, quindi segue la dichiarazione della pagina quando presente e torna all'inglese quando non lo è. Va benissimo per contenuti in inglese, ma conviene impostarlo esplicitamente per tutto il resto.
Le altre sono ragionevoli: parser_class è lxml, http_timeout 30 secondi, strict attivo, log_level ERROR, parse_headers e keep_footnotes attivi, images_min_bytes 4.000.
Memoria, e cosa fa HTML rotto
Due aspetti che in ogni recensione di questa serie risultavano non testati, ora sono stati misurati.
Il contesto più ampio dello stress test è nel confronto tra memoria e HTML malformato di dieci librerie.
Memoria residente massima, tramite /usr/bin/time -l, un processo nuovo per cella — il pavimento di import è il costo della libreria caricata e inattiva, i picchi includono il documento.
| Libreria | Runtime | Pavimento di import | Picco 226 KB | Picco 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. I baseline di Python e Node non sono confrontabili tra loro; l'interprete è incluso in entrambi.
goose3 ha un pavimento di 44,1 MiB e raggiunge 398,5 MiB su un documento da 10 MB. Il suo pavimento di import è il terzo più alto tra le librerie Python mostrate, un dato utile in ambienti sensibili al cold start.
HTML rotto. Dodici documenti che rompono esattamente una cosa ciascuno — tag non chiusi, elementi inline mal annidati, attributi senza virgolette con spazi, chiusure fuori posto, assenza totale di <html>, attributi duplicati, documento troncato a metà tag, entità errate, <script> non chiuso, dichiarazione charset falsa, commento che contiene markup e 600 livelli di annidamento — più due controlli ben formati di dimensioni corrispondenti, perché "non ha restituito nulla" dice qualcosa sulla malformazione solo se la libreria non tace anche su un documento pulito della stessa dimensione.
goose3 ha generato eccezioni su 0 su 14 e non ha restituito nulla su 10, recuperando 2/33 sentinelle tra i fixture rotti (malformed-results.json). Un fixture è escluso da questo conteggio: secondo HTML5 tutto ciò che segue un <script> non chiuso è contenuto dello script, quindi perderlo lì è corretto e recuperarlo sarebbe la deviazione.
Pro e contro
A favore. Zero token di boilerplate etichettati nei dieci fixture di fedeltà del contenuto in cui ha prodotto output. Sono disponibili circa ventotto campi articolo, anche se la loro accuratezza è stata inventariata e non valutata. Default sensato per il recupero immagini (spento). Installazione pulita su Python 3.14. Manutenzione attiva. Apache-2.0. Un risultato vuoto è semplice da intercettare se il chiamante lo verifica esplicitamente.
Contro. Il richiamo articoli più basso del set, 0,8243, dovuto interamente al fatto di non restituire nulla, non a restituire il contenuto sbagliato. Una pagina il cui articolo è una lista produce una stringa vuota indipendentemente dalla configurazione. 44,3 MiB e un cold import di 2,2 secondi sono pesanti rispetto ai 21,0 MiB e 15 ms di resiliparse. Richiede close(). Due default puntano a cose sbagliate sulla maggior parte delle macchine.
Chi dovrebbe usarlo e chi no
Usate goose3 come candidato quando il testo estratto finisce in un modello o in un database in cui il boilerplate etichettato costa caro e le pagine sono articoli convenzionali con paragrafi. In questo set di fixture non ha emesso token di boilerplate etichettati quando ha risposto. L'area metadati è disponibile ma qui non è stata validata; accuratezza di titolo, autore, data e immagine richiedono fixture di verità separati prima di diventare un vantaggio nella selezione.
Saltatelo se il vostro corpus è ricco di liste — otterrete stringhe vuote e nessuna spiegazione. Saltatelo se il costo di cold start conta, dove resiliparse importa 145 volte più velocemente. E saltatelo se vi serve una risposta su ogni pagina, perché qui "nessuna risposta" è un esito reale: 2 su 22 fixture, entrambe in silenzio, nel senso che una stringa vuota non è un'eccezione.
Un abbinamento da testare: goose3 come primario con fallback quando cleaned_text è vuoto o sotto la soglia minima di contenuto. Readability ha recuperato ogni unità articolo in questo set di 22 fixture, comprese entrambe le casistiche vuote di goose3. Questo risultato sintetico supporta il pattern architetturale, non promette che il fallback non perda mai nulla su pagine reali.
Dove si inserisce un'API gestita
Questo benchmark ha testato il percorso di estrazione raw_html di goose3: l'HTML era già stato acquisito prima che goose3 lo vedesse. goose3 ha anche un proprio percorso di recupero via rete, come mostra l'impostazione User-Agent, ma quel percorso non è stato testato qui. Nemmeno il rendering JavaScript e il comportamento anti-bot sono stati testati.
Per gli stessi fixture su tutti e sei gli estrattori, vedere il confronto tra librerie di estrazione.
Un servizio gestito di fetch/render/estrazione, incluso il nostro Thunderbit, copre un confine di responsabilità diverso. Thunderbit non è stato sottoposto a benchmark in questo test. La distinzione rilevante è tra estrazione di articoli da HTML fornito e un servizio hosted che acquisisce ed elabora una URL; questo articolo non offre alcun confronto di qualità o performance sulla stessa metrica.
Il confronto corretto: l'insieme di campi di goose3 è fisso e orientato all'articolo, perfetto quando le pagine sono articoli e sbagliato quando sono listing di prodotti. Se avete già l'HTML e le vostre pagine sono articoli, goose3 è gratuito e molto pulito.
Per il lato hosted, il nostro rassegna delle API di web scraping offre una visione più ampia; per alternative self-hosted, il pilastro sugli scraper open source. Se il testo deve finire in un modello, convertire HTML in Markdown in Python spiega dove la fedeltà si perde davvero.
Prova Thunderbit per l'estrazione di dati web
Conviene usare goose3?
Sì, se gli articoli in forma di paragrafo corrispondono al carico di lavoro e il chiamante tratta l'output vuoto come un'estrazione fallita, non come un successo.
In questo set di fixture, goose3 non ha emesso token di boilerplate etichettati quando ha risposto e ha restituito due stringhe vuote. Una era una pagina quasi vuota e l'altra il corpo sintetico composto solo da liste. Si tratta di un compromesso precisione/copertura, non della prova di un temperamento di prodotto valido ovunque.
Se il richiamo conta di più, testate un fallback con un controllo minimo esplicito sull'output. Readability ha recuperato ogni unità articolo in questi 22 fixture; newspaper4k ha mostrato zero perdita di unità boilerplate etichettate e 0,9865 di richiamo, producendo output su tutte le 22 pagine. Questi risultati collocano questo set sintetico ai valori predefiniti, non su workload produttivi sconosciuti.
goose3 si merita il suo posto quando il costo di una parola sbagliata è più alto del costo di una pagina mancante.
Prova Thunderbit per l'estrazione di dati web Get Started Free
FAQ
La precisione perfetta di goose3 è reale o è solo un effetto del fatto che a volte rinuncia? Entrambe le cose, e sono separabili. È stato valutato su 10 degli 11 fixture che contengono boilerplate, quindi manca una fixture dalla media — quella parte è un effetto del rifiuto. Ma su quei dieci ha restituito zero token contaminanti contro un boilerplate costruito per essere ostile, mentre Readability ne ha filtrati 35. La precisione è reale sulle pagine a cui risponde; il richiamo mostra dove entra in gioco il rifiuto.
Perché goose3 non restituisce nulla su una pagina il cui articolo è una lista?
Il suo punteggio candidato ha bisogno di blocchi con forma da paragrafo per individuare il corpo dell'articolo, e una pagina fatta di <li> non ne ha. Il default parse_lists=True non cambia questo — l'ho testato esplicitamente, insieme a strict=False, ottenendo zero caratteri in tutte e tre le configurazioni, mentre un controllo basato su <p> restituiva 937 in tutte e tre. parse_lists decide se le liste vengono mantenute dentro un articolo già trovato.
Devo chiamare close()?
Sì, chiudetelo esplicitamente con try/finally, come mostrato sopra. Questa recensione non ha misurato cosa si accumula se la chiusura viene omessa, quindi non afferma l'esistenza di un leak quantificato in loop; stabilisce però che Goose ha un ciclo di vita che il chiamante deve gestire.
Cosa invia goose3 come User-Agent?
Goose/3.1.22 per default — nome della libreria e versione esatta. Vale solo quando gli lasci fare il fetch; passando raw_html lo si aggira del tutto. Se invece lo lasci recuperare lui le pagine, impostate il User-Agent in modo esplicito; il default dice a ogni server contattato esattamente chi sta chiamando.
Cosa non ha testato questa recensione?
Pagine reali, del tutto — qui ci sono fixture controllate. Estrazione multilingue, nonostante target_language sia un'impostazione di primo livello. I campi di metadati (titolo, autori, data, immagine principale) sono stati inventariati ma non valutati per accuratezza. Il recupero immagini, che è disattivato di default e usa percorsi ImageMagick che puntano a un package manager che la maggior parte delle macchine non ha. Il comportamento in memoria con concorrenza o carico sostenuto, più il throughput sotto carico; la tabella della memoria ha misurato un solo processo fresco che gestisce un solo documento.


