lxml İncelemesi: Hâlâ Her Python Parser’ı Geride Bırakan XPath Motoru

Son güncelleme: July 17, 2026
lxml İncelemesi: Hâlâ Her Python Parser’ı Geride Bırakan XPath Motoru
AI Özeti
Bu lxml incelemesi, kütüphaneyi libxml2 ve libxslt etrafındaki uzun soluklu Python binding’i olarak konumlandırıyor ve yeni parser’ların hâlâ nadiren yakalayabildiği bir temel avantajı öne çıkarıyor: gerçek bir XPath motoru. Yazı; XPath kapsamını, parser sıkılık modlarını, streaming bellek davranışını, CSS ile XPath arasındaki ifade gücü farkını ve libxml2’nin derinlik sınırlarını test ediyor. lxml’nin XML ve HTML işlerinde eksenler, predicate’ler, fonksiyonlar, streaming ve sağlam kurtarma modları gerektiğinde hızlı, bellek açısından verimli ve alışılmadık derecede yetenekli olduğunu gösteriyor. İnceleme ayrıca güvenlik odaklı ağaç derinliği varsayılanlarını ve huge_tree’nin bu sınırı ne zaman değiştirdiğini açıklıyor.

Her birkaç ayda bir daha hızlı bir HTML parser ortaya çıkar, benchmark sonuçları ortalığa saçılır ve biri eski neslin artık tarihe karıştığını ilan eder. Sonra gidip belirli bir kelimeyi içeren her paragrafı seçmeniz ya da eşleşen bir düğümün ebeveynini almanız gerekir; işte tam o anda lxml neden hâlâ başka sekmede açık durduğunu hatırlarsınız.

lxml, 20 yıllık bir libxml2 binding’idir. Gösterişli değildir. Yeni değildir. Ama tek bir iş için — gerçek XPath gerektiren her şey için — ana akım Python dünyasında gerçekten rakipsizdir. Bu yazı, lxml’in pratik bir incelemesi: ne yaptığı, sessizce nerelerde öne çıktığı ve varsayılan ayarlarını bilmezseniz sizi nerelerde köşeye sıkıştırabileceği.

lxml Tek Paragrafta: Aslında Nedir?

lxml, Python’un libxml2 ve libxslt C kütüphanelerine bağlanan katmanıdır. Bir parser ve serializer’dır; bir scraper değildir, bir tarayıcı da değildir — markup’ı sorgulanıp düzenlenebilen bir ağaca çevirir, sonra bu ağacı yeniden baytlara dönüştürür. Size ElementTree uyumlu bir API, eksiksiz bir XPath 1.0 motoru, XSLT 1.0 ve şema doğrulama sunar. Stefan Behnel tarafından, "Python dilinde XML ve HTML işlemek için en zengin özellik setine sahip ve kullanımı en kolay kütüphane" sloganıyla geliştirilmektedir.

14.07.2026 tarihinde alınan GitHub ve PyPI özetine göre durum şu şekilde:

AlanDeğer
Repolxml/lxml
Yıldız3.043
Fork620
Açık sorun16
LisansBSD-3-Clause
Oluşturulma2011-02-11
Son push2026-07-02
PyPI kararlı sürüm6.1.1 (2026-05-18)
Paketlenen motorlibxml2 2.14.6 + libxslt 1.1.43

Bu inceleme hakkında kimse "abartı yapılıyor" demesin diye bir şeyi baştan netleştireyim: burada gizli bir numara yok. lxml o kadar eski ki, burada gördüğünüz her davranışın bir karşılığı ya lxml belgelerinde, ya libxml2 değişiklik günlüğünde ya da bir launchpad başlığında bulunur. Belgelenmemiş, özel bir hile bulmadım ve uydurmayacağım da. Buradaki asıl değer, bunları sistemli, sayısal ve lxml etrafında düzenlenmiş şekilde sunmak; haber değeri taşımaları değil.

Test Ortamı (ve Neden Süre Ölçümleri Ödünç)

Bu incelemeye iki farklı veri grubu giriyor ve bunlar iki ayrı yerden geliyor; hangisinin nereden geldiğini açık açık söyleyeyim.

Yetenek testleri — XPath davranışı, iki parser API’si, namespace’ler, encoding, düğüm yaşam döngüsü — tek bir makinede yeniden çalıştırıldı: macOS arm64, Python 3.14.2, lxml 6.1.1, libxml2 2.14.6. artifacts/raw/*.json dosyalarındaki her sayı, elle yazılmış değil script çıktısıdır. Yetenek testleri deterministik boolean’lar ve enum’lar olduğu için tek bir koşum yeterince stabildir — makine yükü //a/@href ifadesinin bir attribute string’i döndürüp döndürmemesini değiştirmez.

Süre ve bellek tüketimi rakamları bu paketten değil. Bunlar, önceki selectolax benchmark paketinden aynen yeniden kullanıldı — aynı makine, aynı sanal ortam, aynı lxml ve libxml2 derlemesi, 2026-07-13 itibarıyla benchmark’lar — ve burada yeniden çalıştırılmadılar. Bu bilerek yapılan bir tercih. Zamanlama benchmark’larını bir grup yetenek script’inin yanında yeniden çalıştırmak CPU çakışması yaratır ve ödünç alınan rakamları kirletirdi; ayrıca gereksiz bir tekrar olurdu: lxml o pakette zaten tamamen ölçülmüş bir kontrol kütüphanesiydi. Aynı ölçümü yeniden kullanmak, işi elma ile elma karşılaştırmasına dönüştürür; aksi halde ikinci, hafifçe farklı bir ölçüm katmanı eklenmiş olur. Dolayısıyla aşağıda bir milisaniye değeri gördüğünüzde bunu "aynı test düzeneği, 2026-07-13 itibarıyla" diye okuyun; "bunu bugün yeniden ölçtüm" diye değil.

Sonuçlara güven etiketi eklenmiştir: Deterministik yetenek testleri için single-observation, yeniden kullanılan zamanlama dağılımları için triple-run, mekanizmasını doğrudan izole etmediğim çıkarımlar için hypothesis.

XPath: selectolax ve BeautifulSoup’un Sadece Sahip Olmadığı Şey

Başlık bu olduğu için önce buradan başlayalım.

lxml XPath coverage moat with axes predicates and functions

lxml’in xpath() metodunu önceden kayıt altına alınmış 37 maddelik bir matrise tabi tuttum — her durum için beklenen sonuç test çalışmadan önce kaynak koduna yazılmıştı, böylece sonuca göre puan kırıp artıramazdım. On eksen, dokuz predicate stili, on yerleşik fonksiyon, üç skaler dönüş tipi ve XPath 2.0’a özgü sözdizimi kullanan beş kasıtlı tuzak vaka; lxml’in 1.0 motorunun bunları reddetmesi gerekiyordu.

KategoriKapsamSonuç
Eksenlerchild / descendant / parent / ancestor / following-sibling / preceding-sibling / self / attribute / following / preceding10/10 geçti
Predicates[1] / last() / position()<n / attribute eşitliği / attribute varlığı / and / or / iç içe [.//a] / not()9/9 geçti
Fonksiyonlartext() / contains() / starts-with() / count() / string-length() / normalize-space() / concat() / substring() / name() / string()10/10 geçti
Dönüş tipleriboolean / number skalerleri3/3 geçti
Tuzak vakalarmatches() / sequences / if-then-else / except / sözdizimi hatası5/5 doğru biçimde reddedildi

Skor 37/37 ve asıl önemli olan da tuzak sütunu. matches(), sequence ifadeleri, if/then/else ve except hepsi XPath 2.0 sözdizimidir; libxml2’nin 1.0 motoru bunları yarım yamalak desteklemez — sessizce yanlış node set’i döndürmek yerine XPathEvalError fırlatır ve reddeder. Yani burada mükemmel skor, önce kırmaya çalıştıktan sonra elde edilmiş bir skor; yumuşak sorularla şişirilmiş bir sonuç değil. Buradaki her davranış, lxml XPath belgelerinin anlattığı şeyle birebir örtüşüyor; esas amaç da bu.

Harness’in bir yerde hata yaptığını da dürüstçe söyleyeyim; ama güvenebileceğiniz türden bir "37/37" için önemli olan da bu. //div[.//a[@href]] için ilk beklenen küme iki eşleşme öngörüyordu; koşum bir tane döndürdü. İlk başta yaklaşık otuz saniye lxml’i suçladım, sonra fixture’a baktım ve ikinci elemanın <div> değil <footer> olduğunu gördüm — hatalı olan motor değil, benim beklentimdi. Beklenen kümeyi düzelttim ve hatayı kaynak kod yorumunda bıraktım. Suçlama sırası doğrudur: 20 yıllık C kütüphanesinden önce kendi testinizi şüpheli bulun.

XPath vs CSS: CSS ile Kelimenin Tam Anlamıyla İfade Edemeyecekleriniz

"XPath daha güçlüdür" cümlesi biraz soyut kalır; bu yüzden farkı sayısallaştırdım. lxml size hem .xpath() hem .cssselect() verir; ikincisi CSS’i altta XPath’e çevirir. On seçim hedefi aldım ve hangilerinin CSS ile gerçekten ifade edilebildiğini kontrol ettim.

XPath expresses seven of ten tasks CSS cannot express

HedefXPathCSS (cssselect)
Metin içeriğine göre filtrele (contains(text(),"bargain"))EvetMetin predicate’i yok
Çocuktan ebeveyni seç (//b/parent::p)EvetParent selector yok
Attribute değeri döndür (//a/@href)EvetSadece element
Text node döndür (//p/text())EvetText node yok
Ancestor ekseni (//td/ancestor::div)EvetYukarı yönlü gezinme yok
Çocuk sayısına göre ebeveyni filtrele (//ul[count(li)=4])EvetCount predicate’i yok
Metin uzunluğuna göre filtrele (string-length(text())>5)EvetUzunluk predicate’i yok
nth-child / last-child / bitişik siblingEvetEvet (3 temel durum)

On hedefin yedisinin CSS karşılığı hiç yok. Metin içeriğine göre filtreleme, ebeveyn ve ata düğümlere yukarı doğru gitme, bir attribute değerini ya da sade bir text node’u sonuç olarak alma, sayım tabanlı predicate’ler — CSS bunların hiçbirini söyleyemez. Sadece üçü (nth-child, last-child, bitişik sibling) her iki tarafta da çalışıyor. "lxml’e neden geçeyim?" sorusunun sayısallaştırılmış cevabı bu. selectolax yalnızca CSS destekler ve hiç xpath() metodu yoktur; bu yüzden bu yedi sorgu türü orada ancak çok adımlı Python döngülerine dönüşür ya da hiç yapılamaz. Eğer scraping mantığınız bunlardan herhangi birine dayanıyorsa, kararınız çoktan verilmiş demektir.

(Evet, harness burada da beni ikinci kez yakaladı: string-length(text())>5 için boş küme beklemiştim, ama iki adet altı karakterli string eşleşti. Beklentiyi düzelttim, aracı değil.)

Üç Sertlik Dişlisi: etree, recover ve lxml.html

XPath, lxml’i seçmek için sebeptir. Üç kademeli sıkılık kontrolü ise onu kullanmaya devam etmenizin sebebidir.

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

Çoğu parser bozuk girdi için tek bir davranış sunar. lxml ise üç davranış sunar ve bunlar o kadar öngörülebilirdir ki altı farklı bozuk markup sınıfını her birinden geçirip her yolun nasıl davranacağını önceden kayıt altına aldım.

Bozuk girdilxml.etree (katı)etree + recover=Truelxml.html (esnek)
Kapanmamış etiket <root><a>x</root>hata verirtoparlarkabul eder
Yanlış iç içe geçmiş <b><i></b></i>hata verirtoparlarkabul eder
Tanımsız entity &nbsp;hata verirtoparlarkabul eder
Düz & (Tom & Jerry)hata verirtoparlarkabul eder
Birden çok root <a>1</a><b>2</b>hata verirtoparlarkabul eder
İyi biçimlenmiş XMLkabul ederkabul eder (0 hata)kabul eder
Boolean attribute <input disabled>hata verirtoparlarkabul eder

Yedi vakadan yedisi önceden kayıtlı beklentiyle eşleşti. lxml.etree, altı bozuk sınıfın hepsinde XMLSyntaxError fırlatır. Aynı parser’a recover=True eklediğinizde hataları yutar ve kullanılabilir bir ağaç yeniden oluşturur — ve çoğu zaman gözden kaçan kısım budur — parser.error_log yuttuğu her hatayı listeler. lxml.html ise her şeyi sorgusuz sualsiz kabul eder.

"Hata verdi / toparladı / kabul eder" kararını veren sınıflandırıcı da sabit kodlu değil, çalışma anındaki error_log uzunluğuna göre çalışıyor; bu yüzden recover=True ile çalışan iyi biçimlenmiş bir belge doğru biçimde "kabul eder" diye etiketleniyor (boş günlük), yanlışlıkla "toparlar" diye değil. İlk sürümümde recover=True olan her sonucu "toparlar" diye etiketliyordum ve temiz girdiyi yanlış sınıflandırıyordum; gerçek error_log’u okumak bunu düzeltti.

Pratikte bunun anlamı şudur: bozuk bir feed’in açıkça başarısız olması gerekiyorsa katı doğrulama için lxml.etree kullanın. Elinizdeki kirli gerçek dünya HTML’ini sadece içinden geçirmeniz gerekiyorsa lxml.html kullanın. Çoğu aracın yapamadığı orta yol için — "hoşgörülü ol, ama tam olarak neyin bozuk olduğunu da söyle ki loglayabileyim" — recover=True kullanın ve hata günlüğünü okuyun. selectolax’ın esnek modu vardır; başka da bir şey yoktur, katı mod ve hata günlüğü bulunmaz.

iterparse: selectolax’ta Hiç Olmayan Akış Modu

Bu bir hız ayarı değil, bir yetenek satırıdır. selectolax yalnızca tüm string’i bir kerede tüketir — artımlı bir arayüzü yoktur. lxml’nin iterparse yöntemi, elemanlar kapanırken onları size verir; klasik fast_iter deseniyle birlikte (ilerledikçe elem.clear() çağırıp önceki kardeşleri silmek) belge ne kadar büyük olursa olsun belleği sabit tutar.

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

Bellek karakteristiğini doğrudan ölçtüm — ru_maxrss üzerinden tepe RSS, her konu için taze süreçte, toplam yaklaşık 15 MB eden 300.000 <record> öğesi üzerinde.

ModTepe RSS artışıNot
iterparse + clear (fast_iter)~1-2 MBilerledikçe boşaltır; sayıdan bağımsız olarak düz kalır
iterparse clear olmadan~386 MBreferansları tutar; tam yük kadar ağır
etree.parse (tam yük, referans)~386 MBağırlığın boyutunu meterın okuyabildiğini kanıtlar

Sınırlandırılmış mod, tam yükün ~386 MB’lık artışına karşı tepe RSS artışını yaklaşık 1-2 MB civarında tutuyor — büyüklük olarak %0,3-0,4 fark — ve ilk record olayı dosya henüz tamamen okunmadan önce geliyor; yani gerçek anlamda artımlı, sahte akış değil. Öğretici olan orta satır. Aynı iterparse döngüsünü çalıştırıp clear() çağrısını atlarsanız, bellek yeniden ~386 MB’ye tırmanıyor; çünkü her şeye referans tutuyorsunuz. Kazanç iterparse’ın kendisinde değil, clear() çağrısında yatıyor. Tam yük referansının sınırlandırılmış moda göre çok daha yukarıda olması da RSS ölçerinin büyüklük farkını gerçekten okuyabildiğini gösteriyor; yoksa körü körüne aynı sayıyı verirdi. (Bu bellek testi bu pakette benim doğrudan çalıştırdığım bir testtir; ödünç alınmış zamanlama rakamlarından ayrı bir footprint ölçümüdür.)

Gerçek dünyadaki karşılığı şu: RAM’e sığmayan çok gigabaytlık bir XML dışa aktarımı için selectolax tarafında hiçbir yol yoktur. Ya lxml’in streaming parser’ı vardır ya da başka bir dile geçersiniz.

Namespace’ler: RSS, SVG ve Default Namespace Tuzağı

Üç namespace boyunca RSS, default namespace + xlink içeren SVG ve default-namespace XML’i kapsayan on iki namespace vakası. On ikisi de geçti.

lxml, bir RSS akışından //dc:creator/text() ifadesiyle tam olarak ["Alice", "Bob"] değerlerini çeker, aynı belgede üç ayrı namespace üzerinden //atom:link/@href ve //content:encoded çözer, SVG’nin ikinci namespace’inde //s:rect ve //s:use/@xlink:href ile çalışır, Clark gösterimindeki {uri}local adlarını QName ile ayırır ve nsmap ile içeriğini döker. Bu, belgelenmiş ve bakımı süren davranıştır; ayrıca selectolax’ın dokunmadığı bambaşka bir boyuttur, çünkü selectolax HTML5 odaklıdır ve keyfi XML namespace’lerini işlemez.

Akılda tutulması gereken bir belgeli tuzak vardır. XPath’in default namespace kavramı yoktur. xmlns="urn:..." tanımlayan bir belgeye //book ile giderseniz sonuç sıfır olur — lxml belgelerinde açıkça anlatıldığı gibi, XPath için boş prefix tanımsızdır. Yapay bir prefix bağlamanız gerekir (namespaces={"c": "urn:..."} ile //c:book, bu örnekte üç eşleşme buldu) ya da //*[local-name()='book'] yoluna gitmeniz gerekir (o da üç sonuç verdi). Bu bir bug değil; XPath standardının birebir uygulanmasıdır. Yalnızca herkesi tam bir kez şaşırtır.

Gerçek Kirli Sayfalar: 11 Canlı Scrape Üzerinde Fidelity

Sentetik testler temizdir; web değildir. selectolax paketindeki fixture setinden alınan on bir gerçek sayfayı (2026-07-10 itibarıyla, salt okunur) yeniden kullandım ve lxml’yi konu olarak alıp lxml.html üzerinden çalıştırdım.

FixtureBoyutLinklibxml2’nin toparladığı hataStrict XML
news_bbc.html398 KB2554ok
docs_mdn_array.html243 KB5080hata verdi
wiki_scraping.html227 KB4600hata verdi
gov_whitehouse.html289 KB1540hata verdi
oldstyle_craigslist.html561 KB3510hata verdi
forum_reddit.html129 KB3180hata verdi
docs_python.html80 KB3412hata verdi
ecommerce_books.html51 KB940hata verdi
news_hackernews.html35 KB2290hata verdi
ecommerce_webscraper_allinone.html16 KB350hata verdi
spa_quotes_js.html6 KB50hata verdi

On birinin de lxml.html ile ayrıştırılması başarılı oldu ve bağlantı, başlık ve görsel sayıları, selectolax paketindeki yeniden kullanılan lxml sayılarıyla on birinin tamamında eşleşti — çapraz kontrol true. Bu uyum, yeniden kullanımın gerçekten elma ile elma karşılaştırması olduğunu; aynı etiketi taşıyan iki farklı ölçüm olmadığını gösteriyor.

Yan sonuç ise şu: katı XML parser, on bir sayfanın onunda hata verdi. Gerçek web sayfalarının ezici çoğunluğu iyi biçimlenmiş XML değildir; libxml2’nin HTML kurtarma modunun varlık nedeni de tam olarak budur. Tek istisna BBC News’tü; Next.js ile render edilmişti ve katı XML ayrıştırmayı geçecek kadar iyi biçimlenmişti. "HTML" etiketi taşıyan her şey kurtarma moduna ihtiyaç duymaz.

Kaçırılması kolay bir sayım notu da var. docs_python.html üzerinde //a[@href] (attribute varlığı) 343 sonuç sayarken, selectolax paketindeki if n.get("href") (truthy değer) sayımı 341 idi. Fazladan iki kayıt boş href="" bağlantılarıdır. Bu bir parser davranış farkı değil, bir sayım kuralı farkıdır — attribute’un var olması ile empty olmaması aynı şey değildir — ve predicate’i hizaladığınızda sayılar yeniden uzlaşır. Scrape ederken bilmeye değer: boş href’lerin sayılıp sayılmaması parser’ın değil, filtre seçiminizin meselesidir.

Hata Gibi Görünen Derinlik Sınırı (Ama Değil)

selectolax paketinde, lxml’nin 1.000 ve 5.000 seviyelik iç içe <div> markup’larında en derin içeriği düşürdüğü ve bunu "lxml sessizce en derin içeriği kaybediyor" diye çerçevelediği not edilmişti. Mekanizmayı anlamak için default parser’ı huge_tree=True ile karşılaştırdım.

lxml default depth guard around 253 levels and huge_tree to 2045

İstenen derinlikDefault parser’ın ulaştığıhuge_tree=True ile ulaşılan
300253 (kalanı düşer)299 (kurtarıldı)
1000253 (kalanı düşer)999 (kurtarıldı)
5000253 (kalanı düşer)2045 (yine düşer)

Default parser yaklaşık 253 seviyede keser ve daha derini sessizce atar. Bu bir bug değil — libxml2’nin DoS savunmasıdır; kötü niyetli bir belgenin stack’i çökertmesini önleyen yaklaşık 256 seviyelik bir iç içe geçme sınırı vardır ve bu durum lxml launchpad başlığında XML_PARSE_HUGE ile belgelenmiştir. huge_tree=True ayarladığınızda 300 ve 1.000 derinlikleri tamamen geri gelir. Ancak 5.000 derinlikte, huge_tree açık olsa bile yalnızca 2.045 seviyeye ulaşılır — libxml2 içinde yapılandırılabilir sınırın üstünde ikinci, daha sert bir recursion tavanı vardır ve huge_tree bunu kaldırmaz.

Dolayısıyla eylem maddesi nettir: Güvendiğiniz bir kaynaktan gelen derin markup’ı ayrıştıracaksanız lxml.html.HTMLParser(huge_tree=True) kullanın. Bu paketin ödünç alınmış gözleme eklediği şey ise mekanizmadır (veri bozulması değil, güvenlik sınırı), çözüm (huge_tree) ve bu çözümün ulaşamadığı ikinci bir tavanın varlığıdır.

Read/Write DOM, Serialization, Encoding

lxml, salt-okunur bir çıkarıcı değil, tam bir okuma/yazma ağacıdır; düzenleme yüzeyini madde madde doğruladım. Sekiz DOM işleminin hepsi geçti: SubElement, insert, remove, replace, strip_tags (etiketi kaldırır, metni bırakır), strip_elements (etiketi ve metnini kaldırır), drop_tree (lxml.html için özel) ve yeni başlayanları şaşırtan text/tail ikili alan modeli — <p>head<b>bold</b>tail</p> içinde p.text "head", b.text "bold" ve b.tail "tail" olur.

Serialization beşte beş geçti: XML ve HTML modlarında tostring (HTML void elementleri doğru biçimde self-close etmez), pretty_print, C14N canonicalization (method="c14n", bir başka lxml özel özelliği) ve temiz bir round-trip.

Encoding kısmı lxml’nin kendini sessizce ayırdığı yerdir. UTF-8 olmayan baytlar verin — "<p>café éè</p>".encode("latin-1") girdisini lxml.html.fromstring üzerinden okutun — café éè karakterlerini eksiksiz geri alır; U+FFFD replacement karakteri yoktur, bayt düşmesi yoktur. Bu, selectolax paketinde onun "temiz referans" rolünü doğrudan yeniden üretir; aynı girdi diğer iki motorda sessizce bozuluyordu (Lexbor replacement karakterleri üretiyor, Modest ise baytları tamamen atıyordu). lxml’nin libxml2 tabanlı charset algılama tarafı burada daha istikrarlıdır.

Tersi tarafta ise encoding’i nasıl beyan ettiğiniz konusunda katılık vardır. XML declaration içinde encoding="latin-1" yazarsanız XMLSyntaxError: Unsupported encoding: latin-1 alırsınız; IANA’nın kanonik adı olan encoding="ISO-8859-1" ise sorunsuz parse olur ve café döndürür. libxml2 yalnızca kanonik encoding adlarını kabul eder, alias’ları değil — bu ayrıntı launchpad #613302 içinde belgelenmişti. Bilmiyorsanız sinir bozucu, bildiğinizde önemsiz.

Son olarak node yaşam döngüsü. Üç stale-handle senaryosunu izole subprocess’lerde çalıştırdım (sert çökme, non-zero exit olarak görünürdü): ağaç garbage-collected olduktan sonra bir düğümü elde tutmak, drop_tree() sonrası bir handle okumak ve remove() sonrası bir düğümü kullanmak. Hiçbirinde segfault yoktu — lxml, use-after-free’i önlemek için bir düğümün ağaca referansını canlı tutar. Aynı temiz sağlık raporunu selectolax da bu testte aldı.

Hız ve Bellek (Ödünç Alınmış, Ama Dürüstçe)

Bu bölümdeki her şey selectolax paketinden, 2026-07-13 itibarıyla yeniden kullanıldı. Bu paket kendi başına tek bir timing sayısı üretmedi; ve bunu iki kez söylemeyi, herhangi bir şeyi yeniden ölçtüğümü düşündürmeye tercih ederim.

Boyutlxml değeriOkuma
Saf parse p50 (10 MB)77,9 msselectolax-Lexbor’dan yaklaşık %33-34 daha hızlı
Tam parse + çıkarım p50 (1 MB / 10 MB)14,18 ms / 172,9 msküçük boyutlarda Lexbor ile kabaca eşit
100k-node CSS throughput3.002.646 node/süç C motoru içinde en üst seviye
10 MB RSS artışı128,9 MBölçülen altı parser içinde en hafif; BeautifulSoup’tan ~1,7x daha hafif
Import cold start14,1 msparsel-tarzı import’lardan ~2,3x daha hızlı

Saf parse ve throughput rakamları güçlü, üstelik lxml ölçülen altı parser arasında en az bellek tüketenidir. Yine de thread tablosuna bir not düşmek gerekir. Yeniden kullanılan veri, 4 thread’li wall-clock hızlanmasının yalnızca 1,21x olduğunu ve bunun sonuçsuz kaldığını gösteriyor — ama bu ortak varsayılan parser yoludur. lxml FAQ, parse sırasında GIL’in ancak her thread kendi parser’ını kullanıyorsa (veya kopyalanmış varsayılan bir parser) bırakıldığını açıkça söyler; ortak parser erişimi seri hale getirir. Bunu doğru yapmak için API yüzeyini yapısal olarak doğruladım (XMLParser.copy() var, get/set_default_parser var, XPathEvaluator içsel bir kilit taşıyor), fakat thread başına parser ile hızlanmayı ölçmedim — bu yeni bir timing ölçümü olurdu ve bu paket böyle ölçümler üretmiyor. Dolayısıyla 1,21x ifadesini "naif ortak parser yolunda" diye okuyun; lxml’nin threading tavanı olarak değil.

Ve her şeyin üzerinde bir yıldız işareti: Bunlar tek platform sayıları, macOS arm64. lxml’nin saf parse performansında Lexbor’u geçtiği iddiası, genelde Lexbor tabanlı parser’ın en hızlı olduğu yönündeki yerleşik kanaate ters düşüyor; bu yüzden birisi bunu kesin hüküm diye kullanmadan önce Linux x86_64 üzerinde tekrar bakılmayı gerçekten hak ediyor.

Lisanslama: Sıkıcı Ama Güzel Bir Kazanç

lxml BSD-3-Clause altında yayınlanır; paketlediği C kütüphaneleri olan libxml2 ve libxslt ise MIT lisanslıdır. Bu, hiçbir yerinde copyleft olmayan tamamen permissive bir zincirdir; yeniden dağıtım yaptığınız anda bunun önemi büyür. Karşılaştırma için selectolax wheel’i LGPL-2.1 Modest ve Apache-2.0 Lexbor içerir; bu nedenle kapalı kaynak bir üründe dağıtım söz konusuysa lxml daha temiz bir hikâyedir.

Pratik bir kurulum avantajı da var: lxml önceden derlenmiş wheel’ler yayınlar ve libxml2 ile libxslt’yi statik bağlar; dolayısıyla pip install lxml çoğu durumda sistem libxml2’si ya da makinenizde derleyici gerektirmez — kaynak koddan derleme deneyiminden farklıdır.

lxml Nerede Durur — ve AI Tabanlı Bir Çıkarma Katmanı Nerede Devreye Girer?

Sınırı net konuşmak lazım; kategori hatası yapmak çok kolay. lxml bir parsing kütüphanesidir. Size bir ağaç ve güçlü bir sorgu motoru verir; ağacın çevresindeki her şey hâlâ sizin işinizdir: sayfayı çekmek, JavaScript’i render etmek, anti-bot engellerini aşmak, XPath’i yazıp sürdürmek ve sonucu yapılandırmak. Bu, barındırılan bir extraction hizmetinden farklı bir katmandır; ikisi rakip değil, daha çok komşudur.

Fetch-render-select-maintain yığınını sahiplenmek istemeyen bir geliştirici için üst katmanda Thunderbit gibi bir araç devreye girer — ve bu kitle için önemli olan tarayıcı eklentisi değil, API, MCP sunucusu ve CLI’dır. Thunderbit Open API, bir sayfayı temiz Markdown’a dönüştürmek için POST /distill ve JSON Schema’ya göre yapılandırılmış veri çıkarmak için POST /extract uçlarını sunar; renderMode seçeneği ve yüksek hacim için batch işler de vardır. Aynı motor, ajanlar ve kodlama asistanları için MCP sunucusu olarak (thunderbit_suggest_fields, thunderbit_distill, thunderbit_extract) ve terminalden doğrudan npx @thunderbit/thunderbit-cli ile çalıştırabileceğiniz bir CLI olarak da kullanılabilir. JS render, anti-bot ve CAPTCHA’ları kutudan çıktığı haliyle yönetir ve şema ile eşleşen JSON döndürür — yani bu katman parsing’in üstündedir, yerine geçmez.

Web Veri Çıkarma için Thunderbit’i Deneyin

Kısacası karar basittir. Akışı siz yönetiyor ve bir ağ üzerinde cerrahi XPath kontrolü istiyorsanız lxml kullanın. Selector ve render yönetmek istemiyorsanız AI tabanlı bir extraction API tercih edin. Gerçek sistemlerin birçoğu ikisini birlikte kullanır: kontrol ettikleri yapılandırılmış feed’lerde lxml, kontrol etmedikleri kirli uzun kuyruk sayfalarda extraction hizmeti.

Bu İncelemenin Test Etmediği Şeyler

Bu çalışma nihai bir puan tablosu değil, ön değerlendirmedir; dolayısıyla kapsamadığı şeyleri de söyleyelim.

Tüm zamanlama ve bellek rakamları yeniden kullanılmıştır, tek platformludur (macOS arm64, Python 3.14) ve o paketin uyarılarını taşır — "lxml saf parsing’de daha hızlı" sonucu yerleşik kanaate ters düşer ve Linux x86_64 ile tekrar doğrulanmalıdır. Thread başına parser ile threading hızlanması test edilmedi (yeni timing gerekir). 300k record üzerinde iterparse belleğini ölçtüm ama gigabayt ölçeğinde gerçek XML’de, HTML ile XML arasında iterparse’de ya da çok saatlik soak testte ölçmedim. lxml’nin XSLT 1.0 desteği, RelaxNG / XMLSchema / DTD doğrulaması ve EXSLT uzantıları burada hiç test edilmedi — geniş bir yetenek alanı ama parsing ve seçim çekirdeğinin dışında. 2.045 seviyede ikinci depth sınırını gözledim fakat libxml2’nin tam recursion sabitini netleştirmedim. Yalnızca kararlı 6.1.1 sürümü test edildi, 7.0.0 alpha değil. Windows, kaynak derlemeler ve free-threaded 3.14t build de test dışı kaldı. XPath içinde ise yerleşik fonksiyonları kapsadım; XPath değişkenleri, özel Python extension fonksiyonları ya da önceden derlenmiş etree.XPath nesnelerinin yeniden kullanımını değil.

Sonuç

lxml hızlı ve yeni olan şey değildir; tavsiye edilmesinin tam nedeni de budur. Bu, iki on yıllık bir libxml2 binding’idir; ana akım Python alternatiflerinin eşleşemediği tam bir XPath 1.0 motoruna, ortada hata günlüğü olan üç kademeli öngörülebilir ayrıştırma sıkılığına, belleğe sığmayan belgeler için gerçek bir streaming parser’a, doğru çoklu namespace ve encoding işlemesine ve tamamen permissive bir lisansa sahiptir. Küçük sert kenarlar — yaklaşık 253 seviyelik depth sınırı ve ortak parser threading sayısı — belgelenmiştir, yapılandırılabilirdir ve artık açıklanmıştır.

Eğer scraping hattının sahibi sizseniz ve XPath’e yaslanıyorsanız, hâlâ başvuracağınız parser lxml’dir. Selector ve render yönetimini sürdürmek istemiyorsanız, Thunderbit API, MCP ve CLI gibi bir AI extraction katmanının işi budur — rekabet değil, görev paylaşımıdır. Her iki durumda da bu sayıları geçici kabul edin ve bir tasarım dokümanında kullanmadan önce kendi platformunuzda timing’i yeniden kontrol edin.

Web Veri Çıkarma için Thunderbit’i Deneyin Get Started Free

SSS

lxml bir web scraper mıdır? Hayır. lxml bir parser ve serializer’dır — markup’ı düzenlenebilir ve sorgulanabilir bir ağaca çeviren, libxml2/libxslt için Python binding’i. Sayfa çekmez, JavaScript render etmez, anti-bot savunmalarını yönetmez; istek katmanını siz sağlarsınız (requests, httpx, headless browser ya da bir scraping servisi aracılığıyla) ve baytları lxml’ye verirsiniz.

BeautifulSoup veya selectolax yerine ne zaman lxml kullanmalıyım? XPath’e ihtiyaç duyduğunuzda lxml seçin. BeautifulSoup aslında arka planda parser olarak lxml’yi kullanabilir ama yerleşik XPath sunmaz; selectolax ise yalnızca CSS destekler ve dar alanında daha hızlıdır. Seçim mantığınız metin içeriği filtreleme, ebeveyn veya ata gezinimi, attribute/text-node çıkarımı ya da count predicate’leri gerektiriyorsa, bunları doğrudan ifade edebilen tek ana akım Python seçeneği lxml’nin XPath motorudur.

lxml neden derinlemesine iç içe geçmiş içeriği sessizce atıyor? Varsayılan parser’ı yaklaşık 253 seviyede iç içe geçmeyi sınırlar — bu, kötü niyetli belgelere karşı libxml2’nin DoS savunmasıdır, bug değildir. huge_tree=True ayarlarsanız (örneğin lxml.html.HTMLParser(huge_tree=True)), 300 ve 1.000 seviyelik derinlikleri eksiksiz geri getirir. Ancak huge_tree’nin kaldırmadığı, yaklaşık 2.045 seviyede ikinci ve daha sert bir recursion sınırı vardır.

lxml çok iş parçacıklı ayrıştırmada GIL’i bırakır mı? Sadece doğru koşullarda. lxml FAQ’ye göre, parse sırasında GIL her thread kendi parser’ını kullanıyorsa ya da kopyalanmış bir default parser kullanıyorsa bırakılır; ortak parser erişimi ise işi seri hale getirir. Yeniden kullanılan 4-thread hızlanma değeri olan 1,21x, naif ortak parser yolunu yansıtır; burada ölçülmeyen thread başına parser tavanını değil.

lxml 2026’da hâlâ bakımı yapılıyor mu? Evet. Kararlı 6.1.1 sürümü 2026-05-18’de yayınlandı, repo en son 2026-07-02’de pushlandı ve devam eden bir 7.0.0 alpha var. Yaklaşık 3.000 GitHub yıldızı ve altında aktif bakımı süren libxml2 ile, hâlâ güncel ve desteklenen bir kütüphane; eski bir kalıntı değil.

Ke
Ke
Thunderbit’ta CTO | Kıdemli Veri Bilimci ve ML Uzmanı Makine öğrenimi ve veri bilimi alanında yaklaşık on yıllık deneyime sahip olan Ke Shen, Columbia University mezunu ve Walmart Labs’te eski Kıdemli Veri Bilimci’dir. Python, R, Java ve İstatistik konularında derin ve meslektaşları tarafından tanınan uzmanlığıyla, karmaşık yapay zekâ algoritmalarını teoriden üretim düzeyinde mimariye taşıma konusunda sahada test edilmiş içgörüler paylaşır.
Topics
Web Scraping ToolsAI Web Scraper

Thunderbit'i dene

Potansiyel müşterileri ve diğer verileri sadece 2 tıkla topla. Yapay zekâ destekli.

Thunderbit'i al Ücretsiz
Yapay Zekâ ile Veri Çıkar
Verileri kolayca Google Sheets, Airtable veya Notion'a aktar
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week