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

Poslední aktualizace August 12, 2026
lxml v recenzi: XPath engine, který pořád přetlačí každý Python parser
AI shrnutí
Tato recenze lxml představuje knihovnu jako dlouhodobý Python binding nad libxml2 a libxslt s jednou zásadní výhodou, kterou novější parsery stále jen zřídka dorovnají: skutečným XPath enginem. Článek testuje pokrytí XPath, režimy přísnosti parseru, streamovací chování paměti, expresivitu CSS versus XPath i limity hloubky v libxml2. Ukazuje lxml jako rychlý, paměťově úsporný a mimořádně schopný nástroj pro XML i HTML úlohy, které potřebují osy, predikáty, funkce, streamování nebo robustní recovery režimy. Recenze také vysvětluje bezpečnostně motivované výchozí limity hloubky stromu a kdy hranici změní volba huge_tree.

Každých pár měsíců se objeví rychlejší HTML parser, vyhrnou se benchmarky a někdo hned prohlásí starou gardu za přežitou. Jenže pak potřebujete vybrat každý odstavec, který obsahuje určité slovo, nebo vytáhnout rodiče vybraného uzlu, a v tu chvíli si zase vzpomenete, proč máte lxml pořád otevřené v druhé záložce.

lxml je dvacet let starý binding na libxml2. Žádná senzace. Žádná horká novinka. Ale pro jednu konkrétní práci — cokoli, co opravdu potřebuje XPath — mu v běžném Pythonu nic reálně nesahá ani po kotníky. Tohle je praktická recenze toho, co umí, kde nenápadně vítězí a v jakých pár situacích vás jeho výchozí chování kousne, pokud o něm nevíte.

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

lxml je Pythoní rozhraní k C knihovnám libxml2 a libxslt. Je to parser a serializer, ne scraper a ne prohlížeč — vezme značkovací jazyk, převede ho na strom, který můžete prohledávat a upravovat, a pak strom vrátí zpět do bajtů. Nabízí API kompatibilní s ElementTree, plnohodnotný engine pro XPath 1.0, XSLT 1.0 i validaci schémat. Spravuje ho Stefan Behnel a jeho slogan zní „nejbohatší a zároveň nejsnáze použitelná knihovna pro zpracování XML a HTML v jazyce Python“.

Takhle vypadá jeho stav podle snapshotu z GitHubu a PyPI pořízeného 2026-07-14:

PoložkaHodnota
Repolxml/lxml
Stars3,043
Forks620
Open issues16
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 hype, je fér říct jednu věc: v téhle recenzi není žádné tajemství. lxml je dost starý na to, aby každé jeho chování bylo někde zdokumentované — v dokumentaci lxml, changelogu libxml2 nebo nějakém launchpad vlákně. Nenašel jsem žádný exkluzivní, nezdokumentovaný trik a nebudu si ho vymýšlet. Hodnota toho, co následuje, je v tom, že je to systematizované, změřené a uspořádané kolem lxml jako tématu — ne že by to byly novinky.

Testovací setup (a proč jsou časová čísla převzatá)

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

Testy schopností — chování XPath, obě parser 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 spočítané skriptem, ne ručně dopsané. Testy schopností jsou deterministické booleany a enumy, takže jeden běh je stabilní — zatížení stroje nezmění, jestli //a/@href vrátí atributový řetězec.

Časová a paměťová čísla nejsou z tohoto balíku. Jsou převzatá doslova z předchozího benchmark packu pro selectolax — stejný stroj, stejné virtuální prostředí, stejný build lxml a libxml2, benchmarky k 2026-07-13 — a já je tady nespouštěl znovu. Je to záměr. Spouštět timing benchmarky současně s dávkou capability skriptů by vytvářelo soupeření o CPU a tím znehodnotilo převzaté hodnoty, navíc by to byla duplicitní práce: lxml už byl v tom předchozím packu plně změřená kontrolní knihovna. Znovupoužití stejných benchmarků drží srovnání čistě „jablka s jablky“ místo toho, aby vzniklo druhé, jemně odlišné měření. Takže když níže uvidíte číslo v milisekundách, čtěte ho jako „stejné testovací sestavení, stav k 2026-07-13“, ne jako „dnes jsem to přeměřil“.

Výstupy mají štítek důvěry: single-observation pro deterministické capability testy, triple-run pro převzaté distribuce časů 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

Prohnal jsem lxml xpath() přes předem připravenou matici 37 položek — očekávaný výsledek pro každý případ byl zapsaný ve zdrojáku ještě před spuštěním testu, takže jsem nemohl nechtěně rozdávat body „na měkko“. Deset os, devět stylů predikátů, deset vestavěných funkcí, tři typy skalárního návratu a pět záměrných pastí s XPath 2.0 syntaxí, kterou engine v 1.0 verzi odmítnout má.

KategoriePokrytíVýsledek
Osychild / descendant / parent / ancestor / following-sibling / preceding-sibling / self / attribute / following / preceding10/10 splněno
Predikáty[1] / last() / position()<n / rovnost atributu / existence atributu / and / or / vnořené [.//a] / not()9/9 splněno
Funkcetext() / contains() / starts-with() / count() / string-length() / normalize-space() / concat() / substring() / name() / string()10/10 splněno
Typy návratuboolean / number skaláry3/3 splněno
Pastové případymatches() / 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 syntax XPath 2.0 a engine v libxml2 pro 1.0 je neumí „napůl“ — místo tichého vrácení špatné množiny uzlů vyhodí XPathEvalError a odmítne je. Je to tedy perfektní skóre po pokusu o rozbití, ne jen součet snadných bodů. Každé chování tady přesně odpovídá dokumentaci XPath v lxml, a o to jde.

Přiznám jednu chybu, protože právě tu verzi „37/37“, které se dá věřit. Můj první očekávaný výsledek pro //div[.//a[@href]] předpokládal dva zásahy; test vrátil jeden. Asi třicet vteřin jsem si myslel, že je špatně lxml, pak jsem zkontroloval fixture a zjistil, že druhý element byl <footer>, ne <div> — špatné bylo moje očekávání, ne engine. Opravil jsem očekávanou množinu a chybu nechal v komentáři ve zdrojáku. To je správné pořadí viny: nejdřív podezřívejte vlastní test, až potom dvacetiletou C knihovnu.

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

Obecné tvrzení „XPath je silnější“ si zaslouží konkrétní číslo, takže jsem ten rozdíl změřil. lxml dává jak .xpath(), tak .cssselect() (ta uvnitř převádí CSS na XPath). Vzal jsem deset selekčních cílů a zkontroloval, které z nich CSS vůbec umí vyjádřit.

XPath expresses seven of ten tasks CSS cannot express

CílXPathCSS (cssselect)
Filtrování podle textu (contains(text(),"bargain"))AnoBez textového predikátu
Výběr rodiče z dítěte (//b/parent::p)AnoBez parent selektoru
Návrat hodnoty atributu (//a/@href)AnoJen elementy
Návrat textového uzlu (//p/text())AnoBez textových uzlů
Ancestor osa (//td/ancestor::div)AnoBez pohybu směrem nahoru
Filtr rodiče podle počtu dětí (//ul[count(li)=4])AnoBez count predikátu
Filtr podle délky textu (string-length(text())>5)AnoBez length predikátu
nth-child / last-child / sousední sourozenecAnoAno (3 základní případy)

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, vrácení hodnoty atributu nebo čistého textového uzlu jako výsledku, predikáty založené na počtu — CSS to neumí vyjádřit. Jen tři (nth-child, last-child, adjacent sibling) fungují v obou. To je přesně odpověď na otázku „co vlastně získám, když sáhnu po lxml“. selectolax je čistě CSS-only a žádné xpath() nemá, takže těch sedm typů dotazů se tam buď rozpadne do vícekrokových Python smyček, nebo vůbec neprojde. Pokud vaše scraping logika na některém z nich stojí, máte tím rozhodnuto.

(A ano, harness mě tu chytil podruhé: u string-length(text())>5 jsem čekal 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 režimy přísnosti jsou důvod, proč u něj zůstat.

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

Většina parserů dává pro rozbitý vstup jedno chování. lxml dává tři a jsou dost předvídatelná na to, že jsem přes každý z nich pustil šest tříd chybného markup a předem si zapsal, jak se má který režim zachovat.

Chybný vstuplxml.etree (přísný)etree + recover=Truelxml.html (benevolentní)
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
Sirotčí & (Tom & Jerry)vyhodí chybuopravípřijme
Více kořenů <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 z sedmi odpovídalo očekávání. lxml.etree na všech šest chybových tříd vrací XMLSyntaxError. Když do stejného parseru přidáte recover=True, chyby potlačí a zrekonstruuje použitelný strom — a to je podceňovaná část — parser.error_log pak vypíše každou chybu, kterou spolkl. lxml.html přijme všechno bez řečí.

Klasifikátor, který rozhoduje „vyhodilo chybu vs. opravilo vs. přijalo“, je sám řízený délkou runtime error_log, ne napevno, takže dobře utvořený dokument zpracovaný s recover=True je správně označen jako „přijme“ (prázdný log), ne jako „opraví“. Moje první verze klasifikátoru označovala každý výsledek s recover=True jako „opraví“ a špatně označila čistý vstup; přečtení skutečného error_log to opravilo.

Co z toho plyne v praxi: přísnou validaci, kde má rozbitý feed spadnout nahlas, řešte přes lxml.etree. Špinavé HTML z reálného webu, které jen potřebujete dostat skrz, řešte přes lxml.html. A ten prostřední případ, který většina nástrojů neumí — „buď tolerantní, ale řekni mi přesně, co bylo rozbité, ať to můžu zalogovat“ — řeší recover=True a čtení error logu. selectolax má jen benevolentní režim a nic víc, žádný strict mode ani error log.

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

Tady jde o schopnost, ne o rychlostní knoflík. selectolax umí ingestovat jen celý string — žádné inkrementální 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ě volejte elem.clear() a mažte předchozí sourozence) drží paměť rovnou bez ohledu na velikost dokumentu.

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

Paměťové chování jsem měřil přímo — peak RSS přes ru_maxrss, každý subjekt v samostatném čerstvém procesu, na 300 000 elementech <record> o celkové velikosti zhruba 26,7 MB (26,744,801 bajtů).

RežimZměna peak 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; stejně těžké jako plné načtení
etree.parse (plné načtení, kotva)~386 MBznámý „těžký“ režim; potvrzuje řád velikosti

Omezený režim drží peak RSS delta zhruba na 1–2 MB oproti ~386 MB u plného načtení — rozdíl asi 0,3–0,4 % — a první událost s recordem přichází dřív, než se soubor vůbec dočte, takže jde opravdu o streamování, ne o předstírané streamování. Nejzajímavější je prostřední řádek. Spusťte stejnou smyčku iterparse, ale vynechte clear(), a paměť zase vyletí na ~386 MB, protože držíte reference na všechno. Vítězství je v clear(), ne v samotném iterparse. To, že kotva plného načtení leží mnohem výš než omezený režim, navíc potvrzuje, že RSS metr opravdu vidí rozdíl v řádu velikosti, a ne jen slepě čte čísla. (Tenhle paměťový test jsem v tomhle packu spouštěl já — je to měření footprintu, tedy něco jiného než převzatá timing čísla.)

Praktický překlad: vícgigabajtový XML export, který se nevejde do RAM, nemá žádnou cestu přes selectolax. Buď lxml stream parser, nebo jiný jazyk.

Namespaces: RSS, SVG a past s default namespace

Dvanáct namespace případů, pokrývajících RSS přes tři namespaces, SVG s default namespace a xlinkem a XML s default namespace. Všech dvanáct prošlo.

lxml vytáhne //dc:creator/text() z RSS feedu přesně jako ["Alice", "Bob"], umí vyřešit //atom:link/@href i //content:encoded napříč třemi samostatnými namespaces v jednom dokumentu, zvládne //s:rect a //s:use/@xlink:href ve druhém SVG namespace, rozloží názvy ve tvaru Clarkovy notace {uri}local pomocí QName a umí introspekci přes nsmap. Tohle je dokumentované a udržované chování, a je to úplně jiná dimenze, které se selectolax nedotýká, protože je čistě HTML5-only a libovolné XML namespaces nezpracovává.

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

Opravdu špinavé stránky: věrnost na 11 reálných scrapes

Syntetické testy jsou čisté; web ne. Znovu jsem použil jedenáct reálných zachycených stránek z fixture sady předchozího selectolax packu (stav k 2026-07-10, read-only) a prohnal je přes lxml.html, přičemž lxml byl zkoumaný subjekt.

FixtureVelikostOdkazylibxml2 opravené chybyStrict XML
news_bbc.html398 KB2554ok
docs_mdn_array.html243 KB5080vyhodilo chybu
wiki_scraping.html227 KB4600vyhodilo chybu
gov_whitehouse.html289 KB1540vyhodilo chybu
oldstyle_craigslist.html561 KB3510vyhodilo chybu
forum_reddit.html129 KB3180vyhodilo chybu
docs_python.html80 KB3412vyhodilo chybu
ecommerce_books.html51 KB940vyhodilo chybu
news_hackernews.html35 KB2290vyhodilo chybu
ecommerce_webscraper_allinone.html16 KB350vyhodilo chybu
spa_quotes_js.html6 KB50vyhodilo chybu

Všech jedenáct prošlo přes lxml.html a počty odkazů, nadpisů i obrázků seděly s převzatými počty z packu selectolaxu u všech jedenácti — křížová kontrola true. Právě tohle mi říká, že znovupoužití dat je skutečně „jablka s jablky“ a ne dvě různá měření s jednou nálepkou.

Vedlejší zjištění: strict XML parser vyhodil chybu u deseti z jedenácti stránek. Reálné webové stránky téměř nikdy nejsou dobře utvořené XML, a přesně proto existuje recovery režim v libxml2 HTML parseru. Jedinou výjimkou byl BBC News, vykreslený přes Next.js a dost dobře utvořený na to, aby přežil strict XML parsing. Ne všechno, co je označené jako „HTML“, potřebuje recovery režim.

Je tu ještě jeden detail v počítání, na který je snadné narazit. U docs_python.html se //a[@href] (existence atributu) napočítalo na 343, zatímco pack selectolaxu s if n.get("href") (pravdivost hodnoty) skončil na 341. Dva navíc jsou prázdné odkazy href="". Je to rozdíl v konvenci počítání — atribut existuje versus atribut je neprázdný — ne rozdíl v chování lxml, a čísla se srovnají, když sladíte predikát. Hodí se to vědět při scrapování: jestli se prázdné href počítají, je volba vašeho filtru, ne parseru.

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

V packu selectolax bylo zaznamenáno, že lxml na vnořeném <div> se zanořením 1 000 a 5 000 úrovní ořezává nejhlubší obsah a bylo to popsáno jako „lxml tiše ztrácí nejhlubší obsah“. Chtěl jsem zjistit mechanismus, tak jsem spustil default parser proti huge_tree=True.

lxml default depth guard around 253 levels and huge_tree to 2045

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

Default parser ořezává zhruba na 253 úrovních a cokoli hlubšího potichu zahodí. Není to chyba — je to obrana libxml2 proti DoS, zhruba 256úrovňový limit zanoření, který zabraňuje tomu, aby nepřátelský dokument rozmetal stack, a je to zdokumentované v launchpad vlákně 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 touto konfigurovatelnou hranicí je ještě druhý, tvrdší recursion limit v libxml2 a huge_tree ho neodemyká.

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

Read/Write DOM, serializace, kódování

lxml je plnohodnotný strom pro čtení i zápis, ne jen read-only extraktor, a editační rozhraní jsem ověřil bod po bodu. Všech osm DOM operací prošlo: SubElement, insert, remove, replace, strip_tags (odstraní tagy, ale nechá text), strip_elements (odstraní tagy i jejich text), drop_tree (exkluzivně v lxml.html) a dvojitý model text/tail, který nováčky často mate — 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ává prázdné 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 — "<p>café éè</p>".encode("latin-1") přes lxml.html.fromstring — vrátí café éè neporušené, bez náhradních znaků U+FFFD, bez vyhozených bajtů. To přímo reprodukuje jeho roli „čistého referenčního“ parseru v packu selectolax, kde stejný vstup u dalších dvou enginů potichu korumpoval data (Lexbor vracel náhradní znaky, Modest bajty zahazoval úplně). Charset detekce přes libxml2 je tu prostě stabilnější.

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

A nakonec životní cyklus uzlů. Spustil jsem tři scénáře se zastaralým handlem v izolovaných subprocessích (hard crash by se projevil jako nenulový exit): držení uzlu poté, co je jeho strom uvolněn garbage collectorem, čtení handle po drop_tree() a použití uzlu po remove(). Ani jeden neskončil segfaultem — lxml drží referenci uzlu na strom, aby zabránil use-after-free. Stejně čistý výsledek měl v tomhle testu i selectolax.

Rychlost a paměť (převzaté a poctivě označené)

Všechno v téhle sekci je převzaté z packu selectolax, stav k 2026-07-13. Tenhle pack nevygeneroval žádná vlastní timing čísla a raději to řeknu dvakrát, než abyste si mysleli, že jsem něco přeměřoval.

RozměrHodnota lxmlVýklad
Čistý parse p50 (10 MB)77.9 msasi o 33–34 % rychlejší než selectolax-Lexbor
Full parse + extract p50 (1 MB / 10 MB)14.18 ms / 172.9 msu malých velikostí zhruba na úrovni Lexboru
Propustnost CSS na 100k uzlech3,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
Studený start importu14.1 msasi 2,3× rychlejší než importy ve stylu parsel

Čistý parse i propustnost jsou velmi silné a lxml je ze šesti měřených parserů paměťově nejúspornější. U threading obrázku je ale nutné dodat poznámku. Převzatá data ukazují 4vláknové zrychlení wall-clock jen 1.21×, označené jako neprůkazné — jenže to je cesta se sdíleným default parserem. lxml FAQ výslovně říká, že GIL se při parsování uvolňuje jen tehdy, když každé vlákno používá vlastní parser (nebo kopii defaultního); sdílený parser přístup serializuje. Strukturálně jsem ověřil API pro správné použití (XMLParser.copy() existuje, get/set_default_parser existují, XPathEvaluator má interní lock), ale nezměřil jsem zrychlení s parserem pro každé vlákno — to by bylo nové timing měření a tenhle pack taková čísla negeneruje. Takže číslo 1.21x čtěte jako „naivní sdílená cesta“, ne jako strop threading výkonu lxml.

A jedna hvězdička ke všemu tomu: jde o čísla z jedné platformy, macOS arm64. Tvrzení, že čistý parse lxml poráží Lexbor, jde proti běžnému konsenzu, podle kterého bývá Lexborem poháněný parser nejrychlejší, takže si to skutečně zaslouží kontrolu na Linux x86_64, než to začne kdokoli považovat za uzavřenou věc.

Licence: nudné, ale skvělé vítězství

lxml vychází pod BSD-3-Clause a C knihovny, které balí — libxml2 a libxslt — jsou obě pod MIT licencí. Je to plně permisivní řetězec bez copyleftu, což je důležité v momentě, kdy to začnete distribuovat dál. Pro srovnání: wheel selectolaxu balí LGPL-2.1 Modest a Apache-2.0 Lexbor, takže lxml je čistší volba pro uzavřené produkty.

Je tu i praktická výhoda při instalaci: lxml publikuje předpřipravené wheels, které staticky linkují libxml2 a libxslt, takže pip install lxml obvykle nevyžaduje systémové libxml2 ani kompilátor — jiná zkušenost než stavět to ze zdrojáků.

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

Je čas jasně vymezit hranici, protože se tu snadno plete jedna kategorie s druhou. lxml je parsing knihovna. Dá vám strom a skvělý query engine, ale všechno kolem toho stromu je pořád na vás: stažení stránky, rendering JavaScriptu, průchod přes anti-bot ochranu, psaní a údržba XPath i strukturování výsledku. To je jiná vrstva než hostovaná extrakční služba a ty dvě nejsou ani tak soupeři, jako spíš sousedé.

Pro vývojáře, který nechce vlastní stack fetch-render-select-maintain, je právě nad touto vrstvou něco jako Thunderbit — a pro tohle publikum jde o API, MCP server a CLI, ne o browser extension. Thunderbit Open API nabízí POST /distill pro převod stránky do čistého Markdownu a POST /extract pro strukturovaná data podle JSON Schema, s přepínačem renderMode a dávkovými joby pro větší 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, který sedí na schema — což je vrstva nad parsováním, ne jeho náhrada.

Vyzkoušet Thunderbit pro extrakci webových dat

Zarámování je jednoduché. Sáhněte po lxml, když vlastníte celý pipeline a chcete chirurgickou kontrolu nad stromem pomocí XPath. Sáhněte po AI extrakčním API, když nechcete udržovat selektory ani rendering vůbec. V reálných systémech se často používají obě možnosti: lxml pro strukturované feedy, které máte pod kontrolou, a extrakční služba pro chaotické stránky, které pod kontrolou nemáte.

Co tahle recenze netestovala

Je to průběžná recenze, ne finální verdikt, takže tady je, co nepokrývá.

Všechna timing a memory čísla jsou převzatá, z jedné platformy (macOS arm64, Python 3.14) a nesou s sebou omezení toho původního packu — výsledek „lxml je rychlejší v čistém parsování“ jde proti konsenzu a chce kontrolu na Linux x86_64. Zrychlení při threading s parserem na vlákno nebylo testováno (vyžadovalo by nové timing měření). Paměť iterparse jsem měřil na 300k záznamech, ale ne na skutečném XML o velikosti v řádu gigabajtů, ne na iterparse u HTML versus XML a ne na několikahodinovém soak testu. XSLT 1.0 v lxml, jeho validace RelaxNG / XMLSchema / DTD i rozšíření EXSLT tady nejsou testované vůbec — je to velká schopnostní plocha, ale mimo jádro parsování a selekce. Druhý limit hloubky jsem viděl na 2 045, ale přesnou konstantu rekurze v libxml2 jsem neurčil. Testována byla jen stabilní verze 6.1.1, ne alfa 7.0.0. Windows, buildy ze zdrojáků ani free-threaded 3.14t build jsem netestoval. A přímo uvnitř XPath jsem pokryl vestavěné funkce, ale ne proměnné XPath, vlastní Python rozšiřující funkce ani opětovné použití předkompilovaného objektu etree.XPath.

Verdikt

lxml není ta rychlá nová hračka, a přesně proto ho doporučuji. Je to dvacet let starý binding na libxml2 s plnohodnotným XPath 1.0 enginem, kterému se žádná mainstreamová Python alternativa nevyrovná, se třemi předvídatelnými režimy přísnosti parsování a error logem uprostřed, s opravdovým streamovacím parserem pro dokumenty, které se nevejdou do paměti, se správným zacházením s více namespaces i kódováním a s plně permisivní licencí. Pár ostrých hran — limit hloubky kolem 253 úrovní a číslo pro shared-parser threading — je zdokumentovaných, nastavitelých a teď i vysvětlených.

Pokud vlastníte svůj scraping pipeline a spoléháte na XPath, lxml je pořád parser, po kterém sáhnete. Pokud nechcete udržovat selektory ani rendering, od toho je AI vrstva pro extrakci jako Thunderbit API, MCP a CLI — čisté rozdělení práce, ne soutěž. V každém případě berte ta čísla jako předběžná a přeměřte si timing na vlastní platformě, než je citujete v návrhovém dokumentu.

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

FAQ

Je lxml web scraper? Ne. lxml je parser a serializer — Python binding na libxml2/libxslt, který převádí markup do stromu, se kterým můžete pracovat. Nestahuje stránky, nerenderuje JavaScript ani neřeší anti-bot obranu; request vrstvu musíte dodat sami (přes requests, httpx, headless browser nebo scraping službu) a až pak předat bajty do lxml.

Kdy mám použít lxml místo BeautifulSoup nebo selectolax? Sáhněte po lxml, když potřebujete XPath. BeautifulSoup může jako backend parser používat lxml, ale nenabízí nativní XPath, a selectolax je čistě CSS-only a v úzkém záběru rychlejší. Pokud vaše selekční logika potřebuje filtrování podle textu, pohyb k rodičům nebo předkům, extrakci atributů či textových uzlů nebo predikáty podle počtu, je XPath engine v lxml jediná běžná Pythoní volba, která to umí přímo vyjádřit.

Proč lxml potichu odřezává hluboce zanořený obsah? Jeho default parser omezuje zanoření zhruba na 253 úrovní — je to obrana libxml2 proti DoS útokům na nepřátelské dokumenty, ne bug. Nastavte huge_tree=True (například lxml.html.HTMLParser(huge_tree=True)) a hloubky 300 i 1 000 obnoví celé. Počítejte ale s druhým, tvrdším limitem kolem 2 045 úrovní, který huge_tree neodstraní.

Uvolňuje lxml při multithreadovaném parsování GIL? Jen za správných podmínek. FAQ lxml říká, že GIL se během parsování uvolňuje tehdy, když každé vlákno používá vlastní parser nebo kopii defaultního parseru; sdílený parser místo toho serializuje přístup. Převzaté 4vláknové zrychlení 1.21× odpovídá naivní sdílené cestě, ne limitu při parseru na vlákno, který tady měřen nebyl.

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

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.
Obsah
Thunderbit · AI agent pro webová data

Extrahuj data z jakékoli stránky v 1 kliknutí

Důvěřuje mu více než 250 000 uživatelů
k dispozici bezplatný plán
Z webové stránky do tabulky
Popiš, co potřebuješ — AI agent Thunderbit to vyextrahuje a exportuje do Excelu, Google Sheets, Airtable nebo Notion. Začni zdarma.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week