lxml, Ditinjau: Mesin XPath yang Masih Mengalahkan Hampir Semua Parser Python

Terakhir diperbarui pada July 17, 2026
lxml, Ditinjau: Mesin XPath yang Masih Mengalahkan Hampir Semua Parser Python
Ringkasan AI
Ulasan lxml ini memposisikan library tersebut sebagai binding Python lama namun sangat andal di atas libxml2 dan libxslt, dengan satu keunggulan besar yang masih jarang disaingi parser baru: mesin XPath asli. Artikel ini menguji cakupan XPath, mode ketegasan parser, perilaku memori saat streaming, ekspresivitas CSS versus XPath, serta batas kedalaman libxml2. Hasilnya menunjukkan bahwa lxml cepat, hemat memori, dan sangat mumpuni untuk beban kerja XML dan HTML yang membutuhkan axes, predicate, fungsi, streaming, atau mode recovery yang kuat. Ulasan ini juga menjelaskan default kedalaman tree yang dibuat untuk keamanan dan kapan huge_tree mengubah batas tersebut.

Setiap beberapa bulan, selalu ada parser HTML yang katanya lebih ngebut, benchmark bertebaran, lalu ada yang buru-buru bilang pemain lama sudah basi. Sampai akhirnya Anda harus memilih setiap paragraf yang mengandung kata tertentu, atau mengambil parent dari node yang cocok, dan baru sadar kenapa lxml masih tetap kebuka di tab lain Anda.

lxml adalah binding libxml2 yang sudah berumur 20 tahun. Ia tidak heboh. Ia bukan barang baru. Tapi untuk satu pekerjaan yang sangat spesifik — apa pun yang benar-benar butuh XPath — belum ada yang benar-benar bisa menandingi di ekosistem Python arus utama. Ini ulasan langsung tentang apa yang ia kerjakan, di mana ia diam-diam unggul, dan beberapa jebakan kecil di default-nya yang bisa bikin Anda kepeleset kalau tidak tahu.

lxml dalam Satu Paragraf: Sebenarnya Apa Ini

lxml adalah binding Python untuk pustaka C libxml2 dan libxslt. Ini parser dan serializer, bukan scraper dan bukan browser — ia mengubah markup menjadi tree yang bisa Anda query dan edit, lalu mengubah tree itu kembali menjadi bytes. lxml menyediakan API yang kompatibel dengan ElementTree, mesin XPath 1.0 yang lengkap, XSLT 1.0, dan validasi schema, dirawat oleh Stefan Behnel dengan tagline "the most feature-rich and easy-to-use library for processing XML and HTML in the Python language".

Berikut posisinya, berdasarkan snapshot GitHub dan PyPI yang diambil pada 2026-07-14:

FieldValue
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

Satu hal perlu saya luruskan dari awal supaya tidak ada yang salah paham: ulasan ini bukan sensasi. lxml sudah cukup tua, jadi tiap perilaku yang saya bahas di sini sebenarnya sudah terdokumentasi di suatu tempat di dokumentasi lxml, changelog libxml2, atau thread launchpad. Saya tidak menemukan trik rahasia yang tidak pernah ditulis, dan saya juga tidak akan mengarang-ngarangnya. Nilai dari ulasan ini ada pada semuanya yang sudah disistematisasi, dikuantifikasi, dan disusun dengan lxml sebagai subjek — bukan karena ini berita baru.

Setup Pengujian (dan Kenapa Angka Waktunya Dipinjam)

Ada dua kategori data yang masuk ke ulasan ini, dan keduanya datang dari dua tempat berbeda, jadi saya akan jujur soal mana yang mana.

Capability test — perilaku XPath, dua API parser, namespace, encoding, siklus hidup node — saya jalankan ulang dari nol di satu mesin: macOS arm64, Python 3.14.2, lxml 6.1.1, libxml2 2.14.6. Semua angka di file artifacts/raw/*.json dihitung oleh skrip saat dijalankan, bukan diketik manual. Capability test sifatnya deterministik, berupa boolean dan enum, jadi satu kali run sudah stabil — beban mesin tidak mengubah apakah //a/@href mengembalikan string atribut atau tidak.

Angka timing dan footprint memori bukan dari paket ini. Angka itu dipakai ulang persis dari paket benchmark selectolax sebelumnya — mesin yang sama, virtual environment yang sama, build lxml dan libxml2 yang sama, benchmark per 2026-07-13 — dan saya tidak menjalankannya ulang di sini. Itu memang disengaja. Menjalankan benchmark timing bersamaan dengan kumpulan skrip capability berisiko bikin kontensi CPU yang mengotori angka yang dipakai ulang, dan itu juga pekerjaan duplikat: di paket sebelumnya, lxml sudah berperan sebagai library kontrol yang diukur penuh. Memakai ulang benchmark yang sama menjaga semuanya benar-benar apple-to-apple, bukan memperkenalkan pengukuran kedua yang diam-diam beda. Jadi kalau Anda melihat angka milidetik di bawah, bacalah sebagai "rig pengujian yang sama, per 2026-07-13," bukan "saya ukur ulang hari ini."

Setiap temuan diberi tag keyakinan: single-observation untuk capability test yang deterministik, triple-run untuk distribusi timing yang dipakai ulang, dan hypothesis kalau saya mengusulkan mekanisme yang belum saya isolasi.

XPath: Satu Hal yang Tidak Dimiliki selectolax dan BeautifulSoup

Ini inti utamanya, jadi kita mulai dari sini.

lxml XPath coverage moat with axes predicates and functions

Saya menguji xpath() milik lxml dengan matriks 37 item yang sudah dipre-registrasi — hasil yang diharapkan untuk setiap kasus sudah ditulis ke source sebelum tes berjalan, jadi saya tidak mungkin menilai dengan standar yang berubah-ubah. Sepuluh axis, sembilan gaya predicate, sepuluh fungsi bawaan, tiga tipe return skalar, dan lima kasus jebakan sengaja memakai sintaks XPath 2.0 yang seharusnya ditolak oleh engine 1.0 milik lxml.

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

Skornya 37/37, dan kolom jebakan justru yang paling penting. matches(), ekspresi sequence, if/then/else, dan except semuanya sintaks XPath 2.0, dan engine 1.0 libxml2 tidak setengah-mendukungnya — ia melempar XPathEvalError dan menolak, alih-alih diam-diam mengembalikan node set yang salah. Jadi ini skor sempurna setelah dicoba dibongkar, bukan skor sempurna yang cuma diisi soal-soal gampang. Setiap perilaku di sini persis seperti yang dijelaskan di dokumentasi XPath lxml, dan memang itu poinnya.

Saya akui ada satu hal yang sempat keliru dari harness, tapi justru ini versi "37/37" yang bisa Anda percaya. Set expected awal saya untuk //div[.//a[@href]] memprediksi dua hasil; run-nya mengembalikan satu. Saya sempat mengira lxml salah selama sekitar tiga puluh detik, lalu cek fixture dan ternyata elemen kedua itu <footer>, bukan <div> — jadi ekspektasi saya yang salah, bukan engine-nya. Saya perbaiki expected set dan membiarkan kesalahan itu tetap ada di komentar source. Urutan menyalahkan yang benar adalah: curigai test Anda sendiri dulu sebelum menyalahkan library C berusia 20 tahun.

XPath vs CSS: Apa yang Secara Harfiah Tidak Bisa Diekspresikan di CSS

Klaim abstrak bahwa "XPath lebih kuat" layak diberi angka konkret, jadi saya kuantifikasi celahnya. lxml memberi Anda .xpath() dan .cssselect() sekaligus (yang terakhir menerjemahkan CSS ke XPath di balik layar). Saya ambil sepuluh target seleksi dan cek mana saja yang benar-benar bisa diekspresikan oleh CSS.

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)

Tujuh dari sepuluh target sama sekali tidak punya padanan CSS. Filter berdasarkan isi teks, navigasi ke atas ke parent dan ancestor, mengambil nilai atribut atau text node mentah sebagai hasil, predicate berbasis hitungan — CSS tidak bisa menyatakan semua itu. Hanya tiga (nth-child, last-child, adjacent sibling) yang bekerja di keduanya. Itulah jawaban yang terukur untuk pertanyaan "apa yang sebenarnya saya dapatkan kalau memilih lxml." selectolax hanya CSS dan tidak punya metode xpath() sama sekali, jadi tujuh jenis query itu di sana harus berubah jadi loop Python bertahap atau memang tidak bisa dilakukan. Kalau logika scraping Anda bergantung pada salah satunya, keputusan Anda sebenarnya sudah beres.

(Dan ya, harness menjebak saya untuk kedua kalinya di sini: saya memprediksi himpunan kosong untuk string-length(text())>5, padahal ada dua string berjumlah enam karakter yang cocok. Ekspektasinya yang saya perbaiki, bukan alatnya.)

Tiga Tingkat Ketegasan: etree vs recover vs lxml.html

XPath adalah alasan memilih lxml. Kontrol ketat dengan tiga mode inilah alasan untuk tetap memakainya.

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

Sebagian besar parser kasih Anda satu perilaku untuk input yang rusak. lxml memberi Anda tiga, dan semuanya cukup bisa diprediksi sehingga saya menguji enam kelas markup tidak valid di masing-masing mode dan sudah mem-pre-register bagaimana seharusnya tiap jalur berperilaku.

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

Tujuh dari tujuh sesuai dengan ekspektasi yang sudah dipre-registrasi. lxml.etree melempar XMLSyntaxError pada keenam kelas malformed. Tambahkan recover=True ke parser yang sama dan ia menelan error itu lalu menyusun ulang tree yang bisa dipakai — dan ini bagian yang sering diremehkan — parser.error_log kemudian mencantumkan setiap error yang ditelan. lxml.html menerima semuanya tanpa komplain.

Klasifier yang menentukan "raises vs recovers vs accepts" sendiri digerakkan oleh panjang error_log saat runtime, bukan hard-coded, itulah sebabnya dokumen yang valid dan dijalankan dengan recover=True tetap dilabeli dengan benar sebagai "accepts" (log kosong), bukan "recovers." Versi pertama klasifier saya menandai hasil recover=True apa pun sebagai "recovers" dan salah memberi label pada input bersih; membaca error_log yang sebenarnya memperbaikinya.

Apa manfaatnya dalam praktik: untuk validasi ketat ketika feed yang rusak harus gagal keras, pakai lxml.etree. Untuk HTML dunia nyata yang kotor dan cuma perlu diproses, pakai lxml.html. Dan untuk kasus tengah yang paling jarang bisa dilakukan tool lain — "longgar, tapi kasih tahu saya persis apa yang rusak supaya bisa saya log" — pakai recover=True dan baca error log-nya. selectolax punya mode longgar, tapi tidak lebih: tidak ada strict mode dan tidak ada error log.

iterparse: Mode Streaming yang Sama Sekali Tidak Dimiliki selectolax

Ini garis kemampuan, bukan pengatur kecepatan. selectolax hanya menerima satu string utuh — tidak ada interface inkremental. iterparse milik lxml mengeluarkan elemen saat mereka selesai tertutup, dan bila dipasangkan dengan pola fast_iter klasik (panggil elem.clear() dan hapus sibling sebelumnya sambil berjalan) memori tetap datar tidak peduli sebesar apa dokumennya.

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

Saya mengukur karakteristik memorinya langsung — peak RSS lewat ru_maxrss, masing-masing subjek di proses baru yang segar, pada 300.000 elemen <record> dengan total sekitar 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

Mode yang dibatasi menjaga peak RSS delta tetap sekitar 1-2 MB dibanding full load yang ~386 MB — selisih magnitudo sekitar 0,3-0,4% — dan event record pertama muncul bahkan sebelum file selesai dibaca, jadi ini benar-benar inkremental, bukan streaming palsu. Baris yang paling mengajarkan adalah baris tengah. Jalankan loop iterparse yang sama tapi lewati clear(), dan memori langsung naik lagi ke ~386 MB, karena Anda menahan referensi ke semuanya. Keunggulannya ada di clear(), bukan di iterparse itu sendiri. Angka anchor full-load yang jauh lebih tinggi daripada mode dibatasi juga menegaskan bahwa meter RSS memang bisa melihat selisih magnitudo itu, bukan membaca buta. (Tes memori ini saya jalankan di paket ini — ini pengukuran footprint, beda dari angka timing yang dipinjam.)

Versi dunia nyatanya begini: ekspor XML multi-gigabyte yang tidak muat di RAM sama sekali tidak punya jalur selectolax. Pilihannya parser streaming lxml atau pindah bahasa lain.

Namespace: RSS, SVG, dan Jebakan Default Namespace

Dua belas kasus namespace, mencakup RSS di tiga namespace, SVG dengan default namespace plus xlink, dan XML default-namespace. Dua belas-duabelasnya lulus.

lxml mengambil //dc:creator/text() dari feed RSS sebagai tepat ["Alice", "Bob"], menyelesaikan //atom:link/@href dan //content:encoded lintas tiga namespace terpisah di dokumen yang sama, menangani //s:rect dan //s:use/@xlink:href di namespace kedua SVG, memecah nama Clark-notation {uri}local dengan QName, dan melakukan introspeksi lewat nsmap. Ini perilaku yang dirawat dan didokumentasikan, dan ini juga dimensi yang sama sekali tidak disentuh selectolax, karena selectolax hanya HTML5 dan tidak memproses namespace XML arbitrer.

Ada satu jebakan yang terdokumentasi dan layak dihafal. XPath tidak punya konsep default namespace. Arahkan //book ke dokumen yang mendeklarasikan xmlns="urn:..." dan hasilnya nol — prefix kosong tidak terdefinisi untuk XPath, seperti yang dijelaskan di dokumentasi lxml. Anda harus mengikat prefix buatan (//c:book dengan namespaces={"c": "urn:..."}, yang menemukan semuanya), atau mundur ke //*[local-name()='book'] (juga menemukan tiga). Bukan bug — itu spesifikasi XPath yang diimplementasikan dengan setia. Cuma memang menjebak hampir semua orang setidaknya sekali.

Halaman Nyata yang Kotor: Fidelity pada 11 Scrape Aktual

Tes sintetis itu bersih; web tidak. Saya memakai ulang sebelas halaman nyata yang ditangkap dari set fixture paket selectolax (per 2026-07-10, read-only) dan menguji lxml.html di atasnya dengan lxml sebagai subjek.

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

Semua sebelas terparse dengan lxml.html, dan jumlah link, heading, serta image cocok dengan angka lxml yang dipakai ulang dari paket selectolax pada semua sebelas — cross-check true. Kecocokan itu yang kasih tahu saya bahwa reuse ini memang apple-to-apple, bukan dua pengukuran beda yang pakai label sama.

Temuan sampingannya: strict XML parser melempar pada sepuluh dari sebelas halaman. Halaman web nyata sangat sering bukan XML yang well-formed, dan itulah alasan mode recovery milik libxml2 ada: untuk memakannya. Satu-satunya pengecualian adalah BBC News, dirender oleh Next.js dan cukup well-formed untuk lolos parsing XML ketat. Tidak semua yang dilabeli "HTML" butuh recovery path.

Satu catatan penghitungan yang gampang menjebak. Pada docs_python.html, //a[@href] (keberadaan atribut) menghitung 343, sementara paket selectolax memakai if n.get("href") (nilai truthy) menghitung 341. Dua ekstra itu adalah link href="" yang kosong. Itu beda konvensi hitung — atribut ada versus atribut tidak kosong — bukan beda perilaku lxml, dan angkanya akan selaras setelah predicate Anda disamakan. Penting diingat saat scraping: apakah href kosong dihitung atau tidak adalah pilihan filter Anda, bukan parser-nya.

Batas Kedalaman yang Terlihat Seperti Bug (Tapi Bukan)

Paket selectolax mencatat lxml menjatuhkan konten terdalam pada markup <div> bertingkat 1.000 dan 5.000 level, lalu memframing-nya sebagai "lxml diam-diam kehilangan konten terdalam." Saya ingin tahu mekanismenya, jadi saya menjalankan parser default dan membandingkannya dengan huge_tree=True.

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)

Parser default memotong di sekitar 253 level dan diam-diam membuang apa pun yang lebih dalam. Itu bukan bug — itu pertahanan DoS libxml2, batas nesting sekitar 256 level yang mencegah dokumen jahat merusak stack, dan ini didokumentasikan dalam thread launchpad lxml tentang XML_PARSE_HUGE. Set huge_tree=True dan kedalaman 300 serta 1.000 kembali penuh. Namun pada kedalaman 5.000, bahkan dengan huge_tree aktif, hasilnya cuma sampai 2.045 — ada batas rekursi libxml2 kedua yang lebih keras di atas batas yang bisa dikonfigurasi, dan huge_tree tidak menghapusnya.

Jadi tindakan yang perlu diambil jelas: saat mem-parsing markup dalam dari sumber yang Anda percaya, pakai lxml.html.HTMLParser(huge_tree=True). Yang ditambahkan paket ini di atas observasi yang dipakai ulang adalah mekanismenya (batas pengaman, bukan korupsi data), perbaikannya (huge_tree), dan fakta bahwa ada plafon kedua yang tidak bisa dijangkau perbaikan itu.

Read/Write DOM, Serialisasi, Encoding

lxml adalah tree read/write penuh, bukan extractor read-only, dan saya memverifikasi permukaan editnya satu per satu. Semua delapan operasi DOM lulus: SubElement, insert, remove, replace, strip_tags (menghapus tag, mempertahankan teksnya), strip_elements (menghapus tag dan teksnya), drop_tree (eksklusif lxml.html), dan model dual-slot text/tail yang sering menjebak pendatang baru — dalam <p>head<b>bold</b>tail</p>, p.text adalah "head", b.text adalah "bold", dan b.tail adalah "tail".

Serialisasi lulus lima dari lima: tostring dalam mode XML dan HTML (HTML dengan benar tidak menutup elemen void sebagai self-closing), pretty_print, canonicalization C14N (method="c14n", juga eksklusif lxml), dan round-trip yang bersih.

Encoding adalah tempat lxml diam-diam membedakan dirinya. Beri ia bytes non-UTF-8 — "<p>café éè</p>".encode("latin-1") lewat lxml.html.fromstring — dan ia memulihkan café éè dengan utuh, tanpa karakter pengganti U+FFFD, tanpa byte yang hilang. Itu secara langsung mereproduksi perannya sebagai "clean reference" di paket selectolax, tempat input yang sama rusak diam-diam di dua engine lain (Lexbor menghasilkan karakter pengganti, Modest membuang byte begitu saja). Deteksi charset milik libxml2 di lxml memang jauh lebih stabil di sini.

Sisi lainnya adalah ketat terhadap cara Anda menyatakan encoding. encoding="latin-1" di deklarasi XML melempar XMLSyntaxError: Unsupported encoding: latin-1, sementara nama kanonik IANA encoding="ISO-8859-1" berhasil diparse dan mengembalikan café. libxml2 hanya menerima nama encoding kanonik, bukan alias — detail yang sudah didokumentasikan sejak launchpad #613302. Menyebalkan kalau belum tahu, sepele setelah tahu.

Terakhir, siklus hidup node. Saya menjalankan tiga skenario stale-handle dalam subprocess terisolasi (crash keras akan terlihat sebagai exit code non-zero): menyimpan node setelah tree-nya dikoleksi garbage collector, membaca handle setelah drop_tree(), dan memakai node setelah remove(). Tidak ada segfault di semuanya — lxml menjaga referensi node ke tree-nya tetap hidup untuk mencegah use-after-free. Hasilnya sama bersihnya dengan yang selectolax dapatkan pada tes ini.

Kecepatan dan Memori (Dipinjam, dan Jujur Soal Itu)

Semua yang ada di bagian ini dipakai ulang dari paket selectolax, per 2026-07-13. Paket ini tidak menghasilkan satu pun angka timing sendiri, dan saya lebih suka bilang itu dua kali daripada Anda mengira saya mengukur ulang apa pun.

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

Angka pure-parse dan throughput terlihat kuat, dan lxml adalah yang paling hemat memori di antara enam parser yang diukur. Tapi soal threading perlu diberi catatan. Data yang dipakai ulang menunjukkan speedup wall-clock 4 thread hanya 1.21x, ditandai tidak meyakinkan — tetapi itu jalur shared-default-parser. FAQ lxml menjelaskan dengan tegas bahwa GIL dilepas saat parsing hanya kalau tiap thread memakai parser sendiri (atau salinan default); parser yang dibagi justru bikin akses serial. Saya verifikasi struktur API untuk melakukan ini dengan benar (XMLParser.copy() ada, get/set_default_parser ada, XPathEvaluator membawa lock internal), tapi saya tidak mengukur speedup per-thread-parser — itu bakal jadi pengukuran timing baru, dan paket ini tidak menghasilkan itu. Jadi baca "1.21x" sebagai "pada jalur shared yang naif," bukan sebagai batas threading lxml.

Dan satu tanda bintang untuk semuanya: ini angka satu platform, macOS arm64. Klaim bahwa pure parse lxml mengalahkan Lexbor bertentangan dengan konsensus umum bahwa parser berbasis Lexbor adalah yang tercepat, jadi klaim itu memang layak dicek ulang di Linux x86_64 sebelum siapa pun menganggapnya final.

Lisensi: Kemenangan yang Membosankan

lxml dirilis di bawah BSD-3-Clause, dan pustaka C yang dibundelnya — libxml2 dan libxslt — keduanya MIT. Itu rantai lisensi yang sepenuhnya permisif tanpa copyleft di mana pun, dan itu penting begitu Anda mendistribusikannya. Sebagai pembanding, wheel selectolax membundel Modest berlisensi LGPL-2.1 dan Lexbor berlisensi Apache-2.0, jadi lxml punya cerita yang lebih bersih untuk dipakai di produk tertutup.

Ada juga keuntungan praktis saat instalasi: lxml menyediakan wheel prebuilt yang menautkan libxml2 dan libxslt secara statis, jadi pip install lxml biasanya tidak butuh libxml2 sistem dan tidak perlu compiler di mesin Anda — pengalaman yang beda dibanding membangunnya dari source.

Di Mana lxml Cocok — dan Di Mana Layer Ekstraksi AI Mengambil Alih

Saatnya menjelaskan batasnya dengan tegas, karena ini kategori kesalahan yang sangat gampang terjadi. lxml adalah library parsing. Ia memberi Anda tree dan mesin query yang sangat bagus, sementara semua yang ada di sekitar tree itu tetap urusan Anda: mengambil halaman, merender JavaScript, melewati anti-bot, menulis dan memelihara XPath, serta menstrukturkan hasil. Itu lapisan yang berbeda dari layanan ekstraksi hosted, dan keduanya bukan rival, lebih mirip tetangga.

Bagi developer yang tidak mau memelihara stack fetch-render-select-maintain, lapisan atas itu tempat sesuatu seperti Thunderbit hidup — dan untuk audiens ini, yang relevan adalah API, MCP server, dan CLI, bukan browser extension. Thunderbit Open API menyediakan POST /distill untuk mengubah halaman menjadi Markdown yang bersih dan POST /extract untuk menarik data terstruktur berdasarkan JSON Schema, dengan switch renderMode dan batch jobs untuk volume. Engine yang sama juga tersedia sebagai MCP server (thunderbit_suggest_fields, thunderbit_distill, thunderbit_extract) untuk agen dan coding assistant, serta sebagai CLI yang bisa dijalankan langsung dari terminal lewat npx @thunderbit/thunderbit-cli. Ia menangani rendering JS, anti-bot, dan CAPTCHA secara bawaan, lalu mengembalikan JSON yang cocok dengan schema — itulah lapisan di atas parsing, bukan penggantinya.

Coba Thunderbit untuk Ekstraksi Data Web

Kerangkanya sederhana. Pilih lxml saat Anda mengendalikan pipeline sendiri dan butuh kontrol XPath yang presisi atas tree yang Anda pahami. Pilih API ekstraksi AI saat Anda tidak mau repot memelihara selector dan rendering sama sekali. Banyak sistem nyata pakai keduanya — lxml untuk feed terstruktur yang mereka kendalikan, layanan ekstraksi untuk halaman long-tail yang berantakan dan tidak mereka kuasai.

Apa yang Tidak Diuji dalam Ulasan Ini

Ini ulasan sementara, bukan kartu skor final, jadi berikut hal-hal yang tidak tercakup.

Semua angka timing dan memori dipakai ulang, satu platform saja (macOS arm64, Python 3.14), dan mewarisi catatan kehati-hatian paket sebelumnya — hasil "lxml lebih cepat pada pure parsing" bertentangan dengan konsensus umum dan perlu dicek ulang di Linux x86_64. Speedup threading per-thread-parser tidak diuji (itu butuh timing baru). Saya mengukur memori iterparse pada 300 ribu record, tetapi tidak pada XML nyata skala GB, tidak pada iterparse HTML versus XML, dan tidak pada soak test berjam-jam. XSLT 1.0 milik lxml, validasi RelaxNG / XMLSchema / DTD, dan ekstensi EXSLT sama sekali tidak diuji di sini — ruang kemampuan yang besar, tapi di luar inti parsing dan selection. Saya mengamati plafon kedalaman kedua di 2.045, tapi tidak menetapkan konstanta rekursi libxml2 secara pasti. Hanya versi stabil 6.1.1 yang diuji, bukan alpha 7.0.0. Windows, source build, dan build free-threaded 3.14t juga belum diuji. Dan di dalam XPath sendiri, saya mencakup fungsi bawaan tetapi tidak variabel XPath, fungsi ekstensi Python kustom, atau reuse objek etree.XPath yang sudah diprekompilasi.

Putusan

lxml bukan barang baru yang paling cepat, dan justru itu alasan kenapa ia layak direkomendasikan. Ini binding libxml2 berusia dua dekade dengan mesin XPath 1.0 lengkap yang tidak tertandingi alternatif Python arus utama, tiga mode ketegasan parsing yang bisa diprediksi plus error log di tengah, parser streaming sungguhan untuk dokumen yang tidak muat di memori, penanganan multi-namespace dan encoding yang benar, serta lisensi yang sepenuhnya permisif. Beberapa sisi tajamnya — batas kedalaman sekitar 253 level dan angka threading pada shared parser — sudah terdokumentasi, bisa dikonfigurasi, dan sekarang juga sudah dijelaskan.

Kalau Anda memegang penuh pipeline scraping sendiri dan banyak pakai XPath, lxml masih parser yang sebaiknya Anda pilih. Kalau Anda tidak mau repot memelihara selector dan rendering, itu tugas layer ekstraksi AI seperti Thunderbit API, MCP, dan CLI — pembagian kerja yang rapi, bukan kompetisi. Bagaimanapun, anggap angka-angka ini masih sementara dan cek ulang timing di platform Anda sendiri sebelum mengutipnya di dokumen desain.

Coba Thunderbit untuk Ekstraksi Data Web Get Started Free

FAQ

Apakah lxml itu web scraper? Tidak. lxml adalah parser dan serializer — binding Python ke libxml2/libxslt yang mengubah markup menjadi tree yang bisa diedit dan di-query. Ia tidak mengambil halaman, tidak merender JavaScript, dan tidak menangani anti-bot; Anda yang menyediakan layer request-nya (melalui requests, httpx, headless browser, atau layanan scraping) lalu menyerahkan bytes ke lxml.

Kapan saya harus memakai lxml вместо BeautifulSoup atau selectolax? Pilih lxml saat Anda butuh XPath. BeautifulSoup memang bisa memakai lxml sebagai parser backend, tapi tidak menyediakan XPath native, dan selectolax hanya CSS serta lebih cepat di niche yang sempit. Kalau logika seleksi Anda perlu filter berdasarkan isi teks, navigasi parent atau ancestor, ekstraksi atribut/text node, atau predicate berbasis hitungan, mesin XPath milik lxml adalah satu-satunya opsi Python arus utama yang mengekspresikannya langsung.

Kenapa lxml diam-diam membuang konten yang sangat bertingkat? Parser default-nya membatasi nesting sekitar 253 level — pertahanan DoS libxml2 terhadap dokumen berbahaya, bukan bug. Set huge_tree=True (misalnya lxml.html.HTMLParser(huge_tree=True)) dan ia memulihkan kedalaman 300 dan 1.000 secara penuh. Perhatikan ada plafon rekursi kedua yang lebih keras di sekitar 2.045 level yang tidak dihapus oleh huge_tree.

Apakah lxml melepas GIL untuk parsing multithread? Hanya pada kondisi yang tepat. FAQ lxml menyatakan GIL dilepas saat parsing jika tiap thread memakai parser sendiri atau salinan default parser; parser yang dibagi justru membuat akses serial. Speedup 4-thread 1.21x yang dipakai ulang mencerminkan jalur shared-parser yang naif, bukan batas per-thread-parser, yang tidak diukur di sini.

Apakah lxml masih dirawat pada 2026? Ya. Rilis stabil 6.1.1 keluar pada 2026-05-18, repo terakhir di-push pada 2026-07-02, dan ada alpha 7.0.0 yang sedang berjalan. Dengan sekitar 3.000 bintang GitHub dan libxml2 yang aktif dirawat di bawahnya, ini tetap library yang modern dan didukung dengan baik, bukan sekadar warisan lama.

Ke
Ke
CTO di Thunderbit | Senior Data Scientist & Expert ML Dengan pengalaman hampir satu dekade di bidang machine learning dan data science, Ke Shen adalah lulusan Columbia University dan mantan Senior Data Scientist di Walmart Labs. Dengan keahlian mendalam yang diakui sejawat dalam Python, R, Java, dan Statistik, ia membagikan wawasan teruji lapangan tentang bagaimana membawa algoritma AI yang kompleks dari teori ke arsitektur siap produksi.
Topics
Web Scraping ToolsAI Web Scraper

Coba Thunderbit

Ambil lead & data lainnya hanya dalam 2 klik. Didukung AI.

Dapatkan Thunderbit Gratis
Ekstrak Data menggunakan AI
Dengan mudah transfer data ke Google Sheets, Airtable, atau Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week