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

Terakhir diperbarui pada August 12, 2026
lxml, Ditinjau: Mesin XPath yang Masih Mengalahkan Hampir Semua Parser Python
Ringkasan AI
Ulasan lxml ini memposisikan library tersebut sebagai binding Python lama untuk libxml2 dan libxslt, dengan satu keunggulan utama yang masih jarang ditandingi parser baru: engine XPath yang sesungguhnya. Artikel ini menguji cakupan XPath, mode ketat parser, perilaku memori pada streaming, perbedaan ekspresivitas CSS versus XPath, dan batas kedalaman libxml2. Hasilnya menunjukkan lxml cepat, hemat memori, dan sangat mampu untuk workload XML serta HTML yang membutuhkan axes, predicates, fungsi, streaming, atau mode recovery yang tangguh. Ulasan ini juga menjelaskan default kedalaman tree yang didorong oleh pertimbangan keamanan dan kapan huge_tree mengubah batas tersebut.

Setiap beberapa bulan, muncul parser HTML yang katanya lebih ngebut, hasil benchmark bertebaran, lalu ada saja yang bilang pemain lama sudah basi. Tapi begitu kamu harus memilih setiap paragraf yang mengandung kata tertentu, atau mengambil parent dari node yang cocok, kamu langsung ingat kenapa lxml masih tetap kebuka di tab lain.

lxml adalah binding libxml2 yang usianya sudah 20 tahun. Tidak heboh. Tidak baru. Tapi untuk satu tugas yang sangat spesifik — apa pun yang benar-benar butuh XPath — hampir tidak ada yang bisa ngalahin di ekosistem Python arus utama. Ini ulasan langsung tentang apa yang dikerjakannya, di mana ia diam-diam unggul, dan beberapa titik yang bisa menjebak kamu kalau tidak paham default-nya.

lxml dalam Satu Paragraf: Sebenarnya Apa Ini?

lxml adalah binding Python untuk pustaka C libxml2 dan libxslt. Ini parser sekaligus serializer, bukan scraper dan bukan browser — ia mengubah markup jadi tree yang bisa kamu query dan edit, lalu mengubah tree itu kembali menjadi bytes. lxml menyediakan API yang kompatibel dengan ElementTree, engine XPath 1.0 lengkap, XSLT 1.0, dan validasi schema, dikelola oleh Stefan Behnel dengan slogan "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 yang perlu diluruskan dulu sebelum ada yang mengira ini berlebihan: tidak ada rahasia dalam ulasan ini. lxml cukup lama sampai semua perilaku di sini sudah terdokumentasi di dokumentasi lxml, changelog libxml2, atau thread Launchpad. Saya tidak menemukan trik eksklusif yang tidak terdokumentasi, dan saya juga tidak akan mengarangnya. Nilai tulisan ini ada karena semuanya sudah disistematisasi, diukur, dan disusun dengan lxml sebagai subjek — bukan karena ini hal baru.

Setup Pengujian (dan Kenapa Angka Waktunya Dipinjam)

Ada dua kategori data dalam ulasan ini, dan keduanya berasal dari tempat berbeda, jadi saya jelaskan dari awal mana yang mana.

Capability tests — perilaku XPath, dua API parser, namespace, encoding, lifecycle node — saya jalankan ulang secara fresh 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 script yang benar-benar dijalankan, bukan diketik manual. Capability test bersifat deterministik berupa boolean dan enum, jadi satu kali run sudah stabil — beban mesin tidak mengubah apakah //a/@href mengembalikan string atribut.

Angka timing dan footprint memori bukan berasal dari paket ini. Angka-angka itu dipakai ulang apa adanya 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 bareng dengan kumpulan script capability akan mengundang kontensi CPU yang mengotori angka pakai ulang, dan itu juga kerja dobel: lxml sudah menjadi library kontrol yang diukur penuh di paket itu. Memakai benchmark yang sama bikin semuanya apples-to-apples, bukan memperkenalkan pengukuran kedua yang sedikit berbeda. Jadi saat kamu melihat angka milidetik di bawah, baca sebagai "test rig yang sama, per 2026-07-13," bukan "saya mengukur ulang hari ini."

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

XPath: Satu Hal yang Tidak Dimiliki selectolax dan BeautifulSoup

Ini inti masalahnya, jadi saya mulai dari sini.

lxml XPath coverage moat with axes predicates and functions

Saya menguji xpath() milik lxml pada matriks 37 item yang sudah dipra-registrasi — hasil yang diharapkan untuk tiap kasus ditulis ke source sebelum tes berjalan, jadi saya tidak bisa tanpa sengaja menilai dengan standar yang melunak. Sepuluh axes, sembilan gaya predicate, sepuluh fungsi bawaan, tiga tipe return skalar, dan lima kasus jebakan yang 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 itu yang paling penting. matches(), sequence expression, if/then/else, dan except semuanya adalah sintaks XPath 2.0, dan engine 1.0 milik libxml2 tidak menanganinya setengah-setengah — ia melempar XPathEvalError dan menolak, bukan diam-diam mengembalikan node set yang salah. Jadi ini skor sempurna setelah mencoba merusaknya, bukan skor sempurna karena pertanyaannya terlalu gampang. Semua perilaku di sini persis seperti yang dijelaskan di dokumen XPath lxml, dan memang itu poin utamanya.

Saya akui ada satu hal yang salah dari harness, dan ini versi "37/37" yang memang bisa dipercaya. Set ekspektasi awal saya untuk //div[.//a[@href]] memprediksi dua hasil; run-nya cuma memberi satu. Saya sempat mengira lxml salah selama kira-kira tiga puluh detik, lalu saya cek fixture dan ternyata elemen kedua itu <footer>, bukan <div> — ekspektasi saya yang salah, bukan engine-nya. Saya perbaiki set ekspektasi dan meninggalkan kesalahan itu di komentar source. Itulah urutan menyalahkan yang benar: curigai test kamu sendiri dulu sebelum menyalahkan library C berumur 20 tahun.

XPath vs CSS: Apa yang Secara Harfiah Tidak Bisa Dinyatakan oleh CSS

Klaim abstrak "XPath lebih kuat" pantas diberi angka konkret, jadi saya kuantifikasi selisihnya. lxml menyediakan .xpath() dan .cssselect() (yang menerjemahkan CSS ke XPath di balik layar). Saya ambil sepuluh target seleksi dan periksa mana yang benar-benar bisa diekspresikan 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 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, sibling yang bersebelahan) yang bekerja di keduanya. Itulah jawaban terukur untuk pertanyaan "apa yang sebenarnya saya dapat kalau memilih lxml." selectolax hanya CSS dan tidak punya metode xpath() sama sekali, jadi tujuh tipe kueri itu di sana berubah jadi loop Python bertahap atau memang tidak bisa dilakukan. Kalau logika scraping kamu bergantung pada salah satunya, keputusanmu sudah jelas.

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

Tiga Tingkat Ketat: 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

Kebanyakan parser memberi kamu satu perilaku untuk input yang rusak. lxml memberi kamu tiga, dan semuanya cukup bisa diprediksi sehingga saya alirkan enam kelas markup cacat lewat masing-masing mode dan mempra-registrasi bagaimana tiap jalurnya harus bereaksi.

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 cocok dengan ekspektasi yang sudah dipra-registrasi. lxml.etree melempar XMLSyntaxError untuk keenam kelas yang rusak. Tambahkan recover=True ke parser yang sama, lalu ia menelan error itu dan menyusun ulang tree yang masih bisa dipakai — dan ini bagian yang sering diremehkan — parser.error_log kemudian mencantumkan semua error yang ditelannya. lxml.html menerima semuanya tanpa komplain.

Klasifikator yang menentukan "raises vs recovers vs accepts" juga digerakkan oleh panjang error_log runtime, bukan hard-coded, makanya dokumen yang valid ketika dijalankan dengan recover=True tetap diberi label benar sebagai "accepts" (log kosong), bukan "recovers." Versi awal saya memberi label apa pun yang memakai recover=True sebagai "recovers" dan salah melabeli input bersih; membaca error_log yang sebenarnya memperbaiki itu.

Manfaat praktisnya: kalau kamu butuh validasi ketat dan feed yang rusak harus gagal dengan jelas, pakai lxml.etree. Kalau HTML dunia nyata kotor dan kamu cuma ingin lolos, pakai lxml.html. Dan untuk kasus tengah yang banyak tool lain tidak bisa lakukan — "bersikap longgar, tapi beri tahu saya tepatnya apa yang rusak supaya bisa saya log" — pakai recover=True dan baca error log. selectolax punya mode longgar dan tidak punya yang lain, tidak ada strict mode dan tidak ada error log.

iterparse: Mode Streaming yang Sama Sekali Tidak Dimiliki selectolax

Ini soal kemampuan, bukan sekadar knop kecepatan. selectolax hanya menerima satu string utuh — tidak ada antarmuka inkremental. iterparse milik lxml menghasilkan elemen saat tag-nya tertutup, dan kalau dipadukan dengan pola fast_iter klasik (panggil elem.clear() dan hapus sibling sebelumnya saat berjalan) memori tetap datar berapa pun ukuran dokumennya.

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

Saya mengukur karakteristik memorinya secara langsung — peak RSS via ru_maxrss, tiap subjek di proses baru yang bersih, pada 300.000 elemen <record> dengan total sekitar 26,7 MB (26.744.801 bytes).

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 sekitar 1-2 MB dibanding full load ~386 MB — selisih skala 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 informatif adalah yang tengah. Jalankan loop iterparse yang sama tapi lewati clear(), dan memori langsung naik lagi ke ~386 MB, karena kamu menahan referensi ke semuanya. Kemenangan ada di clear(), bukan di iterparse itu sendiri. Anchor full-load yang jauh lebih tinggi daripada mode terbatas juga menegaskan bahwa meter RSS memang bisa melihat perbedaan skala, bukan membaca buta. (Tes memori ini saya jalankan di paket ini — ini pengukuran footprint, berbeda 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 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. Semua dua belas lolos.

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

Ada satu jebakan terdokumentasi yang layak diingat. XPath tidak mengenal default namespace. Arahkan //book ke dokumen yang mendeklarasikan xmlns="urn:..." dan kamu akan dapat nol hasil — prefix kosong memang tidak terdefinisi dalam XPath, seperti dijelaskan di dokumen lxml. Kamu harus mengikat prefix buatan (//c:book dengan namespaces={"c": "urn:..."}, yang menemukan semua tiga), atau memakai //*[local-name()='book'] (juga tiga). Ini bukan bug — ini spesifikasi XPath yang diimplementasikan dengan setia. Cuma saja, hampir semua orang kaget saat pertama kali ketemu.

Halaman Nyata yang Kotor: Fidelity pada 11 Scrape Aktual

Tes sintetis itu bersih; web tidak. Saya pakai 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 berhasil diparse dengan lxml.html, dan jumlah link, heading, serta image cocok dengan hitungan lxml yang dipakai ulang dari paket selectolax di semua sebelas halaman — cross-check true. Kecocokan itu memberi tahu saya bahwa penggunaan ulang ini benar-benar apples-to-apples, bukan dua pengukuran berbeda yang kebetulan pakai label sama.

Temuan sampingannya: parser XML ketat melempar pada sepuluh dari sebelas halaman. Halaman web nyata umumnya bukan XML yang well-formed, dan itulah alasan mode HTML recovery milik libxml2 ada, untuk menelannya. Satu pengecualian adalah BBC News, dirender oleh Next.js dan cukup well-formed untuk lolos parsing XML ketat. Tidak semua yang berlabel "HTML" perlu mode recovery.

Ada satu catatan hitungan yang gampang menjebak. Pada docs_python.html, //a[@href] (attribute-existence) menghasilkan 343, sedangkan paket selectolax dengan if n.get("href") (nilai truthy) menghasilkan 341. Dua ekstra itu adalah link dengan href="" yang kosong. Itu perbedaan konvensi hitung — atribut ada versus atribut tidak kosong — bukan perbedaan perilaku lxml, dan angkanya akan cocok begitu predikatnya disamakan. Penting diketahui saat scraping: apakah href kosong dihitung atau tidak adalah pilihan filter kamu, bukan parser.

Batas Kedalaman yang Terlihat Seperti Bug (Padahal Bukan)

Paket selectolax sebelumnya mencatat lxml membuang konten terdalam pada markup <div> bertingkat 1.000 dan 5.000 level, lalu menyebutnya sebagai "lxml diam-diam kehilangan konten terdalam." Saya ingin tahu mekanismenya, jadi saya jalankan parser default versus 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 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 berbahaya merusak stack, dan ini didokumentasikan dalam thread Launchpad lxml tentang XML_PARSE_HUGE. Set huge_tree=True dan kedalaman 300 serta 1.000 kembali utuh. Namun pada kedalaman 5.000, meski huge_tree aktif, hanya sampai 2.045 — ada ceiling rekursi kedua yang lebih keras di atas batas yang bisa dikonfigurasi, dan huge_tree tidak menghapusnya.

Jadi tindak lanjutnya konkret: saat memparse markup dalam dari sumber yang kamu percaya, pakai lxml.html.HTMLParser(huge_tree=True). Tambahan yang diberikan paket ini di atas observasi yang dipakai ulang adalah mekanismenya (batas keamanan, bukan korupsi data), perbaikannya (huge_tree), dan fakta bahwa ada ceiling kedua yang tidak tersentuh perbaikan itu.

Read/Write DOM, Serialisasi, Encoding

lxml adalah tree read/write penuh, bukan extractor read-only, dan saya verifikasi permukaan editnya satu per satu. Semua delapan operasi DOM lolos: SubElement, insert, remove, replace, strip_tags (membuang tag tapi mempertahankan teksnya), strip_elements (membuang tag dan teksnya), drop_tree (eksklusif lxml.html), serta model slot ganda text/tail yang sering menjebak pemula — pada <p>head<b>bold</b>tail</p>, p.text adalah "head", b.text adalah "bold", dan b.tail adalah "tail".

Serialisasi lolos lima dari lima: tostring dalam mode XML dan HTML (HTML dengan benar tidak menutup void elements sebagai self-close), pretty_print, canonicalization C14N (method="c14n", satu lagi eksklusif lxml), dan round-trip yang bersih.

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

Sisi sebaliknya adalah ketat soal bagaimana kamu mendeklarasikan encoding. encoding="latin-1" di deklarasi XML akan melempar XMLSyntaxError: Unsupported encoding: latin-1, sedangkan nama kanonik IANA encoding="ISO-8859-1" diparse dengan baik dan mengembalikan café. libxml2 hanya menerima nama encoding kanonik, bukan alias — detail yang sudah didokumentasikan sejak launchpad #613302. Menyebalkan kalau belum tahu, sepele begitu sudah paham.

Terakhir, lifecycle node. Saya menjalankan tiga skenario stale-handle dalam subprocess terisolasi (crash keras akan terlihat sebagai exit non-zero): menahan node setelah tree-nya di-garbage-collect, membaca handle setelah drop_tree(), dan memakai node setelah remove(). Tidak ada segfault di mana pun — lxml menjaga referensi node ke tree-nya tetap hidup agar mencegah use-after-free. Hasilnya sama bersihnya seperti yang didapat selectolax pada tes ini.

Kecepatan dan Memori (Dipinjam, dan Jujur Soal Itu)

Semua di bagian ini dipakai ulang dari paket selectolax, per 2026-07-13. Paket ini tidak menghasilkan angka timing sendiri, dan saya lebih suka bilang itu dua kali daripada bikin kamu 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-nya kuat, dan lxml adalah yang paling hemat memori dari enam parser yang diukur. Tapi gambaran threading perlu catatan. Data yang dipakai ulang menunjukkan percepatan wall-clock 4-thread hanya 1,21x, ditandai sebagai tidak konklusif — tetapi itu jalur shared-default-parser. FAQ lxml menyatakan dengan jelas bahwa GIL dilepas saat parsing hanya jika tiap thread memakai parser sendiri (atau default yang disalin); parser yang dibagikan akan menserialisasi akses. Saya sudah verifikasi struktur API untuk melakukan ini dengan benar (XMLParser.copy() ada, get/set_default_parser ada, XPathEvaluator membawa lock internal), tetapi saya tidak mengukur speedup per-thread-parser — itu akan 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 catatan kaki untuk semuanya: ini angka lintas satu platform, macOS arm64. Klaim bahwa parse murni 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 dianggap final.

Lisensi: Kemenangan yang Membosankan

lxml dirilis dengan BSD-3-Clause, dan pustaka C yang dibundel di dalamnya — libxml2 dan libxslt — keduanya berlisensi MIT. Ini rantai permissive penuh tanpa copyleft di mana pun, dan itu penting begitu kamu melakukan redistribusi. Sebagai pembanding, wheel selectolax membundel Modest berlisensi LGPL-2.1 dan Lexbor berlisensi Apache-2.0, jadi lxml lebih bersih untuk dipakai dalam produk tertutup.

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

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

Sekarang waktunya memperjelas batasnya, karena ini gampang banget ketuker. lxml adalah library parsing. Ia memberi kamu tree dan query engine yang sangat bagus, dan semua yang ada di sekitar tree itu tetap jadi urusanmu: mengambil halaman, merender JavaScript, lolos dari anti-bot, menulis dan memelihara XPath, serta menyusun hasilnya. Itu lapisan yang berbeda dari layanan ekstraksi terkelola, dan keduanya bukan lawan, melainkan tetangga.

Buat developer yang tidak mau ngurus tumpukan fetch-render-select-maintain, lapisan atas itu adalah tempat sesuatu seperti Thunderbit berada — dan untuk audiens ini yang relevan adalah API, MCP server, dan CLI, bukan ekstensi browser. Thunderbit Open API menyediakan POST /distill untuk mengubah halaman jadi Markdown bersih dan POST /extract untuk mengambil data terstruktur berdasarkan JSON Schema, dengan switch renderMode dan job batch untuk volume. Engine yang sama juga tersedia sebagai MCP server (thunderbit_suggest_fields, thunderbit_distill, thunderbit_extract) untuk agent 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 dan mengembalikan JSON yang cocok dengan schema — itulah lapisan di atas parsing, bukan penggantinya.

Coba Thunderbit untuk Ekstraksi Data Web

Kerangkanya sederhana. Pilih lxml saat kamu pegang sendiri pipeline dan butuh kontrol XPath yang presisi atas tree yang kamu pahami. Pilih API ekstraksi AI saat kamu tidak mau memelihara selector dan rendering sama sekali. Banyak sistem nyata memakai keduanya — lxml untuk feed terstruktur yang mereka kontrol, layanan ekstraksi untuk halaman panjang 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 belum tercakup.

Semua angka timing dan memori dipakai ulang, lintas satu platform saja (macOS arm64, Python 3.14), dan mewarisi catatan kehati-hatian dari paket itu — hasil "lxml lebih cepat pada pure parsing" bertentangan dengan konsensus umum dan perlu dicek ulang di Linux x86_64. Speedup threading per-thread-parser belum diuji (akan butuh timing baru). Saya mengukur memori iterparse pada 300k record, tetapi tidak pada XML dunia 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 belum diuji di sini — permukaan kemampuan besar, tapi di luar inti parsing-dan-selection. Saya mengamati ceiling depth kedua pada 2.045, tetapi tidak memetakan konstanta rekursi libxml2 yang tepat. Hanya versi stabil 6.1.1 yang diuji, bukan alpha 7.0.0. Windows, build dari source, 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 penggunaan ulang objek etree.XPath yang sudah dikompilasi.

Putusan Akhir

lxml bukan hal baru yang cepat, dan justru itu yang jadi poinnya. Ini binding libxml2 berumur dua dekade dengan engine XPath 1.0 lengkap yang tidak tertandingi oleh alternatif Python arus utama, tiga mode strictness parsing yang bisa diprediksi dengan 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 permissive. Beberapa sisi tajamnya — batas kedalaman sekitar 253 level dan angka threading pada shared parser — terdokumentasi, bisa dikonfigurasi, dan sekarang sudah dijelaskan.

Kalau kamu memegang sendiri pipeline scraping dan bergantung pada XPath, lxml masih parser yang sangat layak dipilih. Kalau kamu lebih suka tidak memelihara selector dan rendering, itulah tujuan layer ekstraksi AI seperti Thunderbit API, MCP, dan CLI — pembagian kerja yang rapi, bukan kompetisi. Apa pun pilihannya, perlakukan angka-angka ini sebagai sementara dan cek ulang timing di platformmu sendiri sebelum kamu 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 untuk 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; kamu yang menyediakan lapisan request (via requests, httpx, headless browser, atau layanan scraping) lalu menyerahkan bytes-nya ke lxml.

Kapan saya harus memakai lxml dibanding BeautifulSoup atau selectolax? Pilih lxml saat kamu butuh XPath. BeautifulSoup memang bisa memakai lxml sebagai parser backend, tapi tidak menyediakan XPath native, dan selectolax hanya CSS serta lebih cepat di ceruk yang sempit. Kalau logika seleksi kamu butuh filter isi teks, navigasi parent atau ancestor, ekstraksi atribut/text-node, atau predicate berbasis hitungan, engine XPath milik lxml adalah satu-satunya opsi Python arus utama yang mengekspresikannya secara langsung.

Kenapa lxml diam-diam membuang konten yang sangat dalam nested-nya? 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 kedalaman 300 serta 1.000 akan dipulihkan sepenuhnya. Ingat ada ceiling rekursi kedua yang lebih keras sekitar 2.045 level yang tidak dihapus oleh huge_tree.

Apakah lxml melepas GIL untuk parsing multi-thread? Hanya pada kondisi yang tepat. FAQ lxml menyatakan GIL dilepas saat parsing jika tiap thread memakai parser sendiri atau default parser yang disalin; parser yang dibagikan justru menserialisasi akses. Speedup 4-thread 1,21x yang dipakai ulang di sini mencerminkan jalur shared-parser yang naif, bukan batas per-thread-parser, yang tidak diukur di sini.

Apakah lxml masih dipelihara pada 2026? Ya. Rilis stabil 6.1.1 keluar pada 2026-05-18, repositori terakhir di-push pada 2026-07-02, dan ada alpha 7.0.0 yang sedang berjalan. Dengan sekitar 3.000 GitHub stars dan libxml2 yang aktif dipelihara di bawahnya, lxml tetap library yang mutakhir dan didukung dengan baik, bukan sekadar peninggalan 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
Daftar Isi
Thunderbit · Agen data web AI

Ekstrak data dari halaman apa pun dalam 1 klik

Dipercaya oleh 250.000+ pengguna
tersedia paket gratis
Dari halaman web ke spreadsheet
Jelaskan apa yang kamu butuhkan — AI Agent Thunderbit akan men-scrape dan mengekspornya ke Excel, Google Sheets, Airtable, atau Notion. Gratis untuk memulai.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week