W dokumencie, którego najdłuższy akapit ma 151 znaków, jusText przy ustawieniach domyślnych zwraca zero znaków. Nie częściowy wynik — pusty ciąg. Obniż jeden próg tak, by dokładnie jeden akapit go przekroczył, a ten sam dokument zwróci 832 znaki. Obniż go jeszcze bardziej i wynik nadal wynosi 832.
W tym teście zachowanie przypomina urwisko, a nie łagodny spadek. To, czy domyślne ustawienie znajduje się po niewłaściwej stronie, zależy od długości akapitów i rozkładu boilerplate w docelowym korpusie.
Czym jest jusText i dlaczego ważna jest gęstość leksykalna
Na tle innych ekstraktorów z tego zestawu jusText w wyjątkowo dużym stopniu opiera się na stopwordach charakterystycznych dla danego języka. Blok z dużym udziałem słów funkcyjnych — the, and, of, was — z większym prawdopodobieństwem okaże się prozą. Ten sygnał leksykalny nie jest jednak jedynym elementem klasyfikatora: wpływ mają też długość akapitu, gęstość linków, granice bloków wyprowadzone z HTML, odległość od nagłówków, klasy sąsiednich bloków oraz etap uwzględniający kontekst.
Oficjalne źródło: oficjalne repozytorium jusText.

Ponieważ klasyfikator korzysta z listy stopwords dla konkretnego języka, jusText udostępnia 100 takich list. Ta wyraźna, językowo-specyficzna warstwa leksykalna jest jego najbardziej rozpoznawalną cechą w tym porównaniu, choć jakości wielojęzycznej nie testowano.
Testowana wersja: 3.0.2, BSD 2-Clause, 822 gwiazdki na GitHubie. Python 3.14.2.
Urwisko, zmierzone

Klasyfikator jusText działa w dwóch przebiegach. Najpierw etap niezależny od kontekstu przypisuje każdemu akapitowi etykietę good, bad, short albo neargood. Następnie etap zależny od kontekstu podnosi neargood do good — ale tylko wtedy, gdy sąsiaduje z blokiem, który już jest good. Samodzielnie akapit dostaje good tylko wtedy, gdy przekroczy length_high, którego domyślna wartość to 200 znaków.
W dokumencie, w którym żaden akapit nie przekracza 200 znaków, nic nie inicjuje promocji, a każdy blok neargood spada do boilerplate. Cała strona wraca pusta.
Przeskanowałem próg na teście, którego najdłuższy akapit ma 151 znaków:
length_high | Akapity sklasyfikowane jako good | Zwrócone znaki |
|---|---|---|
| 200 (domyślne) | 0 | 0 |
| 150 | 8 | 832 |
| 120 | 8 | 832 |
| 100 | 8 | 832 |
| 80 | 8 | 832 |
justext-length-threshold.json.
W tym teście progowym przekroczenie granicy przez jeden akapit zmieniło wszystkie osiem docelowych akapitów w wynik, a dalsze obniżanie progu nic już nie dodało. Ślad zachowania jest spójny z etapem kontekstowym, który promuje dopuszczalne sąsiednie bloki neargood wokół istniejącego bloku good; nie oznacza to jednak, że każdy sąsiad na dowolnej stronie jest promowany bezwarunkowo.
Zanim przypisałem tę zmianę wartości length_high, przetestowałem length_low w czterech wariantach w połączeniu z max_link_density w dwóch: osiem kombinacji, wszystkie zwróciły zero. Zmiana któregokolwiek z tych parametrów nie przywróciła wyniku na tym teście.
W całym zestawie 22 testów ten wzorzec się utrzymuje: 2 z 22 testów zwracały wynik przy length_high=200, 9 z 22 przy 150, 15 z 22 przy 120.
Dwie rzeczy, których to nie oznacza. Nie oznacza, że jusText działa słabo — na prawdziwej stronie z naturalnym językiem przy ustawieniach domyślnych zwrócił 1 190 znaków czystego tekstu artykułu, bo rzeczywisty akapit newsowy bez problemu przekracza 200 znaków za pierwszym razem. I nie oznacza, że domyślna wartość jest błędna; oznacza, że domyślne założenie dotyczy długich akapitów i trzeba sprawdzić, czy taki korpus w ogóle masz, zanim uruchomisz ekstrakcję.
Druga strona tego zestawu: większy wyciek przy ustawieniach domyślnych
W tym oznaczonym zestawie testów przy ustawieniach domyślnych jusText ma najwyższy zmierzony wyciek boilerplate.
| Biblioteka | Recall artykułu (wszystkie 22) | Wyciek boilerplate | Precyzja tokenów treści | Zanieczyszczające tokeny |
|---|---|---|---|---|
| Readability | 1.0000 | 0.2353 | 0.9109 | 35 |
| trafilatura | 0.9865 | 0.0588 | 0.9411 | 4 |
| newspaper4k | 0.9865 | 0.0000 | 0.9452 | 0 |
| resiliparse | 0.9054 | 0.0588 | 0.9381 | 7 |
| jusText | 0.8378 | 0.4706 | 0.8760 | 74 |
| goose3 | 0.8243 | 0.0000 | 1.0000 | 0 |
sixway-scores.json. Jeden zestaw testów, jeden scorer, a każdy element oznaczony unikalnym sentinelowym znacznikiem, więc odzyskanie wyniku jest dokładnym sprawdzeniem występowania podciągu.

47% wycieku boilerplate i 74 zanieczyszczające tokeny — dwa razy większy wyciek niż w Readability i ponad dwa razy więcej zanieczyszczeń na tym oznaczonym syntetycznym zestawie. To wynik diagnostyczny przy ustawieniach domyślnych, a nie stabilny ranking całego produktu.
Zaobserwowany wyciek jest spójny z tym samym etapem kontekstowym, który pojawia się przy „urwisku” progowym. Dopuszczalne sąsiednie bloki neargood mogą zostać promowane, jeśli pozwalają na to klasy bloków wokół nich i reguły odległości; fragment o charakterze prozy albo komentarz obok tekstu artykułu może więc przekroczyć granicę. Wynik testu pokazuje, które oznaczone bloki wyciekły, ale uproszczony mechanizm nadal zależy od dokładnych reguł kontekstu w jusText.
Jego recall wynosi 0.8378, czyli trzecie miejsce od końca, a cała strata wynika z progu: nic nie odzyskał na pojedynczym artykule o długości 129 znaków, nic na stronie z dziesięcioma krótkimi akapitami i nic na prawie pustym dokumencie.
Zestaw list stopwords dla języków
Dziewięćdziesiąt dziewięć innych list stopwords.
justext.get_stoplists() zwraca 100 języków. Klasyfikator jest z założenia parametryzowany językowo, a nie przez tłumaczenie angielskiej heurystyki, więc zmiana języka to tylko jeden argument:
import justext
paragraphs = justext.justext(html, justext.get_stoplist("Czech"))
text = "\n".join(p.text for p in paragraphs if not p.is_boilerplate)
trafilatura i goose3 również pokazują pewne zachowania zależne od języka, ale w tym przeglądzie nie oceniano żadnego ekstraktora na podstawie prawdy gradowanej dla języków innych niż angielski. 100 dołączonych list stopwords sprawia, że jusText jest naturalnym kandydatem do oceny wielojęzycznej; sam spis nie dowodzi jednak jakości ekstrakcji w tych językach ani nie pokazuje, że konkurenci obsługują je gorzej.
API to dwie funkcje i dziewięć strojonnych stałych — length_low 70, length_high 200, stopwords_low 0.30, stopwords_high 0.32, max_link_density 0.20, max_heading_distance 200, plus obsługa kodowania. Niewielkie, czytelne i całe opisane w sygnaturze.
Instalacja i szybkość
pip install justext pobiera 3 pakiety — najmniej w całym porównaniu — oraz 22.4 MiB, w mniej niż dwie sekundy.
Oficjalne źródło: jusText w PyPI.
| Biblioteka | Pakiety | site-packages | Zimny import | Ekstrakcja 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.
Trzy pakiety i 22.4 MiB to niewielka zależność na tle tego porównania, a mediana ekstrakcji 0.56 ms jest w tym środowisku blisko 0.51 ms trafilatura. Pomiar środowiskowy nie rozbija rozmiaru pakietów według typu plików, więc nie da się powiedzieć, jaka część zajętego miejsca pochodzi z list stopwords.
Pytanie o utrzymanie, odpowiedziane ostrożnie
Daty użyte w tych pomiarach utrzymania to ostatni push do repozytorium i ostatnie wydanie na PyPI, oba 2025-02-25. To było siedemnaście miesięcy przed testem. Łącznie osiem wydań, 91 forków, 9 otwartych issue, brak archiwizacji.
Klasyfikatory PyPI pokazują wsparcie Pythona tylko do 3.9. Uruchomiłem bibliotekę na 3.14.2 i zainstalowała się, zaimportowała w 0.777 s oraz przetworzyła 19 z 22 testów bez żadnego wyjątku.
Metadane są więc pięć wersji Pythona za rzeczywistością, a rzeczywistość jest taka, że działa. To ważne rozróżnienie: ciche repozytorium mówi o wsparciu, nie automatycznie o działaniu. Dla biblioteki, której cały algorytm to opublikowana metoda z 2011 roku plus zestaw list słów, „gotowa” jest całkiem prawdopodobnym stanem — niewiele już trzeba zmieniać, a listy stopwords nie psują się tak jak obejście antybota.
Co oznacza ciche repozytorium? Jeśli trafisz na błąd, poprawiasz go sam albo forkowujesz projekt. Warto to zestawić z dziewięcioma otwartymi issue, co nie wygląda jak biblioteka tonąca w niezałatwionych problemach.
Pamięć i wpływ uszkodzonego HTML
Dwa pytania operacyjne są tu mierzone osobno.
Szerszy kontekst testów obciążeniowych znajduje się w porównaniu zużycia pamięci i źle sformatowanego HTML dla dziesięciu bibliotek.
Szczytowe resident memory, mierzone przez /usr/bin/time -l, po jednym świeżym procesie na komórkę — dolny poziom importu pokazuje koszt załadowanej i bezczynnej biblioteki, a piki obejmują dokument.
| Biblioteka | Runtime | Poziom 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 Pythona i Node nie da się ze sobą bezpośrednio porównywać; interpreter jest w obu środowiskach.
Dla jusText dolny poziom to 30.3 MiB, a szczyt na tym teście 10 MB wynosi 431.2 MiB — około 14.2 razy więcej niż poziom importu i 43.1 razy więcej niż rozmiar wejścia w absolutnym RSS. Jeden test i jeden proces nie wyznaczają ogólnej krzywej skalowania; bazowych wyników Pythona i Node nadal nie da się porównywać.
Uszkodzony HTML. Dwanaście dokumentów, z których każdy psuł dokładnie jedną rzecz — niezamknięte tagi, źle zagnieżdżone elementy inline, atrybuty bez cudzysłowów zawierające spacje, przypadkowe zamknięcia, brak <html> w ogóle, zduplikowane atrybuty, dokument ucięty w połowie taga, błędne encje, niezamknięty <script>, nieprawdziwą deklarację zestawu znaków, komentarz zawierający markup oraz 600 poziomów zagnieżdżenia — plus dwa dobrze uformowane kontrolne dokumenty o dopasowanych rozmiarach, bo „zwrócił nic” mówi coś o uszkodzeniu tylko wtedy, gdy biblioteka nie milczy również na czystym dokumencie o tej samej wielkości.
justext zgłosił wyjątek w 0 z 14 przypadków i zwrócił nic w 13, odzyskując 0/33 oznaczonych sentinelami elementów z uszkodzonych testów (malformed-results.json). Krótki kontrolny dokument dobrze uformowany także zwrócił 0 znaków; długi kontrolny dokument był jedynym niepustym wynikiem i zawierał 1 333 znaki. Wszystkie dwanaście uszkodzonych testów pozostały puste, a przypadek z niezamkniętym <script> jest wyłączony z oceny przetrwania sentinelów. Ten zestaw pokazuje więc tolerancję parsera — brak wyjątku — ale nie pozwala przypisać ciszy uszkodzeniu, a nie już wcześniej zmierzonemu progowi długości.
Zalety i wady
Na plus. 100 list stopwords dla języków, a klasyfikator faktycznie zbudowany wokół nich, a nie tylko na nie „przetłumaczony”. Najmniejsza liczba zależności w porównaniu: 3 pakiety. 0.56 ms mediany ekstrakcji. Dziewięć udokumentowanych, czytelnych stałych strojenia. BSD 2-Clause. Działa poprawnie na Pythonie 3.14 mimo klasyfikatorów kończących się na 3.9.
Na minus. Największy wyciek boilerplate w tym syntetycznym teście przy ustawieniach domyślnych: 47%, z 74 zanieczyszczającymi tokenami. Test krótkich akapitów zwrócił pusty wynik, gdy żaden akapit nie przekroczył length_high. Recall w tym zestawie wyniósł 0.8378. Ostatni odnotowany push do repozytorium i wydanie były siedemnaście miesięcy przed testem, więc trzeba uwzględnić ryzyko związane z utrzymaniem.
Kto powinien go używać, a kto nie
Warto rozważyć jusText dla korpusów wielojęzycznych, ponieważ warstwa list stopwords dla konkretnych języków jest jawna i szeroka. Ten test zliczał 100 list stopwords, ale nie mierzył jakości wielojęzycznej. To także dobry kandydat tam, gdzie trzy pakiety zależności i jawne progi dopasowują się do środowiska wdrożeniowego.
Lepiej pominąć, jeśli karmisz model token po tokenie, bo 74 zanieczyszczające tokeny przy zerze w goose3 i newspaper4k to bezpośredni koszt. Pomiń go przy stronach z krótkimi akapitami — opisach produktów, listach ofert, changelogach, FAQ — chyba że świadomie ustawisz length_high. I pominąć go warto, jeśli z powodów zgodności lub zakupowych potrzebujesz aktywnie utrzymywanej zależności, nawet jeśli kod działa.
Jeśli jednak go użyjesz, dostrój length_high na oznaczonej próbce walidacyjnej zamiast kopiować domyślną wartość albo regułę percentyla. Przeskanuj sensowne progi i mierz jednocześnie recall artykułu oraz precyzję w odniesieniu do boilerplate; obniżenie bramki może odzyskać krótką prozę, ale też promować niechciane sąsiednie bloki.
Gdzie pasuje API zarządzane
jusText przetwarza HTML, który już masz, tak jak wszystkie biblioteki w tym porównaniu. Żadna z nich nie pobiera strony, nie renderuje JavaScriptu ani nie obsługuje warstwy antybotowej.
To samo dla wszystkich sześciu ekstraktorów znajdziesz w porównaniu ekstrakcji sześciu bibliotek.
Hostowana usługa pobierania/renderowania/ekstrakcji, w tym nasz Thunderbit, obejmuje inny zakres odpowiedzialności. Thunderbit nie był tu benchmarkowany. Różnica polega na klasyfikacji prozy z dostarczonego HTML kontra usłudze, która sama pozyskuje i przetwarza URL; ten artykuł nie zawiera porównania jakości w tych samych metrykach.
Uczciwie mówiąc: wielojęzyczne listy stopwords w jusText to prawdziwa i darmowa możliwość. Jeśli Twój problem polega na pobieraniu stron w wielu językach, a nie na klasyfikowaniu w nich prozy, to jest to już inny zakup.
Szerszy przegląd rynku znajdziesz w naszym zestawieniu API do web scrapingu, a filar o open-source scraperach obejmuje rozwiązania hostowane samodzielnie. Jeśli wynik ma trafić do modelu, konwersja HTML do Markdown w Pythonie to miejsce, gdzie najczęściej ginie wierność.
Wypróbuj Thunderbit do ekstrakcji danych z sieci
Czy warto używać jusText?
To dobry kandydat, jeśli korpus jest wielojęzyczny albo rozkład długości akapitów przemawia za wyraźnym dostrojeniem progów. Przed wdrożeniem zweryfikuj oba te aspekty.
100 dołączonych list stopwords to realna cecha projektu, ale dokładności wielojęzycznej nie testowano. Próg „urwiska” można regulować; czy niższe ustawienie jest akceptowalne, zależy od recallu i precyzji wobec boilerplate zmierzonych na oznaczonej próbce.
Na tym syntetycznym zestawie po angielsku przy ustawieniach domyślnych newspaper4k nie przepuścił żadnej oznaczonej jednostki boilerplate i odzyskał 0.9865 jednostek artykułu. To czyni go kandydatem do porównania w tym zadaniu, a nie uniwersalną rekomendacją zamiennika.
Wypróbuj Thunderbit do ekstrakcji danych z sieci Get Started Free
FAQ
Dlaczego jusText zwraca pusty ciąg zamiast częściowego wyniku?
Jego klasyfikator działa w dwóch etapach. Akapit dostaje klasę good samodzielnie tylko po przekroczeniu length_high (domyślnie 200 znaków); drugi etap promuje potem sąsiednie bloki neargood. Jeśli żaden akapit nie przekroczy progu, nie ma z czego rozpocząć promocji, więc wszystkie kandydaty spadają do boilerplate i wynik jest pusty. Z założenia jest to działanie zero-jedynkowe, a nie brak możliwości znalezienia części odpowiedzi.
Czy jusText jest porzucony? Ostatni push do repozytorium i ostatnie wydanie na PyPI miały miejsce 2025-02-25 — siedemnaście miesięcy przed testem — a klasyfikatory PyPI kończą się na Pythonie 3.9. Ale biblioteka zainstalowała się i działała na Pythonie 3.14.2 bez wyjątku, a 9 otwartych issue nie oznacza dużego backlogu. Daty traktuj jako ryzyko utrzymania, a nie dowód uszkodzonego działania; praktyczne ryzyko polega na tym, że błędy możesz musieć naprawić sam.
Dlaczego wycieka więcej boilerplate niż w Readability? W tym zestawie testów dopuszczalne, prozopodobne bloki sąsiednie przekraczały granicę wynikową zgodnie z domyślnymi regułami kontekstu. Promocja zależy od klasy bloku, odległości i kontekstu, a nie następuje automatycznie dla każdego sąsiada. Zmierzony wynik to 47% wycieku jednostek boilerplate i 74 zanieczyszczające tokeny przy ustawieniach domyślnych.
Jak użyć go dla innego języka?
justext.justext(html, justext.get_stoplist("German")). get_stoplists() zwraca wszystkie 100 dostępnych list. Stoplista jest językowo-specyficznym wejściem leksykalnym dla klasyfikatora, który dodatkowo korzysta z długości akapitu, gęstości linków, segmentacji wyprowadzonej z HTML, odległości od nagłówków i kontekstu sąsiednich bloków. To zmienia język wejściowy; samo w sobie nie potwierdza jakości ekstrakcji po niemiecku.
Czego tutaj nie testowano?
Prawdziwych stron internetowych — to są kontrolowane testy z oznaczonymi jednostkami. Zdolności wielojęzycznej, która jest głównym atutem biblioteki, choć spisano 100 list stopwords, nie oceniano na tekście innym niż angielski. Krawędziowych przypadków kodowania, mimo że jusText udostępnia parametry encoding, default_encoding i enc_errors. A max_heading_distance oraz dwa progi udziału stopwords pozostawiono przez cały czas na ustawieniach domyślnych.


