BeautifulSoup w 2026: Najbardziej przyjazny parser HTML jest też najwolniejszy (o 12–17x)

Ostatnia aktualizacja: July 17, 2026
BeautifulSoup w 2026: Najbardziej przyjazny parser HTML jest też najwolniejszy (o 12–17x)
Podsumowanie AI
Ta recenzja pokazuje BeautifulSoup jako najbardziej przystępny parser HTML w Pythonie i precyzyjnie mierzy koszt tej wygody. Porównuje bs4 z parserami opartymi na C pod kątem szybkości, odporności na uszkodzony HTML, pokrycia selektorów CSS, zatrzymywania obiektów i odzyskiwania kodowania. Artykuł pokazuje, że BeautifulSoup jest wyraźnie wolniejszy — często o 12 do 17 razy — ale tłumaczy też, dlaczego developerzy nadal po niego sięgają: czytelne API, wyrozumiałe parsowanie, świetne wsparcie soupsieve dla selektorów i bardzo dobra ergonomia przy jednorazowych, „brudnych” zadaniach scrapingowych. To praktyczny przewodnik po tym, kiedy koszt szybkości jest akceptowalny, a kiedy lepiej wybrać szybszy parser.

BeautifulSoup to biblioteka, po którą niemal każdy sięga na samym początku, kiedy w Pythonie zaczyna web scraping stron internetowych — i faktycznie jest najwolniejsza spośród porządnych parserów HTML. Obie te rzeczy są prawdziwe i żadna nie jest zarzutem. Najciekawsze jest to, że „najwolniejszy” da się tu ująć w konkretną, policzalną liczbę, a nie tylko w ogólne wrażenie.

Przeprowadziłem bs4 (czyli beautifulsoup4, wersja 4.15.0, wydana w czerwcu 2026, na licencji MIT) przez zestaw świeżych testów możliwości oraz ponownie wykorzystanych pomiarów czasu z tego samego stanowiska testowego. Obraz jest spójny: w zamian za mniej więcej rząd wielkości niższą wydajność dostajesz najbardziej przyjazne API i najlepszą odporność na błędy w całej grupie. To, czy taki kompromis ma sens, zależy wyłącznie od Twojego obciążenia, więc w tej recenzji obie strony medalu zostają na stole.

Czym BeautifulSoup naprawdę jest, a czym nie jest

Większość poradników pomija najważniejszy szczegół: BeautifulSoup nie parsuje HTML. To opakowanie. Pod spodem przekazuje dokument do jednego z trzech prawdziwych parserów — wbudowanego w Pythona html.parser, lxml albo html5lib — a potem otacza wygodne drzewo jednym, bardzo przyjaznym API do nawigacji i wyszukiwania. Zadaniem bs4 nie jest samo parsowanie. Jego celem jest sprawienie, by wynik był po prostu przyjemny w użyciu.

Sam autor określa tę bibliotekę jako „screen-scraping library” i od początku obietnica była ta sama: pokaż jej HTML tak popsuty, że przeglądarka by się skrzywiła, a ona i tak wyciągnie dane, o które prosisz. Ta reputacja jest zasłużona, z jednym zastrzeżeniem, do którego zaraz dojdziemy.

Zanim przejdziemy dalej, warto ustalić kilka faktów:

PoleWartość
Pakietbeautifulsoup4 (import jako bs4)
Testowana wersja4.15.0 (wgrana 2026-06-07)
Wymagania dla Pythona>=3.7.0
LicencjaMIT
Oficjalna stronacrummy.com/software/BeautifulSoup
Źródło + tracker błędówLaunchpadnie GitHub
UtrzymanieAktywne (4.15.0 w czerwcu 2026, sześć wydań w ostatnim roku)

To „nie GitHub” ma większe znaczenie, niż wygląda. bs4 to 20-letnia biblioteka rozwijana na crummy.com i Launchpad, więc zwykłe sprawdzanie liczby gwiazdek na GitHubie nie ma tu zastosowania. Stan projektu lepiej oceniać po tempie wydań — a pod tym względem biblioteka ma się dobrze.

Jedna ważna uwaga licencyjna dla osób, które muszą to tłumaczyć działowi compliance: sam wrapper jest na MIT, ale to, co faktycznie trafia do zależności po instalacji bs4, zależy od backendu. html.parser jest częścią standardowej biblioteki Pythona (licencja PSF, brak dodatkowych zależności). lxml działa na BSD, ale opiera się na libxml2/libxslt — zewnętrznej zależności C, którą trzeba albo skompilować, albo pobrać jako gotowy wheel. html5lib to czysty Python i MIT. Jeśli zależy Ci na najczystszym łańcuchu zależności, wbudowany html.parser daje to najlepiej — i właśnie on, paradoksalnie, ma największą pułapkę. Za chwilę.

Koszt szybkości, policzony

Najpierw liczba, bo to nagłówek i ukrywanie jej byłoby nieuczciwe. W realistycznym zadaniu „parsuj i wyciągnij” — najpierw parsowanie ciągu, potem pobranie wszystkich <h3 class="title"> i wszystkich <a href> — BeautifulSoup jest w tym porównaniu najwolniejszym parserem i nie jest to nawet blisko.

Koszt szybkości BeautifulSoup: 232 ms wobec 15 ms dla parserów C

Te czasy pochodzą ponownie z benchmarku selectolax (ten sam komputer, ta sama metodologia 3 przebiegów, stan na 2026-07-13); ta recenzja nie uruchamia własnego benchmarku czasowego, żeby nie powodować konfliktu CPU i nie dublować pracy. Mediana p50 opóźnienia w milisekundach:

Rozmiar stronybs4 (html.parser)bs4 (lxml)selectolax-Lexborlxmlbs4-hp wolniejszybs4-lxml wolniejszy
1 KB0.3230.2810.0270.03612.0x10.5x
10 KB2.0491.7050.1600.16612.8x10.7x
100 KB20.59916.5401.4641.42314.1x11.3x
1 MB232.558181.85514.90114.17715.6x12.2x
10 MB2788.7462261.557159.935172.93317.4x14.1x

Czyli bs4(html.parser) działa mniej więcej 12–17x wolniej niż parser C, taki jak selectolax-Lexbor, a przejście na backend lxml poprawia to tylko do 10.5–14x — nadal cały rząd wielkości gorzej. Powód jest strukturalny, nie jest to błąd: niezależnie od tego, który backend wykonuje samo parsowanie, bs4 tworzy pełny obiekt Pythona (Tag albo NavigableString) dla każdego węzła. Ta warstwa materializacji obiektów to koszt, którego parsery C po prostu nie ponoszą.

Zwróć uwagę, że mnożnik rośnie wraz z wielkością strony — 12.0x przy 1 KB, 17.4x przy 10 MB. To pokazuje, że nie mamy tu do czynienia ze stałym kosztem startowym, który można „rozsmarować” na większym wejściu. To opłata za każdy węzeł, rosnąca liniowo wraz z liczbą tworzonych elementów.

Teraz spojrzenie z innej strony, bo „10x wolniej” brzmi groźniej, niż zwykle jest w praktyce. Przy stronie 1 MB to 232 ms wobec 15 ms. Jeśli Twoim zadaniem jest „przeskanuj kilkaset do kilku tysięcy stron, każda po kilkaset KB”, ta różnica praktycznie znika — nie odczujesz jej, a optymalizacja nic Ci nie da. Jeśli natomiast budujesz pipeline na milion stron, ten sam stosunek oznacza różnicę między zadaniem, które kończy się powodzeniem, a takim, które się nie domyka. Ta sama liczba, ale zupełnie inny werdykt. Oceniaj ją względem rzeczywistej skali, a nie względem benchmarku.

Nie, zmiana backendu tego nie naprawi

Krąży uporczywy mit, że wystarczy podać bs4 backend lxml, żeby dostać szybkość lxml. To nie działa i warto zrozumieć dlaczego. W zapytaniu CSS na paczce 100 000 węzłów (wybierz każdy <a> i odczytaj jego href, przy wcześniej zbudowanym drzewie) różnica przepustowości jest bardzo wyraźna:

ParserCzas zapytania p50Węzłów/s
lxml33.30 ms3,002,646
selectolax-Modest34.19 ms2,924,550
selectolax-Lexbor39.46 ms2,534,027
bs4 (lxml)250.56 ms399,111

bs4(lxml) osiąga około 399 000 węzłów/s — czyli mniej więcej 6.3–7.5x wolniej niż trzy silniki C, mimo że jego własny backend jest lxml. Backend przyspiesza budowę drzewa. Samo zapytanie i przechodzenie po drzewie nadal idzie przez soupsieve do obiektów bs4 Tag, a każdy dopasowany węzeł nadal jest opakowany w Pythona. Czyli model mentalny „dam bs4 lxml i będzie tak szybki jak lxml” jest błędny: backend przyspiesza jeden etap, a najwolniejszy etap nie jest właśnie tym etapem.

Pamięć i zimny start domykają koszt. Na dokumencie 10 MB bs4 zużywa około 1.5–1.75x pamięci rezydentnej selectolax albo lxml (218–226 MB wobec 129–145 MB) — ten sam główny powód, czyli jeden obiekt Pythona na węzeł. Do tego import bs4 trwa około 33.4 ms wobec 14.1 ms dla lxml.html, więc jest 2.36x wolniejszy. To ostatnie nie ma znaczenia w długodziałającym procesie, ale dla narzędzia CLI albo funkcji serverless, która często startuje „na zimno”, to mały, ale realny koszt, o którym warto wiedzieć.

Dlaczego więcej wątków Ci nie pomoże

Jeśli pierwszym odruchem na wolne zadanie CPU-bound jest „dodajmy wątki”, bs4 Cię za to ukarze. Na stronie 1 MB parsowanej 48 razy, porównanie jednowątkowo vs cztery wątki wygląda tak:

Parser1 wątek4 wątkiPrzyspieszenie
selectolax-Lexbor0.563 s0.159 s3.54x
lxml0.459 s0.378 s1.21x
bs4 (lxml)6.945 s26.842 s0.26x

Przeczytaj ostatni wiersz jeszcze raz. Cztery wątki sprawiły, że bs4 był około 3.9x wolniejszy, a nie szybszy. Wniosek empiryczny jest prosty: najpewniej trzyma GIL — budowa drzewa w bs4 jest czystym Pythonem, więc serializuje się pod Global Interpreter Lock, a dokładanie wątków tylko dokłada narzut planowania do zadania, które i tak nie może faktycznie działać równolegle. selectolax zyskuje swoje ~3.5x, bo jego rdzeń w C zwalnia blokadę; bs4 takiej przestrzeni nie ma.

W erze free-threading praktyczny wniosek jest taki: jeśli chcesz równoleglić BeautifulSoup, użyj multiprocessing (ProcessPoolExecutor), a nie wątków. selectolax i lxml dobrze skalują się na wątkach; bs4 nie. Jedno zastrzeżenie dla rygoru: to pojedyncza obserwacja dla jednej liczby wątków (4) i jednego rozmiaru strony (1 MB), a mechanizm „trzyma GIL” jest hipotezą wyprowadzoną z czasu wykonania, nie czymś, co potwierdziłem instrumentując, która dokładnie ścieżka kodu trzyma blokadę. Kierunek jest jasny, ale dokładny mechanizm pozostaje roboczy.

Domyślny backend to pułapka. Przeczytaj to najpierw.

Jeśli z tej recenzji masz zapamiętać tylko jedną rzecz, niech to będzie ta. Zwykłe BeautifulSoup(html) bez drugiego argumentu używa html.parser, a html.parser nie implementuje reguł opcjonalnych tagów końcowych HTML5. Brzmi akademicko, dopóki po cichu nie zepsuje Ci danych.

Macierz tolerancji backendów BeautifulSoup: html.parser 12/15, lxml i html5lib 15/15

Przepuściłem 15 celowo uszkodzonych próbek HTML przez wszystkie trzy backendy, a dla każdej wcześniej zarejestrowałem niezależne od backendu oczekiwanie strukturalne, żeby nikt nie mógł „wybrać zwycięzcy” po fakcie. Wyniki:

BackendZgodność z oczekiwaniem / 15
lxml15
html5lib15
html.parser12

Wszystkie trzy porażki mają jedną wspólną przyczynę. Weźmy niezamkniętą tabelę: <table><tr><td>a<td>b<tr><td>c<td>d</table>. W html.parser wyciągnięty tekst komórek wygląda jak ['abcd','bcd','cd','d'] — każdy <td> „połyka” wszystko, co jest po nim, bo parser zagnieżdża komórki zamiast je zamykać. lxml i html5lib zwracają poprawne ['a','b','c','d']. Tak samo zachowują się zwykłe elementy listy: <li>a<li>b<li>c daje zagnieżdżone ['abc','bc','c'] w html.parser oraz czyste ['a','b','c'] w pozostałych dwóch. Duplikaty atrybutów też się odwracają — <div id="first" id="second"> zachowuje "second" w html.parser, ale "first" w lxml/html5lib, a specyfikacja HTML5 każe zachować pierwszy.

Dlaczego to jest niebezpieczne, a nie tylko irytujące? Bo dzieje się bez błędu. Scraper, który beztrosko robi BeautifulSoup(html) i trafi na niezamkniętą tabelę albo listę — co niestety jest bardzo częste na starych stronach, ręcznie pisanym HTML-u i w szablonach, gdzie ktoś zapomniał domknąć tag — połączy sąsiedni tekst komórek w jedno pole, zwróci brudne dane i nawet nie mrugnie. Naprawa to jeden argument: BeautifulSoup(html, "lxml") albo BeautifulSoup(html, "html5lib").

Na obronę html.parser: pozostałe 12 z 15 uszkodzonych próbek wyszło identycznie we wszystkich trzech backendach — źle zagnieżdżone tagi typu <b><i></b></i>, brakujący szkielet html/body, niecytowane atrybuty, porzucone tagi zamykające, niezamknięte komentarze, zagnieżdżone formularze, mieszana wielkość liter i inne. Odporność bs4 na błędy jest naprawdę szeroka; rozbieżności skupiają się niemal wyłącznie na rodzinie opcjonalnych tagów końcowych. I nic z tego nie jest odkryciem — dokumentacja bs4 w sekcji „Differences between parsers” już wprost mówi, że html.parser jest „less lenient”. To, co daje ta macierz uszkodzonych przypadków, to konkretne, powtarzalne sytuacje, w których „mniej wyrozumiały” oznacza po prostu błędny wynik.

Co zyskujesz: API i CSS są naprawdę świetne

Czyli bs4 jest wolny, jednowątkowy i ma pułapkę w domyślnym backendzie. A jednak ludzie wciąż po niego sięgają, bo ta „przyjazna” strona kompromisu jest jak najbardziej realna — i potwierdza się w testach.

Pokrycie CSS przez soupsieve w BeautifulSoup wygrywa z wynikiem 41/41 plus 20/20

Przeprowadziłem 29 testów API obejmujących wyszukiwanie, CSS, nawigację po drzewie, ekstrakcję tekstu i modyfikację DOM. Wszystkie 29 przeszły, a wynik każdego był liczony przez porównanie zwróconej wartości z oczekiwaną, a nie „na oko”. Dwie z tych możliwości to wygoda, której parsery C po prostu nie oferują:

  • Predykaty funkcyjne w find / find_all. Możesz napisać soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])) i wyrazić złożony warunek w jednej linii Pythona — bez kroku „wybierz wszystko, potem filtruj”.
  • Nazwana, dwukierunkowa nawigacja po drzewie. .parent, .next_sibling, .find_parent, .stripped_strings, .descendants — przejścia są czytelne i działają w obie strony. selectolax w części z nich wymaga kilku kroków albo w ogóle ich nie oferuje.

To jest właśnie ta część „oszczędza czas developerom” zamieniona na coś konkretnego. To nie marketing; to 29 zielonych checków.

Dwie pułapki, które warto odnotować, bo uczciwa recenzja pokazuje obie strony. Po pierwsze, atrybuty boolowskie: <input disabled> zwraca w bs4 pusty ciąg "" dla disabled (selectolax zwraca None). Oba wyniki są fałszywe, więc if node.get("disabled") po cichu pominie atrybut, który faktycznie istnieje w obu bibliotekach — bezpieczny test to "disabled" in tag.attrs. Po drugie, get_text(strip=True) skleja tekst węzłów bez separatora po obcięciu spacji, więc "...with " + "link1" daje "withlink1". Gdy potrzebujesz granic słów, podaj separator=" ". Żadna z tych pułapek nie jest specyficzna tylko dla bs4; obie to problemy wspólne dla kilku bibliotek.

I teraz rzecz, która zaskakuje ludzi: wybór bs4 nie oznacza rezygnacji z pokrycia CSS. Jego silnik CSS, soupsieve, ma najpełniejszą implementację w całym porównaniu. Na bazowej macierzy 41 przypadków (ponownie użytej z benchmarku selectolax) soupsieve zdobył 41/41 — jedyny wynik idealny w całej grupie, przed selectolax-Lexbor z 39/41 i cssselect (lxml/parsel) z 37/41. Następnie uruchomiłem 20 dodatkowych rozszerzonych przypadków, które dokumentacja soupsieve wprost reklamuje, i wynik był 20/20, w tym selektory odrzucane przez Lexbor: :lang(en), charakterystyczne tylko dla soupsieve :-soup-contains('featured'), :is(), :where() oraz :has(> a). Prawdziwe braki to XPath (soupsieve obsługuje tylko CSS) oraz pseudoelementy ::text / ::attr() z parsel, które są rozszerzeniem Scrapy. Jeśli żyjesz w XPath, migracja będzie bolesna.

Wniosek dla tej części jest prosty: to, co tracisz wybierając BeautifulSoup, to szybkość. Nie tracisz ergonomii API i zdecydowanie nie tracisz pokrycia CSS.

Dwie produkcyjne pułapki, na które warto zaplanować budżet

Poza domyślnym backendem są jeszcze dwa zachowania, które uderzą Cię szczególnie w długich przebiegach albo w zadaniach z nietypowym kodowaniem spoza UTF-8.

Cykl referencji: w długich pętlach wywołuj decompose()

Każdy Tag w bs4 ma referencję do rodzica i do dzieci, co tworzy cykl referencji. Zwykłe zliczanie referencji w CPythonie nie potrafi samo odzyskać takiego cyklu — od tego jest generacyjny garbage collector. Żeby sprawdzić, jak duże to ma znaczenie, zbudowałem i usunąłem drzewo 300 razy przy wyłączonym GC, a potem policzyłem obiekty Tag, które nadal siedziały w pamięci:

Cykl referencji BeautifulSoup zatrzymuje 120900 obiektów przy wyłączonym GC i 0 przy włączonym GC

ScenariuszObiektów Tag zachowanych po del
GC wyłączony120,900 (300 cykli, nic nie odzyskano)
GC włączony26,598 (generacyjny GC uruchomił się w trakcie pętli)
Po wymuszonym gc.collect()0 (wszystko odzyskane)
Kontrola bez cyklu (lista stringów, GC wyłączony)delta 0

Przy wyłączonym GC del soup nie odzyskał niczego — wszystkie 120 900 obiektów zostały w pamięci, bo cykl referencji pokonuje zwykłe zliczanie referencji. Jedno gc.collect() usuwało wszystko. Grupa kontrolna bez cyklu (zwykła lista stringów, z definicji bez cyklu) miała deltę równą zero, co potwierdza, że narastanie pochodziło z cyklu bs4, a nie z szumu pomiarowego. Sama dokumentacja bs4 mówi, że obiekty są „densely interconnected ... exactly the sort a garbage collector would have trouble with”, więc to zachowanie jest opisane; test dodaje tu konkretną liczbę zatrzymanych obiektów i dowód, że collect() zeruje stan.

Praktyczna zasada jest prosta: w pipeline, który parsuje wiele dużych stron w ciasnej pętli, jeśli Twój kod — albo jakieś agresywne ustawienie wydajnościowe — wyłącza GC lub nie uruchamia go wystarczająco często, drzewa bs4 będą zalegać, a pamięć będzie rosła. Wywołuj soup.decompose() po każdej stronie — bs4 udostępnia to właśnie po to, by przerwać cykl i odzyskać pamięć wcześniej. Drzewa C z selectolax i lxml nie mają tego problemu.

Kodowanie: UnicodeDammit to cicha przewaga bs4

bs4 ma komponent, którego szybkie parsery nie mają: UnicodeDammit, który wykrywa kodowanie dokumentu i automatycznie konwertuje je do Unicode. Dałem mu ośmiopunktową macierz „deklarowane vs. rzeczywiste kodowanie”:

UnicodeDammit w BeautifulSoup odzyskuje 5 z 8 przypadków kodowania

PrzypadekPrawdziwe kodowanieCo zgadł UnicodeDammitOdzyskane?
utf8_no_declutf-8utf-8Tak
utf16_bomutf-16utf-16leTak
gbk_chinesegbkgb18030Tak (superset)
shiftjisshift_jiscp932Tak (superset)
latin1_declared_utf8latin-1 (zadeklarowane utf-8)iso-8859-1Tak (zignorował kłamstwo)
latin1_no_decllatin-1cp720Nie
cp1252_no_declcp1252cp862Nie
utf8_declared_latin1utf-8 (zadeklarowane latin-1)iso-8859-1Nie (uwierzył kłamstwu)

Pięć z ośmiu zostało odzyskanych. UTF-8, UTF-16 z BOM, GBK, Shift-JIS, a nawet źle oznaczony latin-1 wróciły poprawnie, a zgadywane „supersety” (GBK→gb18030, Shift-JIS→cp932) nadal dekodują się dobrze. Dwie klasy porażek warto znać: krótkie próbki bajtów latin-1/cp1252 bywają uznane za strony DOS-owe, bo detektor statystyczny nie jest wiarygodny na krótkich wejściach, a znaki rysunkowe DOS pokrywają się z punktami kodowymi Latin-1; oraz gdy <meta charset> jest po prostu błędne, UnicodeDammit ufa deklaracji. Dokumentacja bs4 ostrzega o obu tych przypadkach — próbka może być „so short that Unicode, Dammit can't get a lock on it”, a większa ilość danych daje lepszy wynik.

Na tle selectolax, który cicho psuje bajty spoza UTF-8 i oczekuje, że sam je wcześniej zdekodujesz, to realna zaleta: bs4 przynajmniej próbuje wykryć kodowanie i często mu się to udaje. Ale nie jest to gwarancja. Jeśli znasz kodowanie, nie zgaduj — podaj je wprost: BeautifulSoup(bytes, from_encoding="...").

Czy backendy naprawdę różnią się na prawdziwych stronach?

Macierz błędnych danych pokazuje rozbieżności backendów na celowo uszkodzonym HTML-u. Naturalne następne pytanie brzmi: czy ma to znaczenie w realnym świecie? Dlatego przepuściłem wszystkie trzy backendy przez 11 rzeczywistych pobranych stron — BBC, Wikipedia, Craigslist, MDN, old.reddit, dokumentację Pythona, Hacker News, Books to Scrape, webscraper.io, whitehouse.gov i stronę z cytatami renderowaną przez JS — porównując liczbę linków, nagłówków i obrazków.

Wszystkie trzy zgodziły się we wszystkich 11 stronach. Zero rozbieżności. To znaczy, że różnice z sekcji o pułapce pojawiają się tylko na celowo uszkodzonym HTML-u; gdy nowoczesna strona produkcyjna jest zbudowana wystarczająco poprawnie — nawet jeśli jest „brudna” — wybór backendu nie zmienia tego, co wyciągniesz. Praktyczny wniosek: dla mainstreamowych, poprawnie zbudowanych serwisów html.parser jest całkowicie wystarczający i oszczędza zależności. Dopiero gdy scrapujesz widocznie niestandardowy, ręcznie pisany albo bardzo stary HTML, wybór backendu zaczyna wpływać na wynik — i wtedy warto przełączyć się na lxml lub html5lib.

Jest jeszcze jeden szczegół z tego testu, bo to realny przypadek brzegowy. Strona MDN zawiera element <template> i wszystkie backendy bs4 zwróciły 508 linków — czyli bs4 spłaszcza zawartość <template> do głównego drzewa. To stawia bs4 po tej samej stronie co lxml, a po przeciwnej selectolax-Lexbor, który ściśle trzyma się specyfikacji HTML5 (w której <template> jest nieaktywnym DocumentFragment) i zwraca 497, po cichu pomijając 11 linków wewnątrz szablonu. Czyli bs4 wyłapie dane z <template> — przydatne, ale może też zebrać „duchowe” treści, których przeglądarka nigdy by nie wyrenderowała. Żadne z tych zachowań nie jest błędne; to po prostu różne interpretacje specyfikacji i warto wiedzieć, którą z nich dostajesz.

Gdzie BeautifulSoup pasuje, a gdzie nie

Zamiast wciskać wszystko do jednego wyniku 0–100, który ukryłby właśnie te kompromisy, które mają znaczenie, poniżej jest karta ocen na poziomie wymiarów, z komentarzem do każdego wiersza:

WymiarCo pokazały testyUwaga dla czytelnika
Instalacja / pierwszy startCzyste opakowanie, bez przeglądarki i setupu; html.parser bez zależności; wszystkie gotowe wheel'eBackend lxml wymaga zależności C
Szybkość względem parserów C12–17x wolniej (html.parser) / 10.5–14x (backend lxml), dla wszystkich rozmiarówJedno stanowisko; ponownie użyte dane selectolax
Przepustowość zapytań CSS~6–7.5x wolniej na 100k węzłów; backend lxml tego nie ratujeDane ponownie użyte; płaci za koszt obiektu Python Tag
Pamięć1.5–1.75x selectolax/lxml; najwyższy kosztDane ponownie użyte; mierzone RSS
Zimny start importu2.36x wolniej (33.4 vs 14.1 ms)Dane ponownie użyte; mała pozycja
Skalowanie na wątkachbs4-lxml ~3.9x wolniejszy przy 4 wątkach (trzyma GIL)Jedna obserwacja; użyj multiprocessing
Ergonomia API29/29 testów; funkcje-predykaty w find + nawigacja w obie stronyPułapki: pusty string dla atrybutu bool i brak separatora przy strip
Pokrycie CSSsoupsieve najmocniejszy: 41/41 baza + 20/20 rozszerzone; obsługuje :langBez XPath, bez ::text
Odporność trzech backendówlxml/html5lib 15/15; html.parser 12/15Rozbieżności tylko na uszkodzonym HTML-u
Zgodność na prawdziwych stronach3 backendy zgadzają się 11/11; wszystkie spłaszczają <template> (508)Na dobrze zbudowanych stronach backend nie ma znaczenia
GC cykli referencjiDrzewo tworzy cykl; 300 pętli zatrzymało 120,900 obiektów, collect wyzerował wynikW długich pętlach potrzebne decompose()
KodowanieUnicodeDammit odzyskuje 5/8; myli krótkie próbki, ufa błędnym deklaracjomJedna obserwacja
UtrzymanieAktywne (4.15.0, czerwiec 2026); MITStrona na crummy/Launchpad, nie na GitHubie

Więc dla kogo jest BeautifulSoup? Dla osób, które cenią czytelne API i łagodne parsowanie bardziej niż surową przepustowość — przy umiarkowanej skali: prototypy, jednorazowe scrapingi, narzędzia wewnętrzne, zespoły, w których czas programisty kosztuje więcej niż czas wykonania. Kto powinien rozejrzeć się gdzie indziej? Pipeline’y na milion stron, gdzie koszt szybkości zaczyna przekładać się na realne pieniądze, zadania wymagające równoległości na poziomie wątków oraz każdy, kto jest „przywiązany” do XPath.

Jeszcze jedna uwaga o miejscu tej biblioteki w realnym stacku scrapingu i o tym, gdzie wchodzi nasze narzędzie. BeautifulSoup zakłada, że HTML już masz. Nie pobiera stron, nie renderuje JavaScriptu i nie radzi sobie z antybotami ani CAPTCHA — to zupełnie osobne zadanie, a we współczesnym internecie naprawdę trudne. Na innym poziomie działa API do scrapingu z AI: Thunderbit oferuje stack dla developerów — REST API, serwer MCP i CLI — który zajmuje się pobieraniem, renderowaniem JS i problemem antybotowym, a potem zwraca albo czysty Markdown (POST /distill), albo ustrukturyzowany JSON zgodny ze schematem (POST /extract), bez konieczności ręcznego pisania selektorów. Te narzędzia nie konkurują ze sobą; uzupełniają się. bs4 parsuje HTML, który już posiadasz, a API, MCP i CLI Thunderbit pomagają zdobyć HTML, do którego inaczej trudno byłoby dotrzeć. Jeśli Twoim wąskim gardłem jest parsowanie, bs4 jest dobrym wyborem. Jeśli Twoim wąskim gardłem jest pozyskanie danych, potrzebujesz innej warstwy.

Wypróbuj Thunderbit do ekstrakcji danych z sieci

Najkrócej mówiąc

BeautifulSoup daje najbardziej przyjazne API, najlepszą tolerancję na uszkodzony HTML i najpełniejszy silnik CSS w tym porównaniu — ale płacisz za to mniej więcej rzędem wielkości niższą szybkością i największym zużyciem pamięci. To cały kompromis, wprost. Domyślny backend html.parser to jedyna prawdziwa pułapka: po cichu psuje niezamknięte tabele i listy, więc gdy wejście może być brzydkie, podawaj zawsze "lxml" albo "html5lib". Wątki go nie przyspieszą — zrobi to multiprocessing. A w długich pętlach wywołuj decompose() dla każdej strony, żeby cykle referencji nie narastały.

Dwie ostatnie uwagi. Wszystko tutaj było mierzone na jednej platformie (macOS arm64, Python 3.14, gotowe wheel'e), a mnożniki czasu pochodzą ponownie z benchmarku selectolax (ten sam test, stan na 2026-07-13), zamiast być uruchamiane od zera — więc dziedziczą ograniczenia jednej platformy, a konfiguracja Linux x86_64 albo kompilacja ze źródeł może przesunąć dokładne liczby. I nic z tych wyników nie jest nowym odkryciem: bs4 to 20-letnia biblioteka, więc każde testowane zachowanie jest albo opisane w dokumentacji, albo publicznie znane. Wartość nie polega na sensacji. Chodzi o to, że realną liczbą opisano kompromisy, które dokumentacja opisuje tylko jakościowo.

Najczęściej zadawane pytania

Czy BeautifulSoup jest wolny?
Tak, i da się to zmierzyć. W zadaniu parsowanie + ekstrakcja działa około 12–17x wolniej niż parser C, taki jak selectolax-Lexbor, gdy używasz domyślnego backendu html.parser, oraz 10.5–14x wolniej z backendem lxml, ponieważ tworzy obiekt Pythona dla każdego węzła. To, czy ma to znaczenie, zależy od skali: przy stronie 1 MB to 232 ms wobec 15 ms — różnica niewidoczna przy kilku tysiącach stron, ale decydująca przy pipeline’ie na milion stron.

Którego parsera BeautifulSoup używać — html.parser, lxml czy html5lib?
Dla poprawnych, mainstreamowych stron domyślny html.parser jest wystarczający i nie dodaje zależności. Ale nie implementuje opcjonalnych tagów końcowych HTML5, więc przy niezamkniętych tabelach lub listach potrafi łączyć sąsiedni tekst bez błędu. Gdy wejście może być uszkodzone, ręcznie pisane albo stare, podaj jawnie "lxml" lub "html5lib" — oba zdobyły czyste 15/15 w macierzy uszkodzonego HTML-u, podczas gdy html.parser miał 12/15.

Czy BeautifulSoup da się równoleglić wątkami?
Nie. Budowa drzewa w bs4 jest czystym Pythonem i trzyma GIL, więc dodanie wątków spowalnia zamiast przyspieszać — w testach cztery wątki były przy parsowaniu strony 1 MB około 3.9x wolniejsze niż jeden wątek. Do równoleglenia bs4 używaj multiprocessing (ProcessPoolExecutor). Biblioteki z rdzeniem C, takie jak selectolax i lxml, to te, które korzystają na paralelizacji wątkowej.

Czy BeautifulSoup dobrze radzi sobie z uszkodzonym HTML-em?
Ogólnie tak — w szerokim zakresie uszkodzonych próbek (źle zagnieżdżone tagi, brakujący szkielet, niecytowane atrybuty i więcej) wszystkie trzy backendy odzyskiwały dane poprawnie. Słaby punkt to domyślny html.parser i opcjonalne tagi końcowe: niezamknięte <td>/<li> są zagnieżdżane zamiast zamykane, co psuje wyciągany tekst. Przełącz na backend lxml albo html5lib, a ten problem znika.

BeautifulSoup vs lxml — co jest lepsze?
To różne narzędzia. lxml jest znacznie szybszy zarówno przy budowaniu drzewa, jak i przy zapytaniach, i obsługuje XPath. BeautifulSoup opakowuje lxml (i inne backendy) w dużo przyjaźniejsze API i ma nawet szersze pokrycie CSS dzięki soupsieve. Nie oczekuj jednak, że backend lxml zrobi z bs4 coś tak szybkiego jak lxml — backend przyspiesza tylko parsowanie, a zapytania i nawigacja nadal płacą koszt obiektu Pythona na węzeł, przez co na dużych zbiorczych selekcjach jest około 6–7.5x wolniejszy.

Wypróbuj Thunderbit do ekstrakcji danych z sieci Get Started Free

Ke
Ke
CTO w Thunderbit | Starszy data scientist i ekspert ML Dzięki prawie dziesięciu latom doświadczenia w uczeniu maszynowym i data science, Ke Shen jest absolwentem Columbia University i byłym starszym data scientistą w Walmart Labs. Dysponując dogłębną, uznaną przez branżowych ekspertów wiedzą w zakresie Python, R, Java i statystyki, dzieli się sprawdzonymi w boju spostrzeżeniami na temat wdrażania złożonych algorytmów AI — od teorii po architekturę gotową do produkcji.

Wypróbuj Thunderbit

Zbieraj leady i inne dane w zaledwie 2 kliknięcia. Wspierane przez AI.

Pobierz Thunderbit To darmowe
Wyciągaj dane z użyciem AI
Łatwo przenoś dane do Google Sheets, Airtable lub Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week