lxml v recenzi: XPath engine, který stále přetlačí každý Python parser

Poslední aktualizace July 17, 2026
lxml v recenzi: XPath engine, který stále přetlačí každý Python parser
Shrnutí od AI
Tato recenze lxml popisuje knihovnu jako dlouhodobý Python binding nad libxml2 a libxslt s jednou velkou výhodou, kterou novější parsery stále málokdy doženou: skutečným XPath enginem. Článek testuje pokrytí XPath, režimy přísnosti parseru, streamingové chování paměti, rozdíly mezi CSS a XPath a hloubkové limity libxml2. Ukazuje lxml jako rychlý, paměťově úsporný a mimořádně schopný nástroj pro XML i HTML úlohy, které potřebují axes, predikáty, funkce, streaming nebo robustní recovery režimy. Recenze zároveň vysvětluje bezpečnostní výchozí limity hloubky stromu a kdy hranici mění volba huge_tree.

Každých pár měsíců se objeví rychlejší HTML parser, rozjedou se benchmarky a někdo prohlásí starou gardu za zastaralou. Pak ale potřebujete vybrat každý odstavec, který obsahuje určité slovo, nebo získat rodiče nalezeného uzlu, a hned si vzpomenete, proč máte lxml pořád otevřené v dalším panelu.

lxml je 20 let starý binding na libxml2. Není okázalý. Není nový. A pro jednu konkrétní práci — cokoli, co skutečně potřebuje XPath — v mainstreamovém Pythonu prakticky nemá konkurenci. Tohle je praktická recenze toho, co umí, kde tiše vítězí a na kterých dvou místech vás jeho výchozí nastavení může nepříjemně překvapit, pokud o nich nevíte.

lxml v jedné větě: Co to vlastně je

lxml je Python binding na C knihovny libxml2 a libxslt. Je to parser a serializátor, ne scraper ani prohlížeč — vezme značky, přemění je na strom, se kterým můžete pracovat, a pak ten strom zase vrátí do bajtů. Nabízí API kompatibilní s ElementTree, plnohodnotný engine XPath 1.0, XSLT 1.0 a validaci schémat. Projekt spravuje Stefan Behnel a prezentuje ho jako "nejbohatší na funkce a nejjednodušší knihovnu pro zpracování XML a HTML v jazyce Python".

Tady je jeho pozice podle snapshotu z GitHubu a PyPI pořízeného 2026-07-14:

PoleHodnota
Repolxml/lxml
Star3 043
Forky620
Otevřené issue16
LicenceBSD-3-Clause
Vznik2011-02-11
Poslední push2026-07-02
Stabilní verze na PyPI6.1.1 (2026-05-18)
Přibalený enginelibxml2 2.14.6 + libxslt 1.1.43

Než mě někdo obviní z přehánění, jedna věc na úvod: v téhle recenzi nejsou žádná tajemství. lxml je dost starý na to, aby bylo všechno níže někde zdokumentované — v dokumentaci lxml, changelogu libxml2 nebo v nějakém vlákně na Launchpadu. Nenašel jsem žádný neveřejný ani nezdokumentovaný trik a nehodlám si ho vymýšlet. Hodnota toho, co následuje, je v tom, že je to systematizované, kvantifikované a uspořádané kolem lxml jako hlavního tématu — ne v tom, že je to zrovna novinka.

Nastavení testů (a proč jsou časová čísla vypůjčená)

Do téhle recenze vstupují dvě kategorie dat a pocházejí ze dvou různých míst, takže rovnou řeknu, co je co.

Testy schopností — chování XPath, dvě parserová API, namespace, kódování, životní cyklus uzlů — jsem spustil znovu na jednom stroji: macOS arm64, Python 3.14.2, lxml 6.1.1, libxml2 2.14.6. Každé číslo v souborech artifacts/raw/*.json je vypočtené skriptem, ne ručně zapsané. Testy schopností jsou deterministické boolean hodnoty a enumy, takže jeden běh stačí — zatížení stroje nemění, jestli //a/@href vrátí atribut jako řetězec.

Časová a paměťová čísla nejsou z tohoto balíčku. Jsou převzatá doslova z dřívějšího benchmarkového balíčku pro selectolax — stejný stroj, stejné virtuální prostředí, stejný build lxml a libxml2, benchmarky k datu 2026-07-13 — a já jsem je tady znovu nespouštěl. Je to záměr. Spouštět časové benchmarky současně s dávkou capability skriptů by zavedlo CPU contention, které by znečistilo převzatá čísla, a navíc by šlo o duplicitní práci: lxml už bylo v tom balíčku plně změřené jako kontrolní knihovna. Použití stejných benchmarků udržuje srovnání „jablka s jablky“ místo zavedení druhého, jemně odlišného měření. Takže když níže uvidíte milisekundové číslo, čtěte ho jako „stejné testovací prostředí, k datu 2026-07-13“, ne jako „měřil jsem to dnes znovu“.

Zjištění mají štítek důvěry: single-observation pro deterministické capability testy, triple-run pro převzaté distribuční časy a hypothesis tam, kde navrhuji mechanismus, který jsem neizoloval.

XPath: Jediná věc, kterou selectolax a BeautifulSoup prostě nemají

Tohle je hlavní pointa, takže začnu tady.

lxml XPath coverage moat with axes predicates and functions

Prošel jsem xpath() u lxml přes 37 položek v předem zaregistrované matici — očekávaný výsledek pro každý případ byl zapsaný do zdrojáku ještě před spuštěním testu, takže jsem nemohl nechtěně „hodnotit s nadhodnocením“. Deset axes, devět stylů predikátů, deset vestavěných funkcí, tři skalární návratové typy a pět záměrných pastí využívajících syntaxi XPath 2.0, kterou by engine 1.0 v lxml měl odmítnout.

KategoriePokrytíVýsledek
Axeschild / descendant / parent / ancestor / following-sibling / preceding-sibling / self / attribute / following / preceding10/10 prošlo
Predikáty[1] / last() / position()<n / rovnost atributu / existence atributu / and / or / vnořené [.//a] / not()9/9 prošlo
Funkcetext() / contains() / starts-with() / count() / string-length() / normalize-space() / concat() / substring() / name() / string()10/10 prošlo
Návratové typyboolean / number scalars3/3 prošlo
Pastimatches() / sekvence / if-then-else / except / syntax error5/5 správně odmítnuto

Skóre je 37/37 a právě sloupec s pastmi je ten důležitý. matches(), sekvenční výrazy, if/then/else i except jsou syntaxe XPath 2.0 a engine 1.0 v libxml2 je nepolovičně nepodporuje — místo tichého vrácení špatné sady uzlů vyhodí XPathEvalError a odmítne dotaz. To znamená perfektní výsledek po pokusu o rozbití, ne perfektní skóre poskládané z jednoduchých případů. Každé chování tady přesně odpovídá tomu, co popisuje dokumentace XPath v lxml, a o to jde.

Přiznám jednu věc, kterou harness vyhodnotil špatně, protože je to jediná verze „37/37“, které můžete věřit. Moje první očekávání pro //div[.//a[@href]] předpokládalo dva zásahy; běh ale vrátil jeden. Asi třicet sekund jsem si myslel, že lxml dělá chybu, pak jsem zkontroloval fixture a zjistil, že druhý prvek byl <footer>, ne <div> — špatně jsem měl očekávání, ne engine. Opravil jsem očekávanou množinu a chybu nechal jen v komentáři ve zdrojáku. To je správné pořadí podezřelých: nejdřív obviňte vlastní test, až potom 20 let starou C knihovnu.

XPath vs CSS: Co v CSS doslova nejde vyjádřit

Obecné tvrzení „XPath je silnější“ si zaslouží konkrétní číslo, tak jsem ten rozdíl změřil. lxml nabízí jak .xpath(), tak .cssselect() (to druhé převádí CSS na XPath pod pokličkou). Vzal jsem deset cílů výběru a zkontroloval, které z nich CSS skutečně umí vyjádřit.

XPath expresses seven of ten tasks CSS cannot express

CílXPathCSS (cssselect)
Filtrování podle textu (contains(text(),"bargain"))AnoŽádný textový predikát
Výběr rodiče z potomka (//b/parent::p)AnoŽádný selector rodiče
Vrácení hodnoty atributu (//a/@href)AnoJen elementy
Vrácení textového uzlu (//p/text())AnoŽádné textové uzly
Axis ancestor (//td/ancestor::div)AnoŽádná navigace směrem nahoru
Filtrování rodiče podle počtu dětí (//ul[count(li)=4])AnoŽádný count predikát
Filtrování podle délky textu (string-length(text())>5)AnoŽádný predikát délky
nth-child / last-child / sousední siblingAnoAno (3 základní)

Sedm z deseti cílů nemá v CSS vůbec žádný ekvivalent. Filtrování podle obsahu textu, pohyb nahoru k rodičům a předkům, získání hodnoty atributu nebo čistého textového uzlu jako výsledku, predikáty založené na počtu — CSS to neumí. Fungují jen tři (nth-child, last-child, sousední sibling) a ty navíc v obou přístupech. To je kvantifikovaná odpověď na otázku „co vlastně získám, když sáhnu po lxml“. selectolax je čistě CSS-only a nemá vůbec žádnou metodu xpath(), takže těchto sedm typů dotazů se tam buď musí přepisovat do vícekrokových Python smyček, nebo vůbec neprovede. Pokud vaše scraping logika na některém z nich stojí, je rozhodnuto.

(A ano, harness mě nachytal i podruhé: u string-length(text())>5 jsem předpověděl prázdnou množinu, ale dva šestiznakové řetězce prošly. Opravil jsem očekávání, ne nástroj.)

Tři režimy přísnosti: etree vs recover vs lxml.html

XPath je důvod, proč lxml zvolit. Tři stupně přísnosti jsou důvod, proč u něj i zůstat.

lxml strictness gears: etree, recover, and lxml.html

Většina parserů vám dá jedno chování pro rozbitý vstup. lxml dává tři a jsou natolik předvídatelná, že jsem přes ně poslal šest tříd nevalidního značkování a předem zaregistroval, jak se má každá cesta chovat.

Rozbitý vstuplxml.etree (striktní)etree + recover=Truelxml.html (shovívavý)
Neuzavřený tag <root><a>x</root>vyhodí chybuopravípřijme
Špatné zanoření <b><i></b></i>vyhodí chybuopravípřijme
Nedefinovaná entita &nbsp;vyhodí chybuopravípřijme
Sám o sobě ampersand & (Tom & Jerry)vyhodí chybuopravípřijme
Více rootů <a>1</a><b>2</b>vyhodí chybuopravípřijme
Správně utvořené XMLpřijmepřijme (0 chyb)přijme
Booleovský atribut <input disabled>vyhodí chybuopravípřijme

Sedm ze sedmi odpovědí sedělo s předem zaregistrovaným očekáváním. lxml.etree vyhazuje XMLSyntaxError u všech šesti rozbitých tříd. Když k parseru přidáte recover=True, chyby pohltí a znovu poskládá použitelný strom — a to je podceňovaná část — parser.error_log pak vypíše každou chybu, kterou potlačil. lxml.html přijme všechno bez řečí.

Klasifikátor, který rozhoduje mezi „vyhodí chybu“, „opraví“ a „přijme“, je sám řízen délkou error_log za běhu, ne natvrdo, a právě proto je dobře utvořený dokument spuštěný s recover=True správně označen jako „přijme“ (prázdný log), ne jako „opraví“. Moje první verze toho klasifikátoru označovala každý výsledek recover=True jako „opraví“ a špatně tagovala čistý vstup; čtení skutečného error_log to napravilo.

Co to znamená v praxi: pokud potřebujete přísnou validaci a rozbitý feed má selhat nahlas, použijte lxml.etree. Pro špinavé HTML z reálného webu, které prostě potřebujete projít, použijte lxml.html. A pro prostřední případ, který většina nástrojů neumí — „buď shovívavý, ale přesně mi řekni, co bylo rozbité, ať to můžu zalogovat“ — použijte recover=True a čtěte error log. selectolax má jen shovívavý režim a nic víc, žádný striktní mód ani error log.

iterparse: Streaming režim, který selectolax vůbec nemá

Tady jde o schopnost, ne o rychlostní metriku. selectolax umí jen načíst celý řetězec — žádné průběžné rozhraní nemá. lxml iterparse vrací elementy ve chvíli, kdy se uzavřou, a v kombinaci s klasickým vzorem fast_iter (průběžně volat elem.clear() a mazat předchozí sourozence) drží paměť plochou bez ohledu na velikost dokumentu.

lxml iterparse streams 300K records with about 1-2 MB RSS

Paměťové chování jsem změřil přímo — peak RSS přes ru_maxrss, každý subjekt ve vlastním čerstvém procesu, nad 300 000 elementy <record> o celkové velikosti asi 15 MB.

RežimŠpičkový nárůst RSSPoznámky
iterparse + clear (fast_iter)~1–2 MBuvolňuje průběžně; ploché bez ohledu na počet
iterparse bez clear~386 MBdrží reference; paměťově stejně těžké jako plné načtení
etree.parse (plné načtení, anchor)~386 MBzáměrně těžké; potvrzuje, že měřák vidí řád velikosti

Omezený režim drží špičkový RSS nárůst zhruba na 1–2 MB oproti ~386 MB při plném načtení — rozdíl o velikosti 0,3–0,4 % — a první event s rekordem přijde ještě dřív, než se soubor dočte celý, takže jde o skutečné streamování, ne jen předstírané. Instruktivní je prostřední řádek. Spusťte stejnou smyčku iterparse, ale vynechte clear(), a paměť vyroste zpátky na ~386 MB, protože držíte reference ke všemu. Výhoda je v clear(), ne v samotném iterparse. Anchor pro plné načtení výrazně výš než omezený režim navíc potvrzuje, že RSS měřák skutečně vidí rozdíl velikosti a nečte naslepo. (Tenhle paměťový test jsem v tomhle balíčku opravdu spustil — je to měření footprintu, odlišné od převzatých časových čísel.)

Reálný důsledek: vícgigabajtový XML export, který se nevejde do RAM, nemá v selectolax žádnou cestu. Je to buď streaming parser lxml, nebo jiný jazyk.

Namespace: RSS, SVG a past s defaultním namespace

Dvanáct případů s namespace, pokrývajících RSS ve třech namespaces, SVG s defaultním namespace a xlink a XML s defaultním namespace. Všech dvanáct prošlo.

lxml vytáhne //dc:creator/text() z RSS feedu přesně jako ["Alice", "Bob"], rozpozná //atom:link/@href a //content:encoded napříč třemi oddělenými namespace v jednom dokumentu, pracuje s //s:rect a //s:use/@xlink:href ve druhém namespace SVG, rozpadá Clarkovu notaci {uri}local pomocí QName a introspektuje přes nsmap. To je dokumentované a udržované chování a je to úplně jiná dimenze, do které selectolax vůbec nesahá, protože selectolax je jen HTML5 a nepracuje s libovolnými XML namespace.

Je tu ale jedna zdokumentovaná past, kterou je dobré si zapamatovat. XPath vůbec nezná koncept defaultního namespace. Když namíříte //book na dokument, který deklaruje xmlns="urn:...", dostanete nulu zásahů — prázdný prefix je pro XPath nedefinovaný, jak popisuje dokumentace lxml. Musíte buď svázat umělý prefix (//c:book s namespaces={"c": "urn:..."}, které našlo všechny tři), nebo sáhnout po //*[local-name()='book'] (což také našlo tři). Není to bug — je to XPath spec, věrně implementovaná. Jen překvapí každého právě jednou.

Skutečně špinavé stránky: přesnost na 11 reálných scrapech

Syntetické testy jsou čisté; web ne. Znovu jsem použil jedenáct reálně zachycených stránek z fixture sady selectolax balíčku (k datu 2026-07-10, read-only) a pustil přes ně lxml.html jako hlavní předmět testu.

FixtureVelikostOdkazylibxml2 recovered errorsStrict XML
news_bbc.html398 KB2554ok
docs_mdn_array.html243 KB5080vyhodí chybu
wiki_scraping.html227 KB4600vyhodí chybu
gov_whitehouse.html289 KB1540vyhodí chybu
oldstyle_craigslist.html561 KB3510vyhodí chybu
forum_reddit.html129 KB3180vyhodí chybu
docs_python.html80 KB3412vyhodí chybu
ecommerce_books.html51 KB940vyhodí chybu
news_hackernews.html35 KB2290vyhodí chybu
ecommerce_webscraper_allinone.html16 KB350vyhodí chybu
spa_quotes_js.html6 KB50vyhodí chybu

Všech jedenáct se načetlo přes lxml.html a počty odkazů, nadpisů i obrázků odpovídaly převzatým počtům lxml z balíčku selectolax u všech jedenácti — cross-check true. Právě to potvrzuje, že převzetí je opravdu jablka s jablky, ne dvě různá měření se stejným popiskem.

Vedlejší zjištění: striktní XML parser vyhodil chybu u deseti z jedenácti stránek. Skutečné webové stránky jsou zdaleka ne většinou dobře utvořené XML, a přesně proto existuje recovery mód libxml2, který je dokáže strávit. Jedinou výjimkou byl BBC News, renderovaný přes Next.js a dost dobře utvořený na to, aby prošel striktním XML parsingem. Ne všechno, co je označené jako „HTML“, potřebuje recovery cestu.

Jedna drobnost ke sčítání, na kterou se dá snadno nachytat. Na docs_python.html počítalo //a[@href] (existence atributu) 343, zatímco balíček selectolax s if n.get("href") (pravdivá hodnota) spočítal 341. Ty dva navíc jsou prázdné odkazy href="". To je rozdíl v konvenci počítání — atribut existuje versus atribut není prázdný — ne rozdíl v chování lxml, a počty se srovnají, jakmile sjednotíte predikát. Je dobré to vědět při scrapování: zda se prázdné href počítají, je vaše volba filtru, ne parseru.

Limit hloubky, který vypadá jako bug (ale není)

Balíček selectolax zaznamenal, že lxml na 1 000 a 5 000 úrovních vnořených <div> ztrácí nejhlubší obsah, a popsal to jako „lxml tiše ztrácí nejhlubší obsah“. Chtěl jsem znát mechanismus, tak jsem pustil výchozí parser proti huge_tree=True.

lxml default depth guard around 253 levels and huge_tree to 2045

Požadovaná hloubkaVýchozí parser dosáhnehuge_tree=True dosáhne
300253 (zbytek zahodí)299 (obnoveno)
1000253 (zbytek zahodí)999 (obnoveno)
5000253 (zbytek zahodí)2045 (stále zahazuje)

Výchozí parser se zastaví zhruba na 253 úrovních a cokoli hlubšího tiše odřízne. To není bug — je to DoS obrana libxml2, zhruba 256úrovňový limit vnoření, který brání škodlivému dokumentu v přetečení zásobníku, a je zdokumentovaný v launchpad threadu lxml k XML_PARSE_HUGE. Když nastavíte huge_tree=True, hloubky 300 a 1 000 se vrátí celé. Hloubka 5 000 ale i s huge_tree dosáhne jen 2 045 — nad konfigurovatelným limitem je ještě druhý, tvrdší recursní strop libxml2, a huge_tree ho neodstraňuje.

Praktický závěr je jasný: když parsujete hluboké značkování ze zdroje, kterému věříte, použijte lxml.html.HTMLParser(huge_tree=True). Co tahle recenze přidává nad převzaté zjištění, je mechanismus (bezpečnostní limit, ne poškození dat), oprava (huge_tree) a fakt, že existuje ještě druhý strop, na který oprava nestačí.

DOM pro čtení i zápis, serializace, kódování

lxml je plnohodnotný strom pro čtení i zápis, ne jen read-only extraktor, a editovací rozhraní jsem ověřil případ po případu. Všech osm DOM operací prošlo: SubElement, insert, remove, replace, strip_tags (odstraní tagy, ale zachová text), strip_elements (odstraní tagy i jejich text), drop_tree (exkluzivní pro lxml.html) a model dvojího slotu text/tail, který mate nováčky — v <p>head<b>bold</b>tail</p> je p.text "head", b.text je "bold" a b.tail je "tail".

Serializace prošla pět z pěti: tostring v XML i HTML režimu (HTML správně nenechá void elementy samo-uzavřené), pretty_print, C14N canonicalizace (method="c14n", další exkluzivní funkce lxml) a čistý round-trip.

Kódování je místo, kde se lxml tiše odlišuje. Když mu dáte bajty mimo UTF-8 — například "<p>café éè</p>".encode("latin-1") přes lxml.html.fromstring — obnoví café éè beze změny, bez náhradních znaků U+FFFD a bez ztracených bajtů. To přímo odpovídá jeho roli „čisté reference“ v balíčku selectolax, kde stejný vstup u dalších dvou enginů potichu korumpoval data (Lexbor generoval náhradní znaky, Modest bajty odhazoval). lxml podložené detekcí charsetů v libxml2 je tu prostě stabilnější.

Druhá strana je přísnost v tom, jak deklarujete kódování. encoding="latin-1" v XML deklaraci vyhodí XMLSyntaxError: Unsupported encoding: latin-1, zatímco IANA-kánonické encoding="ISO-8859-1" projde a vrátí café. libxml2 přijímá jen kanonické názvy kódování, ne jejich aliasy — detail zdokumentovaný už v launchpad #613302. Otravné, pokud o tom nevíte, triviální, jakmile to víte.

A nakonec životní cyklus uzlů. Otestoval jsem tři scénáře s „mrtvými“ handly v izolovaných subprocessích (tvrdý pád by se projevil jako nenulový exit): držení uzlu po garbage collection stromu, čtení handle po drop_tree() a použití uzlu po remove(). Ani v jednom případě žádný segfault — lxml drží referenci uzlu na strom při životě, aby zabránil use-after-free. Stejné čisté vysvědčení dostal v tomto testu i selectolax.

Rychlost a paměť (vypůjčené, a poctivě přiznané)

Všechno v téhle části je převzaté z balíčku selectolax, k datu 2026-07-13. Tato recenze sama nevygenerovala žádná časová čísla a raději to řeknu dvakrát, než abyste si mysleli, že jsem něco znovu měřil.

DimenzeHodnota lxmlVýklad
Čistý parse p50 (10 MB)77.9 mszhruba o 33–34 % rychlejší než selectolax-Lexbor
Plný parse + extrakce p50 (1 MB / 10 MB)14.18 ms / 172.9 msu malých velikostí přibližně na úrovni Lexbor
Propustnost 100k uzlů CSS3 002 646 uzlů/snejrychlejší třída ze tří C enginů
RSS delta na 10 MB128.9 MBnejúspornější ze šesti parserů, asi 1,7× úspornější než BeautifulSoup
Chladný start importu14.1 msasi 2,3× rychlejší než imports ve stylu parsel

Čísla pro čistý parse a propustnost jsou silná a lxml je z měřených šesti parserů nejúspornější na paměť. Vláknový obraz ale potřebuje poznámku. Převzatá data ukazují 4vláknové zrychlení wall-clock jen 1,21×, označené jako neprůkazné — ale to je cesta se sdíleným defaultním parserem. lxml FAQ výslovně říká, že GIL se při parsování uvolní jen tehdy, když každý thread používá vlastní parser (nebo kopii defaultního); sdílený parser přístup serializuje. Strukturálně jsem ověřil API povrch pro správný postup (XMLParser.copy() existuje, get/set_default_parser existují, XPathEvaluator má interní lock), ale nezměřil jsem zrychlení s parserem na vlákno — to by bylo nové časové měření a tento balíček ho negeneruje. Takže „1,21ד čtěte jako „naivní sdílená cesta“, ne jako strop threading schopností lxml.

A jedna hvězdička ke všemu: jsou to čísla z jedné platformy, macOS arm64. Tvrzení, že lxml v čistém parsování poráží Lexbor, jde proti běžnému konsenzu, že Lexbor-backed parser bývá nejrychlejší, takže si opravdu zaslouží ověření na Linuxu x86_64, než to někdo začne považovat za uzavřenou věc.

Licence: Nudné, ale výherní

lxml je vydáno pod BSD-3-Clause a C knihovny, které přibaluje — libxml2 a libxslt — jsou obě MIT. Je to plně permisivní řetězec bez copyleftu, což je důležité v okamžiku, kdy něco redistribuujete. Pro srovnání, wheel selectolax přibaluje LGPL-2.1 Modest a Apache-2.0 Lexbor, takže lxml má čistší příběh pro nasazení v uzavřeném produktu.

Je tu i praktická výhoda při instalaci: lxml publikuje předpřipravené wheely, které staticky linkují libxml2 a libxslt, takže pip install lxml obvykle nevyžaduje systémové libxml2 ani kompilátor na vašem stroji — jiný zážitek než build ze zdrojáků.

Kam lxml patří — a kde přebírá roli AI vrstva pro extrakci

Je čas jasně vymezit hranici, protože je snadné si splést kategorie. lxml je parser. Dá vám strom a vynikající query engine a všechno okolo toho stromu je pořád vaše práce: stáhnout stránku, vyrenderovat JavaScript, projít anti-bot ochranou, napsat a udržovat XPath a strukturovat výsledek. To je jiná vrstva než hostovaná služba pro extrakci, a ty dvě spolu nejsou spíš konkurenti než sousedé.

Pro vývojáře, který nechce vlastnit celý stack fetch-render-select-maintain, je právě v té vyšší vrstvě něco jako Thunderbit — a pro tohle publikum je důležité hlavně API, MCP server a CLI, ne browser extension. Thunderbit Open API nabízí POST /distill, které převede stránku na čistý Markdown, a POST /extract, které vytáhne strukturovaná data podle JSON Schema, s přepínačem renderMode a batch úlohami pro vyšší objem. Stejný engine je dostupný i jako MCP server (thunderbit_suggest_fields, thunderbit_distill, thunderbit_extract) pro agenty a coding asistenty a také jako CLI, které můžete spustit přímo z terminálu přes npx @thunderbit/thunderbit-cli. Zvládá JS rendering, anti-bot i CAPTCHA hned po instalaci a vrací JSON odpovídající schématu — tedy vrstvu nad parsováním, ne náhradu parsování.

Vyzkoušet Thunderbit pro extrakci webových dat

Zarámování je jednoduchá volba. Sáhněte po lxml, když vlastníte pipeline a chcete chirurgickou kontrolu XPath nad stromem, kterému rozumíte. Sáhněte po AI extrakční API, když nechcete udržovat selektory a rendering vůbec. Spousta skutečných systémů používá obojí — lxml pro strukturované feedy, které mají pod kontrolou, a extrakční službu pro nepořádné dlouhé stránky, které pod kontrolou nemají.

Co tahle recenze netestovala

Tohle je průběžná recenze, ne finální vysvědčení, takže tady je to, co nepokrývá.

Všechna časová a paměťová čísla jsou převzatá, z jedné platformy (macOS arm64, Python 3.14) a nesou omezení toho balíčku — výsledek „lxml je rychlejší v čistém parsování“ jde proti očekávání a zaslouží si kontrolu na Linuxu x86_64. Zrychlení s parserem na vlákno nebylo testované (vyžadovalo by nová měření). Měřil jsem paměť iterparse na 300k rekordů, ale ne na skutečném XML o velikosti gigabajtů, ne iterparse na HTML versus XML a ne dlouhodobý soak test. XSLT 1.0, RelaxNG / XMLSchema / DTD validace a EXSLT rozšíření lxml tu nejsou testované vůbec — je to velká schopnost, ale mimo jádro parsování a výběru. Zaznamenal jsem druhý hloubkový strop na 2 045, ale neurčil přesnou konstantu rekurze libxml2. Testovala se jen stabilní verze 6.1.1, ne alfa 7.0.0. Windows, build ze zdrojů a free-threaded 3.14t build zůstaly netestované. A v rámci samotného XPath jsem pokryl vestavěné funkce, ale ne XPath proměnné, vlastní Python extension funkce ani opakované použití předkompilovaných objektů etree.XPath.

Verdikt

lxml není ta rychlá nová věc, a přesně proto je doporučení kladné. Je to dvacet let starý binding na libxml2 s plným enginem XPath 1.0, který nemá v mainstreamovém Pythonu rovnocennou alternativu, se třemi předvídatelnými režimy přísnosti parsování a error logem uprostřed, se skutečným streaming parserem pro dokumenty, které se nevejdou do paměti, se správnou podporou více namespace i kódování a s plně permisivní licencí. Pár ostrých hran — limit hloubky kolem 253 úrovní a číslo pro threading se sdíleným parserem — je zdokumentovaných, konfigurovatelných a teď i vysvětlených.

Když vlastníte scraping pipeline a spoléháte na XPath, lxml je pořád parser, po kterém sáhnete. Když nechcete udržovat selektory a rendering, od toho je AI vrstva pro extrakci jako Thunderbit API, MCP a CLI — čisté rozdělení práce, ne soupeření. V obou případech berte tato čísla jako průběžná a před citováním do návrhového dokumentu si je na vlastní platformě znovu ověřte.

Vyzkoušet Thunderbit pro extrakci webových dat Get Started Free

Často kladené otázky

Je lxml web scraper? Ne. lxml je parser a serializátor — Python binding na libxml2/libxslt, který přemění značky na strom, se kterým lze editovat a dotazovat. Nestahuje stránky, nevyrenderuje JavaScript ani neřeší anti-bot ochranu; síťovou vrstvu dodáte vy (přes requests, httpx, headless browser nebo scraping službu) a bajty pak předáte do lxml.

Kdy mám použít lxml místo BeautifulSoup nebo selectolax? Sáhněte po lxml, když potřebujete XPath. BeautifulSoup sice může používat lxml jako backend parser, ale nenabízí nativní XPath, a selectolax je čistě CSS-only a ve své úzké nice rychlejší. Pokud vaše selekce potřebují filtrování podle textu, navigaci na rodiče nebo předky, extrakci atributů nebo textových uzlů nebo predikáty podle počtu, je XPath engine v lxml jediná mainstreamová možnost v Pythonu, která to umí přímo vyjádřit.

Proč lxml tiše zahazuje hluboce zanořený obsah? Výchozí parser omezuje zanoření na zhruba 253 úrovní — je to obrana libxml2 proti škodlivým dokumentům, ne bug. Nastavte huge_tree=True (například lxml.html.HTMLParser(huge_tree=True)) a hloubky 300 i 1 000 obnoví celé. Pozor ale na druhý, tvrdší strop kolem 2 045 úrovní, který huge_tree neodstraní.

Uvolňuje lxml GIL při vícevláknovém parsování? Jen za správných podmínek. FAQ lxml říká, že GIL se při parsování uvolní, když každý thread používá vlastní parser nebo kopii defaultního parseru; sdílený parser místo toho přístup serializuje. Převzaté 4vláknové zrychlení 1,21× odpovídá naivní cestě se sdíleným parserem, ne stropu pro parser na vlákno, který tu nebyl měřen.

Je lxml v roce 2026 stále aktivně udržované? Ano. Stabilní verze 6.1.1 vyšla 2026-05-18, repozitář měl poslední push 2026-07-02 a ve vývoji je alfa 7.0.0. S přibližně 3 000 hvězdičkami na GitHubu a aktivně spravovaným libxml2 pod kapotou jde pořád o aktuální a dobře podporovanou knihovnu, ne o mrtvé dědictví.

Ke
Ke
CTO ve Thunderbit | Senior Data Scientist a expert na ML S téměř desetiletou zkušeností v oblasti strojového učení a datové vědy je Ke Shen absolventem Kolumbijské univerzity a bývalým Senior Data Scientist ve Walmart Labs. Díky hlubokým odborným znalostem v Pythonu, R, Javě a statistice, uznávaným i mezi kolegy, sdílí ověřené poznatky o tom, jak převést složité AI algoritmy od teorie až k produkční architektuře.
Topics
Web Scraping ToolsAI Web Scraper

Vyzkoušej Thunderbit

Získej leady i další data jen na 2 kliknutí. Pohání AI.

Získat Thunderbit Je to zdarma
Vytěž data pomocí AI
Snadno přenes data do Google Sheets, Airtable nebo Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week