Bu konudaki payımız küçük olsa da yine de belirtmekte fayda var: Biz Geonode olarak, veri çıkarma çalışmaları yapan kişilere proxy satıyoruz; bu nedenle destek görüşmelerinde seçiciler konusu sürekli gündeme geliyor. Karşılaştırmaya geçmeden önce belirtilmesi gereken nokta, her iki seçeneğin de engellenip engellenmemenizi etkilemediğidir. Bir seçici sorunu ile bir ağ sorunu uzaktan bakıldığında aynı görünür — her ikisi de "kazıyıcım veri döndürmeyi durdurdu" sonucunu verir — ancak aralarında hiçbir ortak nokta yoktur. Sayfa bozulmadan geldi ve ifadeniz hiçbir şeyle eşleşmediyse, bu bir seçici sorunudur ve hiçbir altyapı kararı bu sorunu çözemez.
Özet
| CSS | XPath | |
|---|---|---|
| Sınıf, kimlik veya öznitelikle seçme | Evet, sorunsuz | Evet, biraz zor |
| Alt öğe ve çocuk ilişkileri | Evet | Evet |
| Metin içeriğine göre seçme | Hayır | Evet |
| Üst öğeye veya ataya gitme | Kısmen, :has() aracılığıyla | Evet, doğrudan |
| Önceki kardeş öğeye gitme | Kısmen, :has() aracılığıyla | Evet, doğrudan |
| Konumsal seçim | :nth-child() ailesi | position(), last(), yüklemler |
| Dize işlevleri | Hayır | Evet |
| Ad alanlarıyla XML sorgulama | Hayır | Evet |
| Okunabilirlik | Daha iyi | Daha kötü |
| Araçlar ve tarayıcı desteği | Evrensel | 1.0 için evrensel |
En önemli iki satır şunlardır. CSS, metin içeriğine göre seçim yapamaz; bu da en yaygın veri çıkarma modelini — yani yanındaki etiketle bir değeri bulmayı — imkansız kılar. Ayrıca XPath'in okunması daha zordur; bu durum, bir yıl sonra bir iş arkadaşınızın kodunuzu bakımını yapması gerektiğinde, insanların kabul ettiğinden daha büyük önem taşır.
CSS’in Daha İyi Olduğu Konular
Sınıf ve öznitelik seçimi; bu konuda rakip bile yok.
div.product-card
a[href^="https://"]
input[type="checkbox"]:checked
ul > li:first-child
section.content p:not(.footnote)
XPath’teki karşılıkları daha uzundur ve sınıflar söz konusu olduğunda gerçekten de kullanması zahmetlidir:
//div[contains(concat(' ', normalize-space(@class), ' '), ' product-card ')]
//a[starts-with(@href, 'https://')]
//ul/li[1]
İlk örnek, sınıf eşleştirme kalıbıdır ve bunun var olmasının nedeni, @class
ifadesinin tek bir boşlukla ayrılmış bir dize olması ve XPath’in bu ifade içindeki belirteç kavramını tanımamasıdır. Basit bir contains(@class, 'product-card')
ifadesi, product-card-large
ve old-product-card
ile de eşleşir; bu nedenle, boşluklarla doldurulmuş versiyon doğru olanıdır. Ayrıca, bu ifade div.product-card
ifadesinin dört katı uzunluğundadır ve taranması oldukça zordur.
Seçim kriterleriniz sınıflar, kimlikler, öznitelikler ve yapısal ilişkiler ise, CSS kullanın. Bu, gerçek seçim işlerinin büyük çoğunluğunu kapsar ve bunun için XPath'i seçmek, kullanmadığınız bir özellik uğruna okunabilirlikten ödün vermek anlamına gelir.
CSS ayrıca daha iyi araçlara sahiptir. Her tarayıcının öğe denetleyicisi, CSS seçicilerini doğal olarak üretir; çoğu test çerçevesi varsayılan olarak bunları kullanır ve document.querySelectorAll
, herhangi bir yardımcı araç gerektirmeden her yerde kullanılabilir.
XPath’in Daha İyi Yaptığı Şeyler
CSS’in hiç yapamadığı metin eşleştirme:
//button[normalize-space()='Continue']
//a[contains(., 'Download')]
//dt[normalize-space()='Price']/following-sibling::dd[1]
Bu son kalıp — etiketi bul, yanındaki değeri al — yapılandırılmış sayfa veri çıkarımının bel kemiğidir ve bunun için bir CSS ifadesi yoktur; çünkü CSS’in metin içeriğine erişimi yoktur. Bu tek yetenek, XPath’in aksi takdirde baştan sona CSS kullanan kod tabanlarını kazımaya devam etmesinin sebebidir.
Atalardan gezinme:
//span[@class='price']/ancestor::div[contains(@class,'card')][1]
Bir değerden onu barındıran kapsayıcıya doğru yukarı doğru ilerleyin. :has()
, CSS’ye bunun bir biçimini sunar; sınırlamalar aşağıda ele alınmaktadır.
İçeriğe göre konumsal mantık:
//h2[normalize-space()='Specifications']/following-sibling::table[1]
Belirli bir başlıktan sonraki ilk tablo. :nth-child()
, kardeş öğeler arasındaki konumları sayar; "metni X olan öğeden sonra" ifadesini ifade edemez.
Dize işlevleri. normalize-space()
, substring-before()
, translate()
ve diğerleri, ifade içinde işlem yapmanıza olanak tanır. Özellikle normalize-space()
neredeyse vazgeçilmezdir, çünkü gerçek HTML düzgün biçimlendirilmiştir ve "\n In stock\n"
ile tam metin karşılaştırması başarısız olur.
Ad alanlı XML. HTML yerine XML sorguluyorsanız — bir site haritası, bir RSS beslemesi, bir SOAP yanıtı gibi — CSS uygun bir araç değildir. XPath bu amaç için tasarlanmıştır ve ad alanı işleme, tasarımın bir parçasıdır.
:has()'in Getirileri
Bu karşılaştırmadaki en önemli gelişme budur; ancak bu konuyla ilgili pek çok makale bundan önce yazılmıştır.
MDN belgeleri, :has()'i, "argüman olarak geçirilen göreceli seçicilerden herhangi biri, bu öğeye sabitlendiğinde en az bir öğeyle eşleşirse o öğeyi temsil eden" ve "referans öğeye göre bir üst öğeyi veya önceki bir kardeş öğeyi seçmenin bir yolunu" sağlayan bir özellik olarak tanımlamaktadır. Temel durumu "yaygın olarak kullanılabilir" olup, Aralık 2023'ten beri tüm tarayıcılarda desteklenmektedir.
Böylece CSS artık daha önce ifade edemediği şeyleri ifade edebilmektedir:
div.card:has(span.sold-out) /* a card containing a sold-out marker */
h1:has(+ p) /* an h1 immediately followed by a p */
li:has(~ li.active) /* an li with a later active sibling */
tr:has(td.error) /* a row containing an error cell */
Bu, insanların daha önce XPath'e ihtiyaç duydukları konuların önemli bir kısmını kapsamaktadır — üst öğe seçimi ve kardeş öğeye bağlı koşullar.
Belgelenmiş ve bilinmesi gereken üç sınırlama vardır. :has() "başka bir :has() içinde iç içe yerleştirilemez". Sözde öğeler ":has() içinde geçerli seçiciler değildir" ve bunun için geçerli bağlantı noktaları değildir. Ayrıca özgüllüğü, "argümanlarındaki en özgül seçicinin özgüllüğü"dür; bu, :is() ve :not() öğelerinin davranışına uygundur.
Çıkarma işlemi için en önemli sınırlama bu listede yer almamaktadır: :has() hala metinle eşleşememektedir. div:has(span) çalışır; div:has(span:contains('Price')) mevcut değildir, çünkü :contains() standart bir CSS seçicisi değildir. Bu seçici önerilmiş ancak reddedilmiştir ve tarayıcılar bunu uygulamamaktadır. Dolayısıyla, :has()'dan bağımsız olarak etiket-değer kalıbı XPath'in alanı olarak kalmaktadır.
Yan Yana Çeviriler
Bu iki yaklaşım arasındaki dengeyi kavramanın en hızlı yolu, aynı amacın her iki şekilde de ifade edildiğini görmektir.
| Amaç | CSS | XPath |
|---|---|---|
| Sınıfı olan öğe | div.card | //div[contains(concat(' ',normalize-space(@class),' '),' card ')] |
| Kimliği olan öğe | #main | //*[@id='main'] |
| Öznitelik var | a[href] | //a[@href] |
| Öznitelik şununla başlar | a[href^="/docs"] | //a[starts-with(@href,'/docs')] |
| Öznitelik şunu içerir | a[href*="pdf"] | //a[contains(@href,'pdf')] |
| Doğrudan alt öğe | ul > li | //ul/li |
| Herhangi bir alt öğe | div p | //div//p |
| İlk alt öğe | li:first-child | //li[1] |
| Son alt öğe | li:last-child | //li[last()] |
| N'inci alt öğe | li:nth-child(3) | //li[3] |
| Bir sonraki kardeş öğe | h2 + p | //h2/following-sibling::p[1] |
| Herhangi bir sonraki kardeş öğe | h2 ~ p | //h2/following-sibling::p |
| Negasyon | p:not(.footnote) | //p[not(contains(@class,'footnote'))] |
| Eşleşenin üst öğesi | div:has(> span.price) | //span[contains(@class,'price')]/.. |
| Metin içerir | mümkün değil | //p[contains(., 'Price')] |
| Tam metin | mümkün değil | //p[normalize-space()='Price'] |
| Etiketin yanındaki değer | mümkün değil | //dt[normalize-space()='Price']/following-sibling::dd[1] |
Tabloyu aşağı doğru okuduğumuzda, örüntü net bir şekilde ortaya çıkıyor. Üst satırın üzerindeki her şey için CSS daha kısa ve daha anlaşılırdır; XPath'i seçmek, hiçbir kazanç sağlamadan gereksiz ayrıntılara yer vermek anlamına gelir. En alttaki üç satır için ise CSS sütunu hiç yoktur.
İki çeviriye daha yakından bakmak gerekir. Sınıf satırı, tüm karşılaştırma içinde en keskin kontrastı oluşturur — dokuz karakter karşı yetmiş küsur karakter — ve XPath sürümü sırf uzunluk uğruna uzatılmış değildir, çünkü kısa biçim olan contains(@class,'card'), card-large ve discard ile gerçekten de fazla eşleşmektedir. Ve “first child” satırı bir tuzak barındırıyor: li:first-child ve //li[1] burada aynı sonucu veriyor, ancak //li[1] her ebeveyn için uygulanan bir yüklem olduğundan, belgedeki her listenin altındaki ilk li öğesini seçer. CSS’de belirsizlik yoktur; XPath’te ise yalnızca birini kastettiyseniz (//li)[1] kullanmanız gerekir.
Gerçekçi Bir Karma Örnek
Bunun pratikte nasıl göründüğünü, bir ürün sayfasını ayıklayarak görelim.
# CSS for the structural work — clear and adequate
cards = tree.cssselect('div.product-grid > article.product-card')
for card in cards:
name = card.cssselect('h3.product-name')[0].text_content().strip()
image = card.cssselect('img.product-image')[0].get('src')
# XPath for the label-value pairs, which CSS cannot express
price = card.xpath(
".//dt[normalize-space()='Price']/following-sibling::dd[1]"
)[0].text_content().strip()
stock = card.xpath(
".//span[contains(., 'in stock') or contains(., 'out of stock')]"
)
Burada kopyalanmaya değer üç şey var.
Her XPath ifadesinin başında yer alan .. .//dt mevcut kart içinde arama yapar; //dt ise kökten başlayarak tüm belgeyi tarar ve her kart için sayfadaki ilk eşleşen etiketi döndürür. Bu, karışık çıkarma kodlarında en sık görülen hatalardan biridir ve her kayıt için aynı değeri üretir — mantıklı, tekdüze ve yanlış.
CSS'nin yeterli olduğu yerlerde CSS kullanın. Izgara ve kart seçimi, başlık, resim. Bunları XPath ile yazmak kodu uzatır ve anlaşılırlığı azaltır.
XPath'i sadece gerektiği yerde kullanın. Fiyat, etiketiyle; stok durumu ise metniyle tanımlanır. İkisi de CSS ile ifade edilemez ve her ikisi de XPath’in dosyada bulunmasının asıl nedenidir.
Sonuç olarak, her ifadenin olabildiğince kısa olduğu bir veri toplayıcı ortaya çıkar ve okuyucu, hangi kısımların yapıya, hangilerinin içeriğe bağlı olduğunu bir bakışta görebilir — bu aynı zamanda site değiştiğinde nelerin bozulacağının da bir haritasıdır.
Performans
Genellikle önemsiz, bazen ise belirleyicidir.
CSS, tarayıcılarda genellikle daha hızlıdır; çünkü motorlar seçici eşleşmesini yoğun bir şekilde optimize eder — bu, görüntüleme için kritik yoldadır. XPath ise daha genel bir değerlendirme modelinden geçer.
Çoğu kişinin çalıştığı ölçekte bu fark nadiren önem taşır. Tek bir sayfadan birkaç yüz kez seçim yapmak her iki durumda da ölçülemeyecek kadar önemsizdir; ağ isteği, bu sürece kat kat daha fazla etki eder.
Önemli olduğu durumlar:
preceding ve following eksenleri gerçekten çok kaynak tüketir. Bunlar, bağlam düğümünden önce veya sonra tüm belgeyi tarar. Çok sayıda düğüm içeren bir döngü içinde bu işlem, karesel bir işlem haline gelir. Bir XPath ifadesi belirgin şekilde yavaşsa, bunlardan birini kullanıp kullanmadığını kontrol edin — preceding-sibling ve following-sibling, kardeş düğüm sayısıyla sınırlıdır ve sorun yaratmaz.
** İç içe geçmiş bir ifadenin başında yer alan//, kökten itibaren yeniden arama yapar.** //div//span göründüğünden daha maliyetlidir ve div'ler üzerinde bir döngü içindeki .//span genellikle kastettiğiniz şeydir.
:has(), büyük belgelerde maliyetli olabilir çünkü motor, her aday için iç seçiciyi değerlendirmek zorundadır. Veri çıkarma için uygundur; ancak büyük bir sayfaya uygulanan stil sayfasında dikkat edilmesi gereken bir noktadır.
lxml veya benzeri araçlarla sunucu tarafında ayrıştırma yaparken, aradaki fark o kadar küçüktür ki okunabilirlik öncelikli olmalıdır.
Kullanılabilirlik ve Sürüm Sorunları
“Çevrimiçi test ortamında çalışıyor ama benim kodumda çalışmıyor” durumuna yol açan tuzak.
Tarayıcılar, XPath 1.0'ı document.evaluate aracılığıyla uygular ve Selenium, tarayıcının motorunu kullanır. XPath 2.0 ve 3.1 özellikleri — matches(), replace(), lower-case(), ends-with(), diziler — burada kullanılamaz. Bunlardan herhangi birini kullanan bir kod parçacığı başarısız olur.
lxml, genel API'si için XPath 1.0'ı ve ayrıca düzenli ifadeler için re:test() dahil olmak üzere EXSLT uzantılarını uygular. Dolayısıyla, Python'da çalışan bir düzenli ifade tabanlı ifade, Selenium'da çalışmayacaktır.
Ayrıştırma kütüphanelerindeki CSS desteği genellikle, CSS'yi dahili olarak XPath'e dönüştüren bir çeviri katmanı aracılığıyla sağlanır. Bu, yaygın seçiciler için iyi sonuç verirken, daha yeni seçiciler için o kadar iyi sonuç vermez — sunucu tarafı ayrıştırıcılardaki :has() desteği önemli ölçüde değişiklik gösterir ve bir tarayıcıda çalışan bir seçici, kazıyıcınızda çalışmayabilir.
Pratik kural: Seçicilerinizi tarayıcı konsolunda veya çevrimiçi bir araçta değil, çalıştırılacakları ortamda test edin. En sık karşılaşılan iki sürpriz, Selenium’da XPath 2.0 işlevlerinin çalışmaması ve Python ayrıştırıcısında :has()’ın çalışmamasıdır.
Uygulamada Seçim Yapma
Neredeyse her durumu çözen bir karar süreci.
CSS ile başlayın. CSS daha okunaklıdır, daha iyi araçlara sahiptir ve sınıf, kimlik, öznitelik ve yapısal seçimler için yeterlidir — ki bu, seçimlerin çoğunu oluşturur.
Metinle eşleştirme yapmanız gerektiğinde XPath’e geçin. Bunun temel ve belirleyici nedeni şudur: Hiçbir CSS yapısı metin içeriğini okumaz.
:has()'in kapsadığı sınırların ötesinde atalardan gezinme için XPath'e geçin, özellikle de bilinen bir üst öğeyle koşullu eşleşme yerine birkaç seviye yukarıdaki belirli bir ataya ihtiyacınız olduğunda.
XML için XPath'e geçin. Ad alanları ve belge sırasına göre yapılan işlemler, XPath’in tasarlanma amacıdır.
Tek bir kod tabanında her ikisini de kullanın. Bu, bir uzlaşma değil, normal ve mantıklı bir yaklaşımdır. Basit %90’lık kısım için CSS’yi, zor %10’luk kısım için XPath’i kullanan bir veri toplayıcı, tek bir yönteme bağlı kalana göre bakımı daha kolaydır.
Ve her iki seçenekten de daha değerli bir alışkanlık: bir seçici yazmadan önce gömülü yapılandırılmış verileri kontrol edin. Birçok sayfa, arama özelliklerini desteklediği için <script type="application/ld+json"> bloğunda JSON-LD içerir ve bunu ayrıştırmak, görüntülenen işaretlemeyi ayrıştırmaktan çok daha kararlıdır. Sayfadaki tüm seçicileri bozan yeniden tasarımlardan bile etkilenmez.
İkisinin de Çözemediği Sorun
Her ikisi de aynı şekilde kırılgandır ve asıl zaman kaybına neden olan da budur.
Yapısal seçiciler sessizce bozulur. Birisi bir sütun ekler ve td[3] farklı bir hücreyle eşleşir. Herhangi bir hata bildirilmez; verileriniz sessizce yanlış olur. Mümkün olduğunda metne veya tanımlayıcılara dayanın ve çıkardığınız verinin biçimini doğrulayın — bir fiyat, fiyat gibi görünmesi gerekiyorsa, bunu kontrol edin.
İkisi de istemci tarafında oluşturulan içeriği işleyemez. İşaretleme, yüklemeden sonra JavaScript tarafından birleştiriliyorsa, ikisi de ilk HTML'de hiçbir şey bulamaz. Bu, bir seçici sorunu değil, tarayıcı veya altta yatan API'yi gerektiren bir veri alma sorunudur.
İkisi de yeniden tasarımdan tek başına sağ çıkamaz. Bunun önlemi, seçicileri tek bir yerde tutmaktır; böylece bir değişiklikten sonra güncelleme bir gün yerine bir saat sürer ve bir sorun çıktığında eskisiyle yenisini karşılaştırabilmeniz için ayrıştırdığınız HTML'yi saklamaktır.
Ayrıca ikisi de sayfanın nasıl yüklendiğinden etkilenmez. İşte bu noktada devreye giriyoruz: belge bozulmamışsa ve ifadeniz hiçbir şeyle eşleşmiyorsa, altyapıda ne kadar değişiklik yapılırsa yapılsın sonuç değişmez.
Sık Sorulan Sorular
XPath, CSS seçicilerinden daha mı iyidir?
Genel olarak ikisi de birbirinden üstün değildir. XPath daha kapsamlıdır — metin eşleştirebilir, üst öğelere gidebilir ve dize işlevlerini kullanabilir. CSS ise daha okunaklıdır ve araçlar tarafından daha iyi desteklenir. Varsayılan olarak CSS’yi kullanın ve CSS’nin ifade edemediği özel durumlar için XPath’i kullanın.
CSS seçicileri metne göre seçim yapabilir mi?
Hayır. Metin içeriği için standart bir CSS seçicisi yoktur — :contains() önerilmiş ancak hiçbir zaman benimsenmemiştir ve tarayıcılar bunu uygulamamaktadır. Bu, en büyük yetenek eksikliğidir ve XPath’in veri çıkarma işlerinde hâlâ kullanılmasının ana nedenidir.
:has(), XPath’in yerini alır mı?
Kısmen. Daha önce yalnızca XPath ile mümkün olan CSS üst öğe seçimi ve kardeş öğe koşullu eşleştirme özelliklerini sunar. Metin eşleştirme özelliği eklemez, bu nedenle etiket-değer kalıbı için hâlâ XPath gereklidir. Ayrıca iç içe yerleştirilemez ve sunucu tarafı ayrıştırma kütüphanelerindeki desteği değişkenlik gösterir.
Hangisi daha hızlı, XPath mi yoksa CSS mi?
Seçici eşleştirme, görüntüleme için optimize edildiğinden CSS genellikle tarayıcılarda daha hızlıdır. Bu fark, ağ süresiyle karşılaştırıldığında genellikle önemsizdir. XPath’in gerçekten yavaşladığı yerler, belgenin tamamını tarayan preceding ve following eksenleridir.
Selenium'da XPath 2.0 kullanabilir miyim?
Hayır. Tarayıcılar, document.evaluate aracılığıyla XPath 1.0'ı uygular ve Selenium, tarayıcının motorunu kullanır. matches(), lower-case() ve ends-with() gibi işlevler kullanılamaz. Bu, bir kod parçasının çevrimiçi test ortamında çalışıp test paketinde başarısız olmasının en yaygın nedenidir.
Üst öğeyi nasıl seçerim?
XPath'te, parent:: veya ... CSS'de, :has() koşullu bir biçim sunar — div:has(> span.price), span yerine div öğesini seçer. Birkaç seviye yukarıdaki belirli bir ataya ulaşmak için, XPath'in ancestor:: ekseni daha doğrudandır.
Web kazıma için CSS mi yoksa XPath mi kullanmalıyım?
Aynı kod tabanında ikisini de kullanın. Sınıf, kimlik ve öznitelik seçimi için CSS kullanın; bu, işin büyük bir kısmını oluşturur. Metin eşleştirmesi yapmanız gereken yerlerde ise XPath'i kullanın — bir değeri etiketine göre bulmak tipik bir durumdur ve bunun CSS'de bir karşılığı yoktur.
Bir site değiştiğinde hangisi daha kararlıdır?
Doğası gereği ikisi de değil — kararlılık, hangi dili kullandığınızdan ziyade neye sabitlendiğinize bağlıdır. Metne veya data-* özniteliğine sabitlenmek, yeniden tasarımdan sonra da geçerliliğini korur; konuma sabitlenmek ise hiçbir durumda geçerliliğini korumaz. Her iki dil de her iki seçeneği de sunar.
Sonuç
Karşılaştırma iki asimetriye indirgenebilir. CSS metni okuyamaz ve XPath’in okunması daha zordur. Geri kalan her şey ayrıntıdır.
Bu da çalışma kuralını basitleştirir: varsayılan olarak CSS'yi kullanın, çünkü seçimlerin çoğu sınıf, kimlik veya öznitelik bazlıdır ve CSS bunları net bir şekilde ifade eder. XPath'i, yalnızca onun sunduğu bir şeye ihtiyaç duyduğunuz belirli noktalarda kullanın — her şeyden önce metin eşleştirme, ardından atadan ataya gezinme ve dize işlevleri. İkisini karıştırmak normaldir ve her birinin en iyi olduğu alanda kullanıldığı bir kod tabanı, tek bir tarafı seçen bir kod tabanına göre bakımı daha kolaydır.
:has(), aradaki farkı gerçekten daraltmıştır ve uygun olduğu yerlerde benimsenmeye değerdir: 2023 sonlarından beri yaygın olarak kullanılabilen, düz CSS'deki üst öğe seçimi ve kardeş öğe koşulları. Ancak bunun metinle ilgili olmadığını ve sunucu tarafında ayrıştırıcı desteğinin tarayıcı desteğine kıyasla daha az tutarlı olduğunu unutmayın.
Herhangi bir ifadeye güvenmeden önce ortamınızı kontrol edin. Tarayıcılarda ve Selenium’da XPath 1.0, lxml’de EXSLT ekleri, ayrıştırma kütüphanelerinde değişken :has() desteği — seçicilerle ilgili en yaygın sürpriz, bir sözdizimi hatası değil, onu çalıştırdığınız yerden başka bir yerde bulunan bir özelliktir.
