W testach, w których w ogóle dało się sensownie zdefiniować wyciek, goose3 osiągnął 1.0000 precyzji tokenów treści i zero zanieczyszczających tokenów. Nie przebiło się ani jedno słowo z nawigacji, reklam, paska bocznego, komentarzy ani promo. W porównaniu sześciu bibliotek żadne inne narzędzie nie dorównało temu wynikowi.
Jednocześnie ma on najgorszy recall artykułu w całym zestawie — 0.8243 na 22 fixture’ach, wobec 1.0000 dla Mozilla Readability — bo w dwóch przypadkach zwrócił pusty ciąg.
Te metryki są ze sobą powiązane przez regułę oceny: pusty wynik nie wnosi nic do precyzji warunkowej, a recall odnotowuje brak trafienia.
Czym jest goose3
goose3 to kontynuacja w Pythonie 3 linii narzędzi zaczynającej się od Goose firmy Gravity Labs w Scali, a później rozwijanej jako python-goose. To ekstraktor artykułów z metadanymi, a nie zwykły zrzut tekstu: tworzysz Goose, wywołujesz extract(), i dostajesz obiekt Article z około dwudziestoma ośmioma dostępnymi polami — oczyszczonym tekstem, tytułem, autorami, datą publikacji, obrazem głównym, meta description, tagami, linkami, tweetami i nie tylko.
Oficjalne źródło: oficjalne repozytorium goose3.

Testowana wersja: 3.1.22, licencja Apache, 912 gwiazdek na GitHubie, ostatni push 2026-07-23 — w momencie testów aktywnie utrzymywana. Python 3.14.2.
API sprowadza się do dwóch wywołań i jednego obowiązku:
from goose3 import Goose
g = Goose()
try:
article = g.extract(raw_html=html)
text = article.cleaned_text
finally:
g.close() # po użyciu zamknij jawnie
To close() warto podkreślić, bo łatwo o nim zapomnieć, a narzędzie nie ostrzega. Ten przegląd nie uruchamiał pętli testowej, która mierzyłaby utrzymane sesje, połączenia lub pamięć przy pominięciu zamknięcia, więc określenie „wyciek zasobów” byłoby mocniejsze niż dostępne dowody. Traktuj jawne zamknięcie jako wymóg cyklu życia wynikający z użycia API w tym przykładzie.
Mierzony kompromis
Sześć ekstraktorów, jeden opisany zestaw fixture’ów, jeden scorer. Każdy blok każdego fixture’a jest oznaczony jako article albo boilerplate i ma unikalny token-sygnał, więc pytanie „czy odzyskał tę jednostkę” rozstrzygane jest przez dokładną obecność podciągu, a nie przez podobieństwo.
| Biblioteka | Recall artykułu (wszystkie 22) | Wyciek boilerplate’u | Precyzja tokenów treści | Zanieczyszczające tokeny | Zwrócono wynik |
|---|---|---|---|---|---|
| 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. Recall liczony jest dla wszystkich 22 fixture’ów; wskaźnik wycieku i precyzja — dla 11, które zawierają zarówno artykuł, jak i boilerplate.
Kolumnę precyzji czytaj zawsze razem z ostatnią kolumną. Precyzja tutaj jest warunkowa względem wygenerowania wyniku — biblioteka, która zwraca pusty ciąg dla fixture’a, nie dodaje nic ani do licznika, ani do mianownika, więc „nieodpowiadanie” jest w tej średniej bezkosztowe. Licznik to część wspólna wielozbioru między wyekstrahowanymi tokenami niebędącymi stop-słowami a tokenami artykułu z etykiet; mianownik to wszystkie wyekstrahowane tokeny niebędące stop-słowami. „Zanieczyszczające tokeny” oznaczają węższy przypadek: nakładanie się wyłącznie z tokenami boilerplate’u z etykiet. Dodatkowy token wyekstrahowany, który nie pasuje ani do oznaczonego artykułu, ani do oznaczonego boilerplate’u, obniża precyzję, nie zwiększając tej liczby zanieczyszczeń; to samo mogą zrobić powtórzone tokeny poza wielozbiorem artykułu. Dlatego newspaper4k może mieć 0 zanieczyszczających tokenów i precyzję poniżej 1.0000. Wynik 1.0000 dla goose3 liczono na 10 z 11 fixture’ów; Readability, trafilatura, newspaper4k i resiliparse były liczone na 11 z 11.
W tym syntetycznym zbiorze 1.0000 nadal ma wartość. Na dziesięciu ocenionych fixture’ach goose3 nie wygenerował żadnego tokenu z oznaczonego boilerplate’u; Readability na tych samych stronach wygenerował 35. Jeśli model konsumuje wynik, oznacza to brak wydatku tokenów na boilerplate w tych dziesięciu fixture’ach. Nie dowodzi to jednak zerowego marnotrawstwa na prawdziwych stronach, a pusty wynik może dodać koszt fallbacku lub retry gdzie indziej w pipeline’ie.
Dwa milczenia i ich znaczenie
goose3 nie zwrócił nic dla dokładnie dwóch fixture’ów. Jeden z nich da się obronić, drugi to rzeczywiste ograniczenie.
Prawie pusty dokument. Strona z jedną jednostką artykułu o długości 32 znaków. goose3 rezygnuje. Tak samo jusText. W tym zestawie Readability zwracał wynik na wszystkich 22 stronach, więc jego rezultat nie wspiera tu „milczenia” goose3. To, czy odrzucenie tak małego dokumentu jest akceptowalne, zależy od minimalnego kontraktu na treść po stronie wywołującego.

Artykuł zbudowany wyłącznie z elementów <li>. Sześć jednostek artykułu, żadna w tagu <p>. goose3 zwraca pusty ciąg.
To drugie zaskoczyło mnie, bo domyślna konfiguracja goose3 ma parse_lists=True. Sprawdziłem więc trzy konfiguracje na tle działającego kontrolnego przykładu — bo jeden nieudany przebieg nie jest jeszcze wnioskiem o bibliotece:
| Konfiguracja | Strona tylko z listami | Kontrola z <p> |
|---|---|---|
| domyślne | 0 znaków | 937 znaków |
strict=False | 0 znaków | 937 znaków |
parse_lists=True (jawnie) | 0 znaków | 937 znaków |
Zero we wszystkich trzech przypadkach, podczas gdy kontrola zwraca 937 znaków za każdym razem. Wynika z tego, że parse_lists=True decyduje o tym, czy listy są zachowywane wewnątrz artykułu, który goose3 już wcześniej odnalazł — nie pozwala natomiast scorerowi kandydatów traktować listy jako artykułu. Punktacja węzłów w goose3 potrzebuje bloków o kształcie akapitów, żeby w ogóle znaleźć treść główną, a strona, której treść jest listą, takich bloków nie ma.
Wniosek jest węższy: układ treści w tym syntetycznym fixture’ze — sześć jednostek artykułu, wszystkie w <li>, bez kandydata w postaci akapitu — zwrócił pusty ciąg. Changelogi, dokumentacje API, przepisy, strony FAQ i porównawcze wpisy to sensowne próbki ryzyka dla odtwarzania prawdziwych stron, bo mogą być mocno listowe, ale ten pojedynczy fixture nie dowodzi, że takie kategorie stron zwykle zawodzą.
Pusty ciąg da się wykryć maszynowo tylko wtedy, gdy wywołujący sprawdzi, czy wynik nie jest pusty. Łatwiej go odsiać niż pozornie poprawny tekst, w którym nie ma ani słowa z artykułu, ale w monitoringu opartym wyłącznie na wyjątkach to wciąż cicha awaria. W produkcji potrzebny jest minimalny próg długości wyniku oraz fallback albo jawny zapis nieudanej strony.
Rzeczywistość instalacji
pip install goose3 pobiera 16 pakietów i 44.3 MiB w około 6–9 sekund. Czas zimnego importu mierzony w świeżym subprocessie: 2.181 s.
Oficjalne źródło: goose3 w PyPI.
| Biblioteka | Pakiety | site-packages | Zimny import | 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. Każda biblioteka w osobnym pustym virtualenv, więc nie dziedziczy żadnego śladu po sąsiedniej.
Środek stawki pod względem wagi, środek pod względem szybkości. Instalacja i import przeszły poprawnie na Python 3.14.2, co w tej kategorii nie jest standardem.
Trzy domyślne ustawienia, które warto znać przed wdrożeniem

Odczyt obiektu Configuration z dostarczonej paczki, zamiast polegania na dokumentacji, ujawnia dziewiętnaście ustawień. Trzy z nich mogą kogoś zaskoczyć.
Przedstawia się z nazwy. browser_user_agent domyślnie ma wartość Goose/3.1.22. Jeśli pozwolisz goose3 samodzielnie pobierać strony, każdy serwer zobaczy nazwę biblioteki i jej dokładną wersję. To uczciwe, ale jednocześnie tworzy odcisk palca. Ustaw to świadomie albo pobieraj HTML samodzielnie i przekazuj raw_html.
Wskazuje na binarkę z MacPorts. imagemagick_convert_path domyślnie wskazuje /opt/local/bin/convert, a imagemagick_identify_path — /opt/local/bin/identify. U mnie nie istnieją; /opt/local to MacPorts, którego większość osób nie ma, a Homebrew umieszcza binaria w /opt/homebrew. Domyślna wartość nie robi nic, dopóki nie włączysz pobierania obrazów (enable_image_fetching domyślnie ma False, co jest rozsądne), ale jeśli włączysz je, licząc na ekstrakcję obrazu głównego, tutaj po cichu się to nie uda.
Zakłada angielski. target_language domyślnie ma en przy use_meta_language=True, więc gdy strona deklaruje własny język, narzędzie go przyjmie, a gdy nie — wróci do angielskiego. Dla treści anglojęzycznych w porządku, ale dla innych języków warto ustawić to jawnie.
Reszta wygląda rozsądnie: parser_class to lxml, http_timeout 30 sekund, strict włączony, log_level ERROR, parse_headers i keep_footnotes włączone, images_min_bytes 4,000.
Pamięć i to, co psuje HTML robi z jej zużyciem
Dwie rzeczy, które każdy przegląd w tym pakiecie wcześniej oznaczył jako nieprzetestowane, teraz zostały zmierzone.
Szerszy kontekst testu obciążeniowego znajdziesz w porównaniu pamięci i źle sformatowanego HTML-a dla dziesięciu bibliotek.
Szczytowe zużycie pamięci rezydentnej, przez /usr/bin/time -l, jeden świeży proces na komórkę — „podłoga importu” to koszt załadowanej i bezczynnej biblioteki, a szczyty obejmują dokument.
| Biblioteka | Runtime | Podłoga importu | Szczyt 226 KB | Szczyt 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. Bazowych wyników Python i Node nie da się bezpośrednio porównywać; interpreter jest w obu przypadkach częścią środka.
goose3 ma podłogę na poziomie 44.1 MiB i dochodzi do 398.5 MiB na dokumencie 10 MB. Jego podłoga importu jest trzecia najwyższa wśród pokazanych bibliotek Pythona, co warto wziąć pod uwagę przy wdrożeniach wrażliwych na cold start.
Zepsuty HTML. Dwanaście dokumentów, z których każdy psuje dokładnie jedną rzecz — niezamknięte tagi, źle zagnieżdżone elementy inline, niecytowane atrybuty ze spacjami, samotne tagi zamykające, brak <html> w ogóle, duplikujące się atrybuty, dokument ucięty w połowie taga, błędne encje, niezamknięty <script>, kłamliwa deklaracja charsetu, komentarz zawierający markup i 600 poziomów zagnieżdżenia — plus dwa poprawne kontrolne dokumenty o dopasowanych rozmiarach, bo „zwrócił nic” mówi coś o malformedness tylko wtedy, gdy biblioteka nie milczy także na czystym dokumencie o tej samej wielkości.
goose3 zgłosił wyjątek w 0 z 14 przypadków i zwrócił nic w 10, odzyskując 2/33 sygnałów w zepsutych fixture’ach (malformed-results.json). Jeden fixture jest wyłączony z tego liczenia: zgodnie z HTML5 wszystko po niezamkniętym <script> jest treścią skryptu, więc utrata tego fragmentu jest poprawna, a jego odzyskanie byłoby odstępstwem.
Plusy i minusy
Na plus. Zero tokenów oznaczonego boilerplate’u w dziesięciu fixture’ach, w których zwrócił wynik. Dostępnych jest około dwudziestu ośmiu pól artykułu, choć ich poprawność została tylko zinwentaryzowana, nie oceniona. Sensowny domyślny brak pobierania obrazów. Czysta instalacja na Pythonie 3.14. Aktywnie utrzymywany. Apache-2.0. Pusty wynik łatwo odsiać, jeśli wywołujący sprawdza go jawnie.
Na minus. Najniższy recall artykułu w całym zestawie: 0.8243, napędzany wyłącznie zwracaniem pustego wyniku, a nie błędnym tekstem. Strona, której artykuł jest listą, daje pusty ciąg niezależnie od konfiguracji. 44.3 MiB i 2.2-sekundowy cold import są ciężkie w porównaniu z 21.0 MiB i 15 ms resiliparse. Wymaga close(). Dwa domyślne ustawienia wskazują na rzeczy, które na większości maszyn są po prostu niepoprawne.
Kto powinien, a kto nie powinien go używać
Użyj goose3 jako kandydata, jeśli wyekstrahowany tekst trafia do modelu albo bazy danych, gdzie boilerplate z etykietami jest kosztowny, a strony mają konwencjonalną budowę artykułów z akapitami. W tym zestawie fixture’ów, kiedy odpowiadał, nie wypuszczał żadnych tokenów oznaczonego boilerplate’u. Warstwa metadanych jest dostępna, ale tutaj nie została zweryfikowana; poprawność tytułu, autora, daty i obrazu wymaga osobnych fixture’ów z prawdą referencyjną, zanim stanie się przewagą wyboru.
Pomiń go, jeśli korpus jest mocno listowy — dostaniesz puste ciągi i żadnego wyjaśnienia. Pomiń go, jeśli liczy się koszt cold startu, bo resiliparse importuje się 145× szybciej. I pomiń go, jeśli potrzebujesz odpowiedzi na każdej stronie, bo „brak odpowiedzi” jest tu realnym wynikiem: 2 z 22 fixture’ów, i oba bez ostrzeżenia, w tym sensie, że pusty ciąg nie jest wyjątkiem.
Warto przetestować takie połączenie: goose3 jako główny ekstraktor z fallbackiem uruchamianym wtedy, gdy cleaned_text jest pusty albo spada poniżej minimalnego progu treści. Readability odzyskał wszystkie jednostki artykułu w tym zestawie 22 fixture’ów, w tym oba puste przypadki goose3. Ten syntetyczny wynik wspiera wzorzec architektury, a nie obietnicę, że fallback nigdy nie zawiedzie na prawdziwych stronach.
Gdzie pasuje zarządzane API
Ten benchmark testował ścieżkę ekstrakcji goose3 przez raw_html: HTML został już wcześniej pobrany, zanim goose3 go zobaczył. goose3 ma też własną ścieżkę pobierania z sieci, co sugeruje ustawienie User-Agent, ale tej ścieżki tutaj nie testowano. Renderowanie JavaScriptem i zachowanie wobec anty-botów również nie były testowane.
Dla tych samych fixture’ów we wszystkich sześciu ekstraktorach zobacz porównanie ekstrakcji sześciu bibliotek.
Zarządzana usługa pobierania/renderowania/ekstrakcji, w tym nasz własny Thunderbit, odpowiada za inny zakres obowiązków. Thunderbit nie był benchmarkowany w tym teście. Istotna różnica to ekstrakcja artykułu z dostarczonego HTML-a kontra hostowana usługa, która sama pobiera i przetwarza URL; ten artykuł nie zawiera porównania tych rozwiązań w tych samych metrykach jakości ani wydajności.
Uczciwe porównanie: zestaw pól goose3 jest stały i ukierunkowany na artykuły, co jest idealne, gdy Twoje strony są artykułami, a błędne dla listingów produktów. Jeśli masz już HTML i Twoje strony są artykułami, goose3 jest darmowy i bardzo czysty.
Dla hostowanego rozwiązania szerszy obraz daje przegląd API do web scrapingu; dla alternatyw self-hosted — pillar open-source scraperów. Jeśli tekst ma trafić do modelu, konwersja HTML do Markdown w Pythonie pokazuje, gdzie naprawdę ginie wierność.
Wypróbuj Thunderbit do ekstrakcji danych z sieci
Czy warto używać goose3?
Tak, jeśli artykuły mają układ akapitowy, a wywołujący traktuje pusty wynik jako nieudaną ekstrakcję, a nie sukces.
W tym zestawie fixture’ów goose3 nie wypuścił żadnych tokenów oznaczonego boilerplate’u, kiedy odpowiadał, i zwrócił dwa puste ciągi. Jeden dotyczył prawie pustej strony, drugi — syntetycznego ciała złożonego wyłącznie z list. To kompromis między precyzją a pokryciem, a nie dowód na ogólny temperament produktu.
Jeśli ważniejszy jest recall, przetestuj fallback z jawną kontrolą minimalnej długości wyniku. Readability odzyskał każdą jednostkę artykułu w tych 22 fixture’ach; newspaper4k nie miał wycieku oznaczonego boilerplate’u i osiągnął recall 0.9865, zwracając wynik na wszystkich 22 stronach. Te wyniki ustawiają ten syntetyczny zestaw przy domyślnych ustawieniach, nie na nieznanych obciążeniach produkcyjnych.
goose3 zasługuje na miejsce tam, gdzie koszt jednego błędnego słowa jest wyższy niż koszt jednej brakującej strony.
Wypróbuj Thunderbit do ekstrakcji danych z sieci Get Started Free
FAQ
Czy perfekcyjna precyzja goose3 jest prawdziwa, czy wynika z tego, że odmawia odpowiedzi? Jedno i drugie, i da się to rozdzielić. Biblioteka była liczona na 10 z 11 fixture’ów zawierających boilerplate, więc jedna próbka wypadła ze średniej — to właśnie efekt odmowy odpowiedzi. Ale w tych dziesięciu zwróciła zero zanieczyszczających tokenów wobec celowo trudnego boilerplate’u, podczas gdy Readability wypuścił 35. Precyzja jest realna dla stron, na które odpowiada; to w kolumnie recall widać, że czasem milczy.
Dlaczego goose3 zwraca nic na stronie, której artykuł jest listą?
Jego scoring kandydatów potrzebuje bloków o kształcie akapitów, żeby znaleźć treść artykułu, a strona zbudowana z <li> takich bloków nie ma. Domyślne parse_lists=True tego nie zmienia — sprawdziłem to jawnie, razem z strict=False, i we wszystkich trzech konfiguracjach wynik miał 0 znaków, podczas gdy kontrola oparta na <p> zwracała 937 znaków we wszystkich trzech. parse_lists decyduje tylko o tym, czy listy zostaną zachowane wewnątrz artykułu, który został już odnaleziony.
Czy muszę wywoływać close()?
Tak, zamykaj jawnie przez try/finally, jak pokazano wyżej. Ten przegląd nie mierzył, co kumuluje się przy pominięciu zamknięcia, więc nie twierdzi, że istnieje ilościowo zmierzony wyciek w pętli; pokazuje natomiast, że Goose ma cykl życia, którym musi zarządzać wywołujący.
Jaki User-Agent wysyła goose3?
Domyślnie Goose/3.1.22 — nazwa biblioteki i dokładna wersja. Dotyczy to tylko sytuacji, gdy pozwolisz mu samemu pobierać stronę; przekazanie raw_html całkowicie omija ten mechanizm. Jeśli już pozwalasz na pobieranie, ustaw User-Agent świadomie; domyślna wartość mówi każdemu serwerowi dokładnie, co się do niego zgłasza.
Czego ten przegląd nie testował?
Prawdziwych stron w ogóle — to kontrolowane fixture’y. Ekstrakcji wielojęzycznej, mimo że target_language jest ustawieniem pierwszej klasy. Pól metadanych (tytuł, autorzy, data, obraz główny) nie oceniano pod kątem dokładności, a jedynie zinwentaryzowano. Pobierania obrazów, które jest domyślnie wyłączone i którego ścieżki ImageMagick wskazują na menedżer pakietów, którego większość maszyn nie ma. Zachowania pamięci przy pracy równoległej lub długotrwałym obciążeniu, a także przepustowości pod loadem; tabela pamięci mierzyła jeden świeży proces obsługujący jeden dokument.


