BeautifulSoup je knihovna, po které sáhne téměř každý při prvním web scraping v Pythonu, a zároveň je opravdu nejpomalejší z důležitých HTML parserů. Obojí je pravda a ani jedno není výtka. Zajímavé je, že „nejpomalejší“ se ukazuje jako přesné, měřitelné číslo, ne jen pocit.
Prohnal jsem bs4 (tedy beautifulsoup4, verze 4.15.0, vydaná v červnu 2026, s licencí MIT) sadou nových testů schopností a zároveň znovu použil timingová data ze stejného benchmarkovacího setupu. Výsledek je konzistentní: za přibližně řádovou ztrátu rychlosti dostanete nejpřívětivější API a nejvyšší toleranci chyb v oboru. Jestli je takový kompromis chytrý, záleží čistě na vašem workloadu, takže tahle recenze ukazuje obě strany mince.
Co BeautifulSoup ve skutečnosti je — a co není
Většina návodů přeskočí to nejdůležitější: BeautifulSoup HTML ve skutečnosti neparsuje. Je to obal. V zákulisí předá váš dokument jednomu ze tří skutečných parserů — vestavěnému Python html.parser, lxml nebo html5lib — a pak výsledný strom zabalí do jednoho velmi pohodlného rozhraní pro navigaci a vyhledávání. Úkolem bs4 není parsovat. Jeho úkolem je udělat výsledek příjemný na práci.
Jeho autor tomu říká „screen-scraping library“ a nabídka je pořád stejná: pošlete mu HTML tak rozbité, že by z něj prohlížeč cukal, a stejně z něj vytáhne data, která chcete. Ta pověst je zasloužená, s jednou hvězdičkou, ke které se dostaneme.
Než půjdeme dál, stojí za to si ujasnit pár faktů:
| Pole | Hodnota |
|---|---|
| Balíček | beautifulsoup4 (import jako bs4) |
| Testovaná verze | 4.15.0 (nahrána 2026-06-07) |
| Požadavek na Python | >=3.7.0 |
| Licence | MIT |
| Oficiální domov | crummy.com/software/BeautifulSoup |
| Zdroj + bug tracker | Launchpad — ne GitHub |
| Údržba | Aktivní (4.15.0 v červnu 2026, šest releasů za poslední rok) |
To „ne GitHub“ je důležitější, než vypadá. bs4 je dvacet let stará knihovna, která žije na crummy.com a Launchpadu, takže obvyklé „kolik má hvězdiček na GitHubu“ tu neplatí. Posuzujte její kondici podle tempa vydávání verzí — a podle toho je na tom velmi dobře.
Jeden drobný detail k licenci pro ty, kdo musí odpovídat compliance týmu: wrapper je MIT, ale to, co se vám při použití bs4 skutečně natáhne do dependency tree, závisí na backendu, který nainstalujete. html.parser je součást standardní knihovny Pythonu (licence PSF, žádné extra závislosti). lxml je BSD, ale stojí na libxml2/libxslt — externí C závislosti, které buď kompilujete, nebo berete jako předpřipravený wheel. html5lib je čistý Python a MIT. Pokud chcete nejčistší dependency footprint, vestavěný html.parser vám ho dá — a právě ten má zároveň největší háček. K tomu hned.
Cenovka za rychlost, přesně spočítaná
Nejdřív si řekněme čísla, protože to je hlavní pointa a bylo by nepoctivé ji schovávat. Na realistickém úkolu typu „parsuj a vytěž“ — tedy parse string, vytáhni všechna <h3 class="title"> a všechna <a href> — je BeautifulSoup v tomto srovnání nejpomalejší parser, a není to ani trochu těsné.

Tyto časy jsou převzaté z benchmarkovacího setupu pro selectolax (stejný stroj, stejná metodika 3 běhů, stav k 2026-07-13); tahle recenze žádný vlastní timingový benchmark nepřepočítávala, aby nevznikala CPU kontence a zdvojená práce. Medián p50 latence v milisekundách:
| Velikost stránky | bs4 (html.parser) | bs4 (lxml) | selectolax-Lexbor | lxml | bs4-hp pomalejší | bs4-lxml pomalejší |
|---|---|---|---|---|---|---|
| 1 KB | 0.323 | 0.281 | 0.027 | 0.036 | 12.0x | 10.5x |
| 10 KB | 2.049 | 1.705 | 0.160 | 0.166 | 12.8x | 10.7x |
| 100 KB | 20.599 | 16.540 | 1.464 | 1.423 | 14.1x | 11.3x |
| 1 MB | 232.558 | 181.855 | 14.901 | 14.177 | 15.6x | 12.2x |
| 10 MB | 2788.746 | 2261.557 | 159.935 | 172.933 | 17.4x | 14.1x |
Takže bs4(html.parser) běží asi 12–17× pomaleji než C parser typu selectolax-Lexbor a přechod na backend lxml ho stáhne jen na 10.5–14× — pořád o celou řádovou třídu zpět. Důvod je strukturální, ne chyba: ať už HTML parsuje kterýkoli backend, bs4 z každého uzlu vytváří plnohodnotný Python objekt (Tag nebo NavigableString). Tahle vrstva materializace objektů je daň, kterou C parsery prostě neplatí.
Všimněte si, že multiplikátor roste s velikostí stránky — 12.0× na 1 KB, 17.4× na 10 MB. To znamená, že nejde o fixní startovní režii, kterou by šlo rozmělnit. Je to daň za každý uzel, která roste lineárně s tím, kolik uzlů vytvoříte.
A teď přerámování: „10× pomalejší“ zní hrozivěji, než bývá v praxi. Na 1MB stránce je to 232 ms versus 15 ms. Pokud je vaše práce „web scraping pár stovek až pár tisíc stránek po několika stovkách KB“, ten absolutní rozdíl je v podstatě neviditelný — vůbec ho nepocítíte a optimalizace vám nic nepřinese. Pokud ale běží milionstránkový pipeline, stejný poměr rozhoduje o tom, jestli job doběhne, nebo ne. Stejné číslo, opačný verdikt. Posuzujte ho podle skutečného objemu, ne podle benchmarku.
Ne, změna backendu to nespasí
Stále se drží mýtus, že když bs4 předáte backend lxml, dostanete rychlost lxml. Nedostanete, a je dobré chápat proč. Na CSS dotazu nad dávkou 100 000 uzlů (vyber všechna <a> a přečti jejich href, strom už je předem postavený) je rozdíl propustnosti výrazný:
| Parser | Dotaz p50 | Uzlů za sekundu |
|---|---|---|
| lxml | 33.30 ms | 3,002,646 |
| selectolax-Modest | 34.19 ms | 2,924,550 |
| selectolax-Lexbor | 39.46 ms | 2,534,027 |
| bs4 (lxml) | 250.56 ms | 399,111 |
bs4(lxml) zvládne asi 399 000 uzlů za sekundu — zhruba 6.3–7.5× pomaleji než tři C enginy, i když jeho vlastní backend je lxml. Backend zrychluje fázi budování stromu. Dotazování a traversal ale stále procházejí přes soupsieve do objektů Tag v bs4 a každý nalezený uzel se stejně zabalí do Pythonu. Mentální model „dej bs4 lxml a bude lxml rychlé“ je tedy špatně: backend zrychlí jednu fázi, ale nejpomalejší fáze není ona.
Paměť a cold start doplňují náklady. Na 10MB dokumentu spotřebuje bs4 asi 1.5–1.75× residentní paměti oproti selectolax nebo lxml (218–226 MB proti 129–145 MB) — stejná příčina, jeden Python objekt na uzel. A import bs4 trvá asi 33.4 ms proti 14.1 ms u lxml.html, takže je 2.36× pomalejší při importu. To poslední je u dlouho běžícího procesu maličkost, ale pro CLI nástroj nebo serverless funkci s častým cold startem je to malý, ale reálný náklad, který je dobré znát.
Proč vás víc vláken nespasí
Jestli je vaše první reakce na pomalý CPU-bound úkol „naházet na to vlákna“, bs4 vás za ten nápad potrestá. Na 1MB stránce parsované 48krát, jednovláknově vs. ve čtyřech vláknech:
| Parser | 1 vlákno | 4 vlákna | Zrychlení |
|---|---|---|---|
| selectolax-Lexbor | 0.563 s | 0.159 s | 3.54x |
| lxml | 0.459 s | 0.378 s | 1.21x |
| bs4 (lxml) | 6.945 s | 26.842 s | 0.26x |
Ten spodní řádek si přečtěte dvakrát. Čtyři vlákna udělala z bs4 asi 3.9× zpomalení, ne zrychlení. Empirický signál je „pravděpodobně drží GIL“: strom se v bs4 staví v čistém Pythonu, takže se pod Global Interpreter Lock serializuje a přidání vláken jen zvyšuje overhead plánování u úlohy, která reálně paralelně běžet nemůže. selectolax získává své ~3.5× zrychlení proto, že jeho C jádro lock uvolňuje; bs4 takový prostor nemá.
Pro éru free-threadingu je praktický závěr jednoduchý: pokud potřebujete BeautifulSoup paralelizovat, sáhněte po multiprocessing (ProcessPoolExecutor), ne po threadech. selectolax a lxml umí škálovat přes vlákna; bs4 ne. Jedna poznámka k přesnosti: jde o jediné pozorování při jednom počtu vláken (4) na jedné velikosti stránky (1 MB) a mechanismus „drží GIL“ je hypotéza odvozená z wall-clock chování, ne něco, co jsem potvrzoval instrumentací konkrétní lock cesty. Směr je jasný; přesný mechanismus je zatím pracovní hypotéza.
Výchozí backend je past. Tohle si přečtěte jako první.
Jestli si z téhle recenze odnesete jen jednu věc, ať je to tahle. Prosté BeautifulSoup(html) bez druhého argumentu používá html.parser, a html.parser neimplementuje pravidla HTML5 pro volitelné koncové tagy. To zní akademicky, dokud vám to potichu neničí data.

Prohnal jsem 15 úmyslně rozbitých HTML ukázek všemi třemi backendy, přičemž pro každou z nich byla předem zadaná strukturální kontrola nezávislá na backendu (takže nikdo nemůže po výsledku vybírat vítěze). Skóre:
| Backend | Odpovídá očekávání / 15 |
|---|---|
| lxml | 15 |
| html5lib | 15 |
| html.parser | 12 |
Všechny tři chyby mají stejnou příčinu. Vezměme si neuzavřenou tabulku: <table><tr><td>a<td>b<tr><td>c<td>d</table>. U html.parser vyjde extrahovaný text buněk jako ['abcd','bcd','cd','d'] — každé <td> pohltí vše, co je za ním, protože parser buňky vnořuje místo toho, aby je uzavřel. lxml i html5lib správně vrátí ['a','b','c','d']. Totéž dělají prosté položky seznamu: <li>a<li>b<li>c dá pod html.parser vnořený výsledek ['abc','bc','c'], zatímco ostatní dva vrátí čisté ['a','b','c']. Duplicitní atributy se chovají stejně — <div id="first" id="second"> uchová u html.parseru "second", ale u lxml/html5lib "first", a HTML5 specifikace říká ponechat první.
Proč je to nebezpečné a ne jen otravné? Protože se to stane bez chyby. Scraper, který bez rozmyslu použije BeautifulSoup(html) a narazí na neuzavřenou tabulku nebo seznam — což je depresivně běžné na starých webech, ručně psaném HTML i v šablonách, kde chybí closing tag — vám sleje sousední text buněk do jednoho pole, podá špinavá data a ani jednou si nestěžuje. Oprava je jeden argument: BeautifulSoup(html, "lxml") nebo BeautifulSoup(html, "html5lib").
Aby bylo fér k html.parser, ve zbývajících 12 z 15 rozbitých vzorků vyšel výsledek u všech tří backendů stejně — špatně zanořené tagy jako <b><i></b></i>, chybějící skeleton html/body, nequoted atributy, osiřelé closing tagy, neuzavřené komentáře, vnořené formuláře, mix velkých a malých písmen a další. Tolerance bs4 je opravdu vysoká napříč celou oblastí; rozdíly se soustředí téměř výhradně na rodinu volitelných koncových tagů. A nic z toho není objev — v dokumentaci bs4 už samotná sekce „Differences between parsers“ říká, že html.parser je „less lenient“. Přínos téhle matice rozbitých HTML je v konkrétních, reprodukovatelných případech, kde se „méně tolerantní“ mění na špatný výstup.
Co naopak neztrácíte: API a CSS jsou jeho nejsilnější stránka
Takže bs4 je pomalé, jednovláknové a má past ve výchozím backendu. Lidé po něm ale stejně sahají, protože ta „přívětivá“ část kompromisu je naprosto reálná — a v testech to drží.

Spustil jsem 29 API probe testů pokrývajících vyhledávání, CSS, navigaci stromem, práci s textem a úpravy DOM. Všech 29 prošlo a výsledek každého probe byl porovnán s očekávanou hodnotou, ne od oka. Dvě z těchto schopností jsou ergonomie, kterou C parsers prostě nenabízejí:
- Funkční predikáty v
find/find_all. Můžete napsatsoup.find(lambda t: t.name == "a" and "btn" in t.get("class", []))a vyjádřit složitou podmínku jedním řádkem Pythonu — žádný dvoukrok „vyber všechno a pak filtruj“ není potřeba. - Pojmenovaná obousměrná navigace stromem.
.parent,.next_sibling,.find_parent,.stripped_strings,.descendants— traversal se čte skoro jako angličtina a jde na obě strany. selectolax některé z těchto věcí vyžaduje na více kroků, nebo je nenabízí vůbec.
To je ta část „ušetří vývojářský čas“ převedená do praxe. Není to marketing; je to 29 zelených fajfek.
Dvě pasti, které stojí za zmínku, protože férová recenze musí říct obě strany. Za prvé, booleovské atributy: <input disabled> vrátí u bs4 pro disabled prázdný řetězec "" (selectolax vrátí None). Obojí je falsey, takže if node.get("disabled") tiše mine booleovský atribut, který je ve skutečnosti přítomný u obou knihoven — bezpečný test je "disabled" in tag.attrs. Za druhé, get_text(strip=True) spojí text uzlů bez oddělovače poté, co je ořeže, takže "...with " + "link1" se změní na "withlink1". Když potřebujete hranice slov, předejte separator=" ". Ani jedna past není specifická pro bs4; obě jsou křížově knihovní gotcha.
A teď věc, která lidi často překvapí: volba bs4 vás nepřipraví o CSS coverage. Jeho CSS engine, soupsieve, je v tomhle srovnání nejkompletnější implementace. Na základní 41položkové matici (převzaté z benchmarku selectolax) získal soupsieve 41/41 — jediný perfektní výsledek v poli, před selectolax-Lexbor s 39/41 a cssselectem (lxml/parsel) s 37/41. Pak jsem přidal 20 rozšířených případů, které dokumentace soupsieve přímo uvádí, a výsledek byl 20/20, včetně selektorů, které Lexbor rovnou odmítá: :lang(en), soupsieve-only :-soup-contains('featured'), :is(), :where() a :has(> a). Jediné skutečné mezery jsou XPath (soupsieve umí jen CSS) a pseudoprky ::text / ::attr() z parselu, což jsou rozšíření Scrapy. Jestli žijete v XPath, migrace bude bolet.
Verdikt pro tuhle část je jasný: to, co obětujete volbou BeautifulSoup, je rychlost. Ne ergonomie API, a už vůbec ne CSS coverage.
Dvě produkční pasti, na které si vyčleňte rozpočet
Kromě výchozího backendu vás potrápí ještě dvě věci — hlavně v dlouho běžících procesech nebo při práci s ne-UTF-8 vstupem.
Referenční cykly: v dlouhých smyčkách volejte decompose()
Každý Tag v bs4 drží referenci na svého rodiče a zároveň na své děti, čímž vzniká referenční cyklus. Čisté referenční počítání v CPythonu takový cyklus samo neuklidí — to je práce pro generational garbage collector. Aby bylo jasné, jak moc na tom záleží, postavil jsem a smazal strom 300krát se zapnutým GC, a pak spočítal přeživší objekty Tag, které pořád zůstávaly v paměti:

| Scénář | Zachované Tagy po del |
|---|---|
| GC vypnuto | 120,900 (300 cyklů, nic se neuvolnilo) |
| GC zapnuto | 26,598 (generational GC se aktivoval během smyčky) |
Po vynuceném gc.collect() | 0 (vše uvolněno) |
| Kontrolní skupina bez cyklu (seznam stringů, GC vypnuto) | delta 0 |
S vypnutým GC del soup neuvolnil nic — všech 120 900 objektů zůstalo v paměti, protože referenční cyklus poráží reference counting. Jediný gc.collect() je všechny vyčistil. Kontrolní skupina bez cyklu (obyčejný seznam stringů, o kterém víme, že cyklus nemá) měla nulovou změnu, což potvrzuje, že nárůst způsobilo bs4 a ne chyba měření. Dokumentace bs4 sama říká, že objekty jsou „densely interconnected ... exactly the sort a garbage collector would have trouble with“, takže jde o zdokumentované chování; test k tomu přidává konkrétní počet zachovaných objektů a důkaz, že collect() ho vynuluje.
Praktické pravidlo: v pipeline, která v těsné smyčce parsuje mnoho velkých stránek, pokud váš kód (nebo nějaké vysokoprůchodové nastavení) GC vypíná nebo ho nespouští dost často, stromy bs4 budou zůstávat v paměti a ta poroste. Po každé stránce volejte soup.decompose() — bs4 to poskytuje právě proto, aby rozbil cyklus a uvolnil paměť dřív. C stromy v selectolax a lxml tímto problémem netrpí vůbec.
Kódování: UnicodeDammit je tichá výhoda bs4
bs4 obsahuje komponentu, kterou rychlé parsers nemají: UnicodeDammit, která detekuje kódování dokumentu a automaticky ho převede na Unicode. Dal jsem mu 8položkovou matici „deklarované vs. skutečné charset“:

| Případ | Skutečné kódování | UnicodeDammit odhadl | Obnoveno? |
|---|---|---|---|
| utf8_no_decl | utf-8 | utf-8 | Ano |
| utf16_bom | utf-16 | utf-16le | Ano |
| gbk_chinese | gbk | gb18030 | Ano (superset) |
| shiftjis | shift_jis | cp932 | Ano (superset) |
| latin1_declared_utf8 | latin-1 (deklarováno jako utf-8) | iso-8859-1 | Ano (ignoroval lež) |
| latin1_no_decl | latin-1 | cp720 | Ne |
| cp1252_no_decl | cp1252 | cp862 | Ne |
| utf8_declared_latin1 | utf-8 (deklarováno jako latin-1) | iso-8859-1 | Ne (uvěřil lži) |
Pět z osmi bylo obnoveno správně. UTF-8, UTF-16 s BOM, GBK, Shift-JIS a dokonce i špatně označený latin-1 dopadly správně a odhady typu superset (GBK→gb18030, Shift-JIS→cp932) stále dekódují bez problému. Dva režimy selhání je dobré znát: krátké vzorky bajtů v latin-1/cp1252 se vyhodnotí jako DOS code page, protože statistický detektor na krátkých vstupech není spolehlivý a znaky box-drawing z DOSu se překrývají s kódovými body Latin-1; a když je deklarace <meta charset> prostě špatně, UnicodeDammit deklaraci věří. Dokumentace bs4 na obojí upozorňuje — vzorek může být „so short that Unicode, Dammit can't get a lock on it“, a více dat znamená lepší odhad.
Proti selectolax, který ne-UTF-8 bajty potichu zkazí a očekává, že je dekódujete sami, je to skutečná výhoda: bs4 se alespoň pokouší kódování odhadnout a často uspěje. Není to ale garance. Pokud kódování znáte, nehádejte a zadejte ho explicitně: BeautifulSoup(bytes, from_encoding="...").
Liší se backendy na reálných stránkách vůbec?
Rozbitá matice ukazuje, že se backendy rozcházejí na záměrně vadném vstupu. Přirozená další otázka je, jestli na tom v praxi záleží, tak jsem všechny tři backendy pustil přes 11 reálně stažených stránek — BBC, Wikipedia, Craigslist, MDN, old.reddit, Python docs, Hacker News, Books to Scrape, webscraper.io, whitehouse.gov a stránku s quotes renderovanou přes JS — a porovnal počty odkazů, nadpisů a obrázků.
Všechny tři se shodly na všech 11 stránkách. Žádná divergence. To znamená, že rozdíly z části s pastí se objeví jen na záměrně rozbitém HTML; když je moderní produkční web strukturovaný dostatečně dobře — i když je „neuhlazený“ — volba backendu nezmění, co z něj vytáhnete. Prakticky: pro mainstreamové, dobře formované weby je html.parser naprosto v pořádku a ještě vám ušetří závislost. Až když scrapujete zjevně nestandardní, ručně psané nebo hodně staré HTML, začne volba backendu měnit výsledky — a tehdy dává smysl přepnout na lxml nebo html5lib.
Jeden postřeh z toho běhu, protože jde o reálný edge case. MDN stránka obsahuje element <template> a všechny backendy bs4 vrátily 508 odkazů — to znamená, že bs4 obsah <template> rozplácne do hlavního stromu. Tím se bs4 staví na stejnou stranu jako lxml a proti selectolax-Lexbor, který přísně sleduje HTML5 specifikaci (<template> je inertní DocumentFragment) a vrátí 497, tedy tiše zahodí 11 odkazů uvnitř template. bs4 vám tedy zachytí data uvnitř <template> — užitečné, ale zároveň je to způsob, jak získat „fantomový“ obsah, který by prohlížeč nikdy nevykreslil. Ani jedno není špatně; jde o různé výklady specifikace a je dobré vědět, který dostáváte.
Kam BeautifulSoup zapadá — a kam ne
Místo toho, abychom všechno sečetli do jedné známky 0–100 (což by skrylo právě ty trade-offy, na kterých záleží), tady je hodnocení po jednotlivých dimenzích, vždy s jednou výhradou:
| Dimenze | Co ukázaly testy | Upozornění pro čtenáře |
|---|---|---|
| Instalace / první spuštění | Čistý wrapper, žádné browser setup; html.parser bez závislostí; všechny wheel balíčky předpřipravené | Backend lxml potřebuje C závislost |
| Rychlost vs. C parsers | 12–17× pomalejší (html.parser) / 10.5–14× (backend lxml), na všech velikostech | Jediný setup; přebraná data z selectolax |
| Propustnost CSS dotazů | Asi 6–7.5× pomalejší na 100k uzlech; backend lxml to nespasí | Přebráno; platí daň za Python Tag |
| Paměť | 1.5–1.75× selectolax/lxml; nejtěžší | Přebráno; měřeno přes RSS |
| Cold start importu | 2.36× pomalejší (33.4 vs. 14.1 ms) | Přebráno; malá položka |
| Škálování přes vlákna | bs4-lxml ve 4 vláknech asi 3.9× pomalejší (drží GIL) | Jediné pozorování; použijte multiprocessing |
| Ergonomie API | 29/29 probe testů; funkční predikát ve find + obousměrná navigace | Past s prázdným stringem u bool atributů a stripem |
| CSS coverage | soupsieve nejsilnější: 41/41 základ + 20/20 rozšíření; podporuje :lang | Žádné XPath, žádné ::text |
| Tolerance tří backendů | lxml/html5lib 15/15; html.parser 12/15 | Rozdíly jen na rozbitém HTML |
| Shoda na reálných stránkách | 3 backendy 11/11 shoda; všechny flattenují <template> (508) | U dobře formovaných webů backend nerozhoduje |
| GC referenčních cyklů | Strom je cyklus; 300 smyček drželo 120 900 objektů, collect() to vynuloval | V dlouhých smyčkách je potřeba decompose() |
| Kódování | UnicodeDammit obnoví 5/8; u krátkých vzorků odhaduje špatně, u špatných deklarací věří deklaraci | Jediné pozorování |
| Údržba | Aktivní (4.15.0, červen 2026); MIT | Domov na crummy/Launchpad, ne GitHub |
Takže pro koho je BeautifulSoup? Pro každého, kdo chce čitelné API a tolerantní parsování víc než maximální propustnost, a pracuje ve středním objemu — prototypy, jednorázové scrapers, interní nástroje, týmy, kde vývojářský čas stojí víc než runtime. Kdo by měl hledat jinde? Pipeline na milion stránek, kde se cenovka rychlosti nasčítá do reálných peněz, workloady, které potřebují paralelismus na úrovni vláken, a kdokoli, kdo je pevně spojený s XPath.
Krátká poznámka k tomu, kde to sedí v reálném scraping stacku, a kam vstupuje náš vlastní nástroj. BeautifulSoup předpokládá, že HTML už máte. Stránky nestahuje, JavaScript nerenderuje a neřeší anti-bot obranu ani CAPTCHA — to je samostatná a na moderním webu opravdu těžká úloha. Tady je místo pro AI scraping API v jiné vrstvě: Thunderbit pro vývojáře — REST API, MCP server a CLI — obstará stažení, renderování JS i anti-bot problém a vrátí buď čistý Markdown (POST /distill), nebo strukturované JSON podle schématu (POST /extract), aniž byste museli psát selektory. Ty dvě věci spolu nesoupeří; doplňují se. bs4 parsuje HTML, které už držíte v ruce; API, MCP a CLI od Thunderbitu vám dají HTML, ke kterému se jinak snadno nedostanete. Pokud je vaše úzké hrdlo parsování, bs4 je dobrá odpověď. Pokud je úzké hrdlo získání dat, je to jiná vrstva.
Vyzkoušet Thunderbit pro extrakci webových dat
Shrnutí
BeautifulSoup nabízí nejpřívětivější API, nejvyšší toleranci rozbitého HTML a nejkompletnější CSS engine v tomhle srovnání — zaplacené zhruba řádovou ztrátou rychlosti a nejtěžší paměťovou stopou. To je celý obchod, řečeno na rovinu. Výchozí backend html.parser je jediná skutečná past: potichu kazí neuzavřené tabulky a seznamy, takže při nehezkém vstupu vždy předejte "lxml" nebo "html5lib". Vlákna ho nezrychlí — multiprocessing ano. A v dlouhých smyčkách volejte po každé stránce decompose(), aby se nehromadily referenční cykly.
Na závěr dvě omezení. Všechno tady bylo měřeno na jediné platformě (macOS arm64, Python 3.14, předpřipravené wheely) a timingové multiplikátory jsou převzaté ze setupu selectolax (stejný bench, stav k 2026-07-13), ne znovu přepočítané — takže dědí to samé platformní omezení a Linux x86_64 nebo build ze zdrojáků mohou posunout přesná čísla. A nic z těchto výsledků není nový objev: bs4 je dvacet let stará knihovna, takže každé testované chování je buď dokumentované, nebo veřejně zaznamenané. Hodnota není v „senzaci“. Je v tom, že ke kvalitativně popsaným kompromisům dáme reálné číslo.
Často kladené otázky
Je BeautifulSoup pomalý?
Ano, a měřitelně. Na úloze typu parse + extract běží asi 12–17× pomaleji než C parser jako selectolax-Lexbor při výchozím backendu html.parser a 10.5–14× pomaleji s backendem lxml, protože na každý uzel vytváří Python objekt. Jestli na tom záleží, závisí na měřítku: na 1MB stránce je to 232 ms versus 15 ms, což je nevýrazné u pár tisíc stránek, ale rozhodující u milionstránkového pipeline.
Který parser v BeautifulSoup mám použít — html.parser, lxml, nebo html5lib?
Pro dobře formované, mainstreamové weby je výchozí html.parser v pořádku a nepřidá žádné závislosti. Neimplementuje ale HTML5 pravidla pro volitelné koncové tagy, takže u neuzavřených tabulek nebo seznamů potichu míchá sousední text dohromady bez chyby. Když může být vstup rozbitý, ručně psaný nebo starý, předejte explicitně "lxml" nebo "html5lib" — oba měly v matici rozbitého HTML čistých 15/15, zatímco html.parser skončil na 12/15.
Umí BeautifulSoup paralelní běh přes vlákna?
Ne. Strom se v bs4 staví v čistém Pythonu a drží GIL, takže přidání vláken ho zpomalí, ne zrychlí — v testu běžela 1MB parsovací úloha ve čtyřech vláknech asi 3.9× pomaleji než v jednom. Pokud chcete bs4 paralelizovat, použijte multiprocessing (ProcessPoolExecutor). Knihovny s C jádrem, jako selectolax a lxml, jsou ty, které z vláken skutečně těží.
Zvládá BeautifulSoup rozbité HTML dobře?
Obecně ano — napříč různě poškozenými vzorky (špatně zanořené tagy, chybějící skeleton, nequoted atributy a další) se všechny tři backendy zotavily bez problémů. Jediná slabina je výchozí html.parser a volitelné koncové tagy: neuzavřené <td>/<li> se vnoří místo uzavření, čímž zkazí extrahovaný text. Přepněte na backend lxml nebo html5lib a tahle třída problému zmizí.
BeautifulSoup vs lxml — co je lepší?
Jsou to různé nástroje. lxml je výrazně rychlejší při budování stromu i dotazování a podporuje XPath. BeautifulSoup naopak balí lxml (mimo jiné) do mnohem přívětivějšího API a přes soupsieve má dokonce širší CSS coverage. Jen nečekejte, že backend lxml udělá z bs4 lxml rychlý nástroj — backend zrychluje jen parsování, zatímco dotazy a traversal pořád platí daň za Python objekt na uzel, takže na velkých dávkách bývá bs4 asi 6–7.5× pomalejší.
Vyzkoušet Thunderbit pro extrakci webových dat Get Started Free


