lxml की समीक्षा: वह XPath इंजन जो आज भी हर Python पार्सर से आगे निकल जाता है

अंतिम अपडेट:July 17, 2026
lxml की समीक्षा: वह XPath इंजन जो आज भी हर Python पार्सर से आगे निकल जाता है
AI सारांश
यह lxml समीक्षा इस library को libxml2 और libxslt के लंबे समय से चले आ रहे Python binding के रूप में प्रस्तुत करती है, जिसकी सबसे बड़ी ताकत आज भी वही है जो नए parsers अक्सर नहीं दे पाते: एक असली XPath engine। लेख XPath coverage, parser strictness modes, streaming memory behavior, CSS बनाम XPath की expressiveness, और libxml2 depth limits की जांच करता है। इसमें lxml को XML और HTML workloads के लिए तेज़, memory-efficient, और बेहद सक्षम दिखाया गया है, खासकर तब जब axes, predicates, functions, streaming, या robust recovery modes चाहिए हों। समीक्षा security-minded tree-depth defaults और huge_tree से बदलने वाली सीमा भी समझाती है।

हर कुछ महीनों में कोई न कोई तेज़ HTML parser सामने आ जाता है, benchmark घूमते रहते हैं, और कोई न कोई पुराने विकल्पों को बेकार घोषित कर देता है। फिर आपको किसी ऐसे शब्द वाले हर paragraph को चुनना होता है जो contains करता हो, या matched node का parent निकालना होता है — और याद आता है कि lxml अभी भी आपके दूसरे tab में क्यों खुला है।

lxml, libxml2 का 20 साल पुराना binding है। यह न तो रोमांचक है, न नया। और एक खास काम के लिए — यानी जहाँ असली XPath चाहिए — mainstream Python में इसका कोई सीधा मुकाबला नहीं है। यह एक hands-on समीक्षा है: यह क्या करता है, कहाँ चुपचाप जीतता है, और किन दो-चार default सेटिंग्स की वजह से अगर आपको पता न हो तो दिक्कत हो सकती है।

lxml एक नज़र में: यह असल में है क्या

lxml, C libraries libxml2 और libxslt के लिए Python binding है। यह parser और serializer है, scraper या browser नहीं — यह markup को एक tree में बदल देता है जिसे आप query और edit कर सकते हैं, और फिर उस tree को bytes में वापस बदल देता है। इसमें ElementTree-compatible API, पूरा XPath 1.0 engine, XSLT 1.0, और schema validation मिलती है। Stefan Behnel इसे "the most feature-rich and easy-to-use library for processing XML and HTML in the Python language" के tagline के साथ maintain करते हैं।

नीचे GitHub और PyPI snapshot के आधार पर इसकी स्थिति है, जो 2026-07-14 को लिया गया था:

फ़ील्डमान
Repolxml/lxml
Stars3,043
Forks620
Open issues16
LicenseBSD-3-Clause
Created2011-02-11
Last push2026-07-02
PyPI stable6.1.1 (2026-05-18)
Bundled enginelibxml2 2.14.6 + libxslt 1.1.43

एक बात पहले साफ कर दूँ, ताकि बाद में hype का आरोप न लगे: इस समीक्षा में कोई secret नहीं है। lxml इतना पुराना है कि यहाँ दिखने वाला हर behavior कहीं न कहीं lxml docs, libxml2 changelog, या किसी launchpad thread में पहले से दर्ज है। मैंने कोई undocumented trick नहीं खोजा, और न ही गढ़ने वाला हूँ। इस पूरे लेख की कीमत यह है कि इसे systematic, quantified, और lxml-centered तरीके से organize किया गया है — न कि इसलिए कि इसमें कोई नई खबर है।

Test Setup (और timing numbers उधार क्यों लिए गए)

इस review में दो तरह का data है, और वे दो अलग जगहों से आए हैं — इसलिए शुरुआत में ही साफ कर रहा हूँ कि कौन-सा किससे आया है।

Capability tests — यानी XPath behavior, दो parser APIs, namespaces, encoding, node lifecycle — मैंने एक machine पर fresh run किए: macOS arm64, Python 3.14.2, lxml 6.1.1, libxml2 2.14.6। artifacts/raw/*.json files में जो भी number है, वह script-run से निकला है, हाथ से लिखा नहीं गया। Capability tests deterministic booleans और enums होते हैं, इसलिए एक run काफी stable रहता है — machine load से यह नहीं बदलता कि //a/@href attribute string लौटाएगा या नहीं।

Timing और memory-footprint numbers इस pack से नहीं हैं। इन्हें पहले वाले selectolax benchmark pack से ज्यों-का-त्यों लिया गया है — वही machine, वही virtual environment, वही lxml और libxml2 build, benchmarks as-of 2026-07-13 — और मैंने उन्हें यहाँ दोबारा run नहीं किया। यह जानबूझकर किया गया। Timing benchmarks को capability scripts के batch के साथ run करने से CPU contention पैदा हो सकता है, जिससे reused figures खराब हो जाते; और वह duplicate work भी होता, क्योंकि उस pack में lxml पहले ही पूरी तरह measured control library था। एक ही bench को reuse करने से apples-to-apples तुलना बनी रहती है, दूसरी हल्की-सी अलग measurement नहीं बनती। इसलिए नीचे जहाँ millisecond figure दिखे, उसे "same test rig, as-of 2026-07-13" समझें, न कि "मैंने आज दोबारा timing निकाली है।"

Findings के साथ confidence tag दिए गए हैं: deterministic capability tests के लिए single-observation, reused timing distributions के लिए triple-run, और जहाँ मैं कोई ऐसा mechanism सुझा रहा हूँ जिसे isolate नहीं किया गया, वहाँ hypothesis

XPath: वह एक चीज़ जो selectolax और BeautifulSoup के पास नहीं है

यही headline है, इसलिए यहीं से शुरू करते हैं।

lxml XPath coverage moat with axes predicates and functions

मैंने lxml के xpath() को 37-item pre-registered matrix में चलाया — हर case का expected result test शुरू होने से पहले source में लिखा गया था, ताकि grading में अनजाने में दया का असर न पड़े। दस axes, नौ predicate styles, दस built-in functions, तीन scalar return types, और पाँच deliberate trap cases जिनमें XPath 2.0-only syntax था, जिसे lxml का 1.0 engine reject करना चाहिए।

CategoryCoverageResult
Axeschild / descendant / parent / ancestor / following-sibling / preceding-sibling / self / attribute / following / preceding10/10 pass
Predicates[1] / last() / position()<n / attribute equality / attribute existence / and / or / nested [.//a] / not()9/9 pass
Functionstext() / contains() / starts-with() / count() / string-length() / normalize-space() / concat() / substring() / name() / string()10/10 pass
Return typesboolean / number scalars3/3 pass
Trap casesmatches() / sequences / if-then-else / except / syntax error5/5 correctly rejected

स्कोर 37/37 है, और trap कॉलम सबसे ज़रूरी है। matches(), sequence expressions, if/then/else, और except सब XPath 2.0 syntax हैं, और libxml2 का 1.0 engine उन्हें आधा-सा support नहीं करता — यह XPathEvalError उठाकर साफ मना कर देता है, गलत node set चुपचाप नहीं लौटाता। यानी यह perfect score है उसे तोड़ने की कोशिश के बाद, न कि softballs से जमा किया गया score। यहाँ हर व्यवहार बिल्कुल वैसा है जैसा lxml XPath docs बताते हैं, और यही इसका point है।

एक बात मैं मानूँगा: harness ने एक जगह गलती पकड़ी, और वही 37/37 वाला version है जिस पर भरोसा किया जा सकता है। //div[.//a[@href]] के लिए मेरा पहला expected set दो hits बता रहा था; run में एक ही मिला। मैंने लगभग तीस सेकंड तक मान लिया कि lxml गलत है, फिर fixture देखा और पता चला कि दूसरा element <footer> था, <div> नहीं — गलती मेरी थी, engine की नहीं। मैंने expected set ठीक किया और comment में गलती भी रहने दी। blame का सही क्रम यही है: पहले अपने test पर शक करें, 20 साल पुरानी C library पर बाद में।

XPath बनाम CSS: CSS में आप क्या लिख ही नहीं सकते

"XPath ज़्यादा powerful है" — इस abstract दावे के लिए concrete number चाहिए, इसलिए मैंने gap को quantify किया। lxml आपको .xpath() और .cssselect() दोनों देता है (दूसरा CSS को internally XPath में translate करता है)। मैंने दस selection targets लिए और देखा कि CSS में किन्हें सचमुच express किया जा सकता है।

XPath expresses seven of ten tasks CSS cannot express

TargetXPathCSS (cssselect)
Filter by text content (contains(text(),"bargain"))YesNo text predicate
Select parent from child (//b/parent::p)YesNo parent selector
Return an attribute value (//a/@href)YesElements only
Return a text node (//p/text())YesNo text nodes
Ancestor axis (//td/ancestor::div)YesNo upward navigation
Filter parent by child count (//ul[count(li)=4])YesNo count predicate
Filter by text length (string-length(text())>5)YesNo length predicate
nth-child / last-child / adjacent siblingYesYes (3 baseline)

दस में से सात targets के लिए CSS का कोई equivalent ही नहीं है। text-content filtering, parent और ancestor तक ऊपर जाना, attribute value या bare text node को result की तरह लेना, count-based predicates — CSS इनमें से कुछ भी नहीं कह सकता। सिर्फ तीन (nth-child, last-child, adjacent sibling) दोनों में काम करते हैं। यही quantified जवाब है उस सवाल का: "lxml लेने से मुझे असल में क्या मिलता है?" selectolax सिर्फ CSS तक सीमित है और उसके पास xpath() method नहीं है, इसलिए उन सात query types के लिए वहाँ या तो multi-step Python loops लिखने पड़ेंगे, या काम नहीं होगा। अगर आपकी scraping logic इनमें से किसी पर निर्भर है, तो आपका फैसला पहले ही तय हो चुका है।

(और हाँ, harness ने यहाँ मुझे दूसरी बार पकड़ा: मैंने string-length(text())>5 के लिए empty set अनुमानित किया था, लेकिन दो छह-character strings match हुए। expectation ठीक किया, tool नहीं।)

Strictness के तीन मोड: etree, recover, और lxml.html

XPath lxml चुनने की वजह है। Strictness control के तीन मोड इसकी वजह हैं कि आप इसे इस्तेमाल करते रहें।

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

ज़्यादातर parser खराब input के लिए सिर्फ एक behavior देते हैं। lxml तीन देता है — और वे इतने predictable हैं कि मैंने छह तरह के malformed markup को हर mode से गुज़ारा और पहले से तय किया कि हर रास्ते पर क्या होना चाहिए।

Malformed inputlxml.etree (strict)etree + recover=Truelxml.html (lenient)
Unclosed tag <root><a>x</root>raisesrecoversaccepts
Mis-nested <b><i></b></i>raisesrecoversaccepts
Undefined entity &nbsp;raisesrecoversaccepts
Bare & (Tom & Jerry)raisesrecoversaccepts
Multiple roots <a>1</a><b>2</b>raisesrecoversaccepts
Well-formed XMLacceptsaccepts (0 errors)accepts
Boolean attribute <input disabled>raisesrecoversaccepts

सात में से सात cases ने pre-registered expectation से मेल खाया। lxml.etree सभी छह malformed classes पर XMLSyntaxError उठाता है। उसी parser में recover=True जोड़िए और यह errors को निगलकर usable tree बना देता है — और underrated बात यह है कि parser.error_log फिर भी हर swallowed error को सूचीबद्ध करता है। lxml.html बिना शिकायत सब कुछ स्वीकार कर लेता है।

यह तय करने वाला classifier कि "raised vs recovered vs accepted" क्या होगा, runtime error_log length पर चलता है, hard-coded नहीं है — इसलिए recover=True के तहत well-formed document को सही ढंग से "accepts" (empty log) कहा जाता है, न कि "recovers"। classifier का मेरा पहला version recover=True वाले हर result को "recovers" बता देता था और clean input को गलत label कर देता था; असली error_log पढ़ने से यह ठीक हुआ।

व्यावहारिक रूप से यह आपको क्या देता है: जहाँ broken feed को loudly fail करना चाहिए, वहाँ lxml.etree लें। गंदे real-world HTML को बस किसी तरह पढ़ लेना है, तो lxml.html लें। और वह बीच का case जो ज़्यादातर tools नहीं कर पाते — "ढील रखो, लेकिन बताओ क्या टूटा था ताकि मैं log कर सकूँ" — उसमें recover=True इस्तेमाल करें और error log पढ़ें। selectolax के पास lenient gear है, बस; strict mode नहीं, error log नहीं।

iterparse: वह streaming gear जो selectolax के पास बिल्कुल नहीं है

यह speed knob नहीं, capability line है। selectolax सिर्फ एक पूरा string ingest करता है — incremental interface नहीं है। lxml का iterparse elements को उनके close होने पर yield करता है, और classic fast_iter pattern (जाते-जाते elem.clear() कॉल करें और preceding siblings delete करें) के साथ यह document कितना भी बड़ा हो, memory को लगभग flat रखता है।

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

मैंने memory characteristic सीधे मापी — ru_maxrss के जरिए peak RSS, हर subject के लिए अलग fresh process, 300,000 <record> elements पर, जो कुल मिलाकर लगभग 15 MB थे।

ModePeak RSS deltaNotes
iterparse + clear (fast_iter)~1-2 MBreleased as it goes; flat regardless of count
iterparse without clear~386 MBkeeps references; as heavy as full load
etree.parse (full load, anchor)~386 MBknown-heavy; proves the meter reads magnitude

bounded mode peak RSS delta को full load के ~386 MB की तुलना में लगभग 1-2 MB तक सीमित रखता है — यानी लगभग 0.3-0.4% का अंतर — और पहला record event file पूरी तरह read होने से पहले ही आ जाता है, इसलिए यह सचमुच incremental है, नकली streaming नहीं। सबसे instructive line बीच वाली है। वही iterparse loop चलाइए लेकिन clear() छोड़ दीजिए, और memory फिर ~386 MB तक चढ़ जाती है, क्योंकि आप हर चीज़ के references पकड़े हुए हैं। जीत clear() में है, iterparse अकेले में नहीं। full-load anchor का bounded mode से काफी ऊपर रहना यह भी साबित करता है कि RSS meter सचमुच magnitude gap देख पा रहा है, अँधेरे में नहीं पढ़ रहा। (यह memory test मैंने इसी pack में किया — यह footprint measurement है, borrowed timing numbers से अलग।)

असल दुनिया में इसका मतलब: multi-gigabyte XML export जो RAM में फिट नहीं होता, उसके लिए selectolax का कोई रास्ता ही नहीं है। वहाँ या तो lxml का streaming parser है, या फिर दूसरी भाषा।

Namespaces: RSS, SVG, और default-namespace trap

बारह namespace cases, जिनमें RSS के तीन namespaces, default namespace + xlink वाला SVG, और default-namespace XML शामिल थे। बारहों पास हुए।

lxml किसी RSS feed से //dc:creator/text() को ठीक ["Alice", "Bob"] की तरह निकालता है, एक ही document में तीन अलग namespaces के across //atom:link/@href और //content:encoded को resolve करता है, SVG के दूसरे namespace में //s:rect और //s:use/@xlink:href संभालता है, Clark-notation {uri}local names को QName से split करता है, और nsmap के जरिए introspect करता है। यह documented और maintained behavior है, और यह selectolax के लिए एक पूरी dimensional gap है, क्योंकि selectolax HTML5-only है और arbitrary XML namespaces को process नहीं करता।

एक documented trap याद रखने लायक है। XPath में default namespace की कोई अवधारणा नहीं होती। xmlns="urn:..." वाले document पर //book चलाइए और zero hits मिलेंगे — XPath में empty prefix undefined है, जैसा कि lxml docs में साफ लिखा है। आपको या तो एक artificial prefix bind करना पड़ता है (//c:book के साथ namespaces={"c": "urn:..."}), जिसने तीनों मैच किए, या फिर //*[local-name()='book'] पर जाना पड़ता है, जिसने भी तीन मैच किए। यह bug नहीं है — XPath spec यही कहती है, और lxml उसे ठीक वैसे ही लागू करता है। बस हर किसी को यह एक बार चौंकाती है।

असली गंदे pages: 11 वास्तविक scrapes पर fidelity

Synthetic tests साफ-सुथरे होते हैं; web नहीं। मैंने selectolax pack के fixture set से ग्यारह असली captured pages reuse किए (as-of 2026-07-10, read-only) और lxml के subject के रूप में lxml.html को उन पर चलाया।

FixtureSizeLinkslibxml2 recovered errorsStrict XML
news_bbc.html398 KB2554ok
docs_mdn_array.html243 KB5080raised
wiki_scraping.html227 KB4600raised
gov_whitehouse.html289 KB1540raised
oldstyle_craigslist.html561 KB3510raised
forum_reddit.html129 KB3180raised
docs_python.html80 KB3412raised
ecommerce_books.html51 KB940raised
news_hackernews.html35 KB2290raised
ecommerce_webscraper_allinone.html16 KB350raised
spa_quotes_js.html6 KB50raised

सभी ग्यारह lxml.html से parse हुए, और link, heading, और image counts सभी ग्यारह पर selectolax pack में मौजूद lxml counts से मेल खाए — cross-check true। यही agreement बताता है कि reuse सचमुच apples-to-apples है, न कि same label के नीचे दो अलग measurements।

एक side finding यह थी: strict XML parser ने ग्यारह में से दस pages पर raise किया। असली web pages overwhelmingly well-formed XML नहीं होतीं, और इसी वजह से libxml2 का HTML recovery mode मौजूद है — ताकि वह उन्हें झेल सके। एकमात्र अपवाद BBC News था, जो Next.js द्वारा rendered था और strict XML parsing से बच निकलने लायक well-formed था। हर चीज़ जिसे "HTML" कहा जाता है, उसे recovery path की ज़रूरत नहीं होती।

एक counting note जिस पर फँसना आसान है। docs_python.html पर //a[@href] (attribute-existence) ने 343 count किया, जबकि selectolax pack के if n.get("href") (truthy value) ने 341 count किया। ये दो extra links empty href="" थे। यह counting-convention का फर्क है — attribute exists बनाम attribute non-empty — न कि lxml behavior का, और predicate align होने पर counts मिल जाते हैं। scraping में यह जानना उपयोगी है: empty href count होंगे या नहीं, यह parser का नहीं, आपके filter का फैसला है।

वह depth limit जो bug जैसी लगती है (लेकिन नहीं है)

selectolax pack में यह दर्ज था कि lxml 1,000 और 5,000 स्तर वाले nested <div> markup में सबसे गहरा content छोड़ देता है, और इसे "lxml silently loses the deepest content" की तरह पेश किया गया था। मैं mechanism जानना चाहता था, इसलिए मैंने default parser को huge_tree=True के साथ run किया।

lxml default depth guard around 253 levels and huge_tree to 2045

Requested depthDefault parser reacheshuge_tree=True reaches
300253 (drops rest)299 (recovered)
1000253 (drops rest)999 (recovered)
5000253 (drops rest)2045 (still drops)

Default parser लगभग 253 levels पर truncate कर देता है और उससे गहरा सब कुछ चुपचाप छोड़ देता है। यह bug नहीं है — यह libxml2 की DoS defense है, लगभग 256-level nesting cap, जो hostile document को stack उड़ाने से रोकता है, और यह lxml launchpad thread में XML_PARSE_HUGE पर documented है। huge_tree=True सेट करने पर 300 और 1,000 depth पूरी तरह वापस मिल जाती है। लेकिन 5,000 depth पर भी huge_tree के साथ केवल 2,045 तक ही पहुँचा जा सका — यानी libxml2 में एक दूसरी, कड़ी recursion ceiling है जो configurable cap से ऊपर रहती है, और huge_tree उसे नहीं हटाता।

इसलिए practical action item साफ है: जब आप trusted source से deep markup parse कर रहे हों, lxml.html.HTMLParser(huge_tree=True) इस्तेमाल करें। इस pack का नया योगदान mechanism है (safety cap, data corruption नहीं), fix है (huge_tree), और यह तथ्य भी कि एक दूसरी ceiling है जिसे fix पार नहीं करता।

Read/Write DOM, Serialization, Encoding

lxml एक full read/write tree है, read-only extractor नहीं — और मैंने editing surface को case by case verify किया। सभी आठ DOM operations पास हुए: SubElement, insert, remove, replace, strip_tags (tag हटाकर text रहने देना), strip_elements (tag और उसका text दोनों हटाना), drop_tree (एक lxml.html exclusive), और text/tail dual-slot model जो newcomers को confuse करता है — <p>head<b>bold</b>tail</p> में p.text "head" होता है, b.text "bold" होता है, और b.tail "tail" होता है।

Serialization पाँच में पाँच पास हुई: XML और HTML modes में tostring (HTML void elements को सही तरह self-close नहीं करता), pretty_print, C14N canonicalization (method="c14n", एक और lxml exclusive), और clean round-trip।

Encoding वह जगह है जहाँ lxml चुपचाप खुद को अलग करता है। इसे non-UTF-8 bytes दीजिए — "<p>café éè</p>".encode("latin-1") को lxml.html.fromstring में डालिए — और यह café éè को सही-सलामत recover कर लेता है, न U+FFFD replacement characters, न dropped bytes। यह सीधे selectolax pack में इसकी भूमिका को दोहराता है, जहाँ वही input दूसरे दो engines में silently corrupt हो गया था (Lexbor ने replacement characters दिए, Modest ने bytes ही गिरा दिए)। libxml2-backed charset detection यहाँ बस अधिक stable है।

दूसरी तरफ, यह encoding को declare करने के तरीके में strict है। XML declaration में encoding="latin-1" देने पर XMLSyntaxError: Unsupported encoding: latin-1 मिलता है, जबकि IANA-canonical encoding="ISO-8859-1" ठीक parse होकर café लौटाता है। libxml2 aliases नहीं, सिर्फ canonical encoding names स्वीकार करता है — यह detail launchpad #613302 में document है। अगर यह पता न हो तो irritate करने वाली बात है, पता हो तो trivial।

आख़िर में, node lifecycle। मैंने तीन stale-handle scenarios isolated subprocesses में run किए (hard crash non-zero exit के रूप में दिखता): tree garbage-collected होने के बाद node पकड़ कर रखना, drop_tree() के बाद handle पढ़ना, और remove() के बाद node इस्तेमाल करना। इनमें से किसी में भी segfault नहीं हुआ — lxml node की tree-reference को alive रखता है ताकि use-after-free न हो। यही clean bill of health selectolax को भी इस test में मिला।

Speed और Memory (उधार ली गई, और ईमानदारी से बताई गई)

इस section की हर चीज़ selectolax pack से reuse की गई है, as-of 2026-07-13। इस pack ने अपना कोई timing number नहीं बनाया, और मैं यह बात दो बार कह देना बेहतर समझता हूँ बजाय इसके कि आपको लगे मैंने कुछ re-time किया है।

Dimensionlxml valueReading
Pure parse p50 (10 MB)77.9 ms~33-34% faster than selectolax-Lexbor
Full parse + extract p50 (1 MB / 10 MB)14.18 ms / 172.9 msroughly even with Lexbor at small sizes
100k-node CSS throughput3,002,646 nodes/sfastest tier of the three C engines
10 MB RSS delta128.9 MBleanest of six parsers, ~1.7x leaner than BeautifulSoup
Import cold start14.1 ms~2.3x faster than parsel-style imports

pure-parse और throughput के number मज़बूत हैं, और lxml छह measured parsers में सबसे कम memory खाता है। लेकिन threading picture के साथ एक caveat ज़रूरी है। reused data दिखाती है कि 4-thread wall-clock speedup सिर्फ 1.21x था, और उसे inconclusive चिह्नित किया गया — पर वह shared-default-parser path था। lxml FAQ साफ कहती है कि parsing के दौरान GIL तभी release होता है जब हर thread अपना parser इस्तेमाल करे (या default का copied version); shared parser access को serialize कर देता है। मैंने सही तरीके से काम करने के API surface को structurally verify किया (XMLParser.copy() मौजूद है, get/set_default_parser मौजूद हैं, XPathEvaluator के पास internal lock है), लेकिन per-thread-parser speedup measure नहीं किया — वह नई timing measurement होती, और इस pack का काम वह नहीं है। इसलिए 1.21x को "naive shared path के नीचे" पढ़ें, न कि lxml की threading ceiling के रूप में।

और इन सब पर एक asterisk भी है: ये single-platform numbers हैं, macOS arm64 पर। यह दावा कि lxml का pure parse Lexbor से तेज़ है, आम consensus के उलट जाता है, जहाँ Lexbor-backed parser को सबसे तेज़ माना जाता है — इसलिए कोई भी इसे final मानने से पहले Linux x86_64 recheck चाहता है।

Licensing: वह boring लेकिन बढ़िया जीत

lxml BSD-3-Clause के तहत ship होता है, और जो C libraries यह bundle करता है — libxml2 और libxslt — वे दोनों MIT हैं। यह पूरी तरह permissive chain है, कहीं भी copyleft नहीं, और redistribution के समय यही मायने रखता है। तुलना में selectolax wheel LGPL-2.1 Modest और Apache-2.0 Lexbor bundle करता है, इसलिए closed product में shipping के लिए lxml की कहानी साफ़-सुथरी है।

एक practical install advantage भी है: lxml prebuilt wheels publish करता है जो libxml2 और libxslt को statically link करते हैं, इसलिए pip install lxml के लिए आमतौर पर आपके सिस्टम पर libxml2 या compiler की ज़रूरत नहीं पड़ती — source से build करने के मुकाबले यह अलग अनुभव है।

lxml कहाँ fit होता है — और कहाँ AI extraction layer काम संभाल लेती है

Boundary साफ कर लेते हैं, क्योंकि यहाँ category error होना आसान है। lxml एक parsing library है। यह आपको tree और शानदार query engine देता है, और उस tree के आसपास की हर चीज़ अभी भी आपकी ज़िम्मेदारी है: page fetch करना, JavaScript render करना, anti-bot defenses पार करना, XPath लिखना और संभालना, और result structure बनाना। यह hosted extraction service से अलग layer है, और दोनों rival कम, पड़ोसी ज़्यादा हैं।

जो developer fetch-render-select-maintain stack खुद नहीं चलाना चाहता, उसके लिए ऊपर वाली layer में Thunderbit जैसा कुछ बैठता है — और इस audience के लिए browser extension नहीं, बल्कि API, MCP server, और CLI ज़्यादा relevant हैं। Thunderbit Open API POST /distill देता है ताकि page को clean Markdown में बदला जा सके, और POST /extract देता है ताकि JSON Schema के आधार पर structured data निकाला जा सके, साथ में renderMode switch और volume के लिए batch jobs भी हैं। वही engine MCP server (thunderbit_suggest_fields, thunderbit_distill, thunderbit_extract) के रूप में agents और coding assistants के लिए उपलब्ध है, और CLI के रूप में भी जिसे आप सीधे terminal से npx @thunderbit/thunderbit-cli के जरिए चला सकते हैं। यह JS rendering, anti-bot, और CAPTCHAs out of the box संभालता है और schema-matched JSON लौटाता है — यानी parsing के ऊपर वाली layer, उसका replacement नहीं।

वेब डेटा एक्सट्रैक्शन के लिए Thunderbit आज़माएँ

सीधी बात यह है: जब आप pipeline खुद नियंत्रित करते हैं और tree पर surgical XPath control चाहिए, तब lxml लें। जब selectors और rendering संभालना ही नहीं चाहते, तब AI extraction API लें। कई real systems दोनों का इस्तेमाल करते हैं — controlled feeds के लिए lxml, और messy long-tail pages के लिए extraction service।

इस समीक्षा ने क्या टेस्ट नहीं किया

यह provisional review है, final scorecard नहीं — इसलिए यह भी साफ़ है कि इसमें क्या शामिल नहीं है।

सारे timing और memory figures reused हैं, single-platform हैं (macOS arm64, Python 3.14), और उस pack की caveats inherit करते हैं — "lxml pure parsing में तेज़ है" वाला result counter-consensus है और Linux x86_64 recheck मांगता है। per-thread-parser threading speedup untested है (नई timing चाहिए होती)। मैंने 300k records पर iterparse memory मापी, लेकिन GB-scale real XML पर नहीं, iterparse on HTML बनाम XML पर नहीं, और multi-hour soak पर भी नहीं। lxml का XSLT 1.0, RelaxNG / XMLSchema / DTD validation, और EXSLT extensions यहाँ पूरी तरह untested हैं — capability surface बड़ी है, लेकिन parsing-and-selection core से बाहर। मैंने 2,045 पर दूसरी depth ceiling तो देखी, लेकिन libxml2 की exact recursion constant pin down नहीं की। सिर्फ stable 6.1.1 टेस्ट हुआ, 7.0.0 alpha नहीं। Windows, source builds, और free-threaded 3.14t build — सब untested हैं। और XPath के भीतर भी, मैंने built-in functions कवर किए, लेकिन XPath variables, custom Python extension functions, या precompiled etree.XPath object reuse नहीं देखा।

अंतिम फैसला

lxml नया तेज़ tool नहीं है, और यही इसकी सिफारिश का कारण है। यह दो दशक पुराना libxml2 binding है, जिसके पास पूरा XPath 1.0 engine है — ऐसा जो mainstream Python में कोई और नहीं दे पाता — parsing strictness के तीन भरोसेमंद मोड हैं जिनके बीच error log बीच में मौजूद है, memory में न आने वाले documents के लिए असली streaming parser है, multi-namespace और encoding handling सही है, और license पूरी तरह permissive है। कुछ sharp edges — लगभग 253-level depth cap और shared-parser threading number — documented हैं, configurable हैं, और अब समझाए भी जा चुके हैं।

अगर आप अपनी scraping pipeline खुद चलाते हैं और XPath पर निर्भर हैं, तो lxml आज भी वही parser है जिसे आप चुनते हैं। अगर selectors और rendering खुद maintain नहीं करना चाहते, तो Thunderbit API, MCP, और CLI जैसी AI extraction layer उसी काम के लिए है — काम बाँटने का साफ तरीका, competition नहीं। दोनों का साथ में इस्तेमाल भी आम है। चाहे जो भी चुनें, इन numbers को provisional मानें और अपने platform पर timing दोबारा check करें, उससे पहले कि आप उन्हें किसी design doc में quote करें।

वेब डेटा एक्सट्रैक्शन के लिए Thunderbit आज़माएँ Get Started Free

FAQs

क्या lxml एक web scraper है? नहीं। lxml एक parser और serializer है — libxml2/libxslt का Python binding, जो markup को editable और queryable tree में बदलता है। यह pages fetch नहीं करता, JavaScript render नहीं करता, और anti-bot defenses handle नहीं करता; request layer आपको देना होता है (requests, httpx, headless browser, या scraping service के जरिए), और फिर bytes lxml को देने होते हैं।

BeautifulSoup या selectolax की जगह lxml कब इस्तेमाल करना चाहिए? जब आपको XPath चाहिए, तब lxml चुनें। BeautifulSoup backend parser के रूप में lxml का उपयोग कर सकता है, लेकिन native XPath नहीं देता, और selectolax CSS-only है तथा अपने narrow niche में तेज़ है। अगर आपकी selection logic में text-content filtering, parent या ancestor navigation, attribute/text-node extraction, या count predicates चाहिए, तो lxml का XPath engine ही mainstream Python विकल्प है जो इन्हें सीधे express करता है।

lxml गहरी nesting वाला content चुपचाप क्यों हटा देता है? इसका default parser nesting को लगभग 253 levels पर cap कर देता है — यह libxml2 की DoS defense है, bug नहीं। huge_tree=True सेट करें (उदाहरण के लिए lxml.html.HTMLParser(huge_tree=True)) और यह 300 और 1,000 depths पूरी तरह वापस ला देता है। ध्यान रहे, लगभग 2,045 levels पर एक दूसरी, कड़ी recursion ceiling भी है जिसे huge_tree नहीं हटाता।

क्या lxml multithreaded parsing में GIL छोड़ता है? सिर्फ सही परिस्थितियों में। lxml FAQ के अनुसार parsing के दौरान GIL तब release होता है जब हर thread अपना parser या copied default parser इस्तेमाल करता है; shared parser access को serial कर देता है। यहाँ reusable 4-thread speedup 1.21x था, और वह naive shared-parser path को दिखाता है, per-thread-parser ceiling को नहीं — जिसे यहाँ measure नहीं किया गया।

क्या lxml 2026 में भी maintained है? हाँ। stable release 6.1.1, 2026-05-18 को आया, repository का last push 2026-07-02 को हुआ, और 7.0.0 alpha प्रगति पर है। लगभग 3,000 GitHub stars और नीचे सक्रिय libxml2 maintenance के साथ, यह legacy नहीं बल्कि एक current, well-supported library है।

Ke
Ke
Thunderbit में CTO | वरिष्ठ डेटा वैज्ञानिक और एमएल विशेषज्ञ मशीन लर्निंग और डेटा साइंस में लगभग एक दशक के अनुभव के साथ, के शेन कोलंबिया विश्वविद्यालय के पूर्व छात्र हैं और Walmart Labs में पूर्व वरिष्ठ डेटा वैज्ञानिक रह चुके हैं। Python, R, Java और सांख्यिकी में उनकी गहरी, सहकर्मी-मान्य विशेषज्ञता है, और वे जटिल AI एल्गोरिद्म को सिद्धांत से उत्पादन-स्तरीय आर्किटेक्चर तक ले जाने पर व्यावहारिक, आजमाई हुई अंतर्दृष्टियाँ साझा करते हैं।
Topics
वेब स्क्रैपिंग टूल्सAI वेब स्क्रैपर

Thunderbit आज़माएं

लीड्स और अन्य डेटा सिर्फ 2 क्लिक में स्क्रैप करें। AI से संचालित।

Thunderbit पाएं यह मुफ्त है
AI का उपयोग करके डेटा निकालें
डेटा को Google Sheets, Airtable या Notion में आसानी से ट्रांसफर करें
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week