Bunu kimin yazdığına dair kısa bir not. Biz Geonode olarak, veri çıkaran kişilere proxy satıyoruz; dolayısıyla XPath, işimizin bir parçası olmaktan ziyade işimizle yakından ilişkili bir konudur. Burada dikkat edilmesi gereken nokta, bir seçici sorunu ile bir proxy sorununun birbirine hiç benzemediği ve bunları karıştırmanın saatlerce zaman kaybına yol açtığıdır — engellenen bir istek, bir doğrulama sayfası veya hata durumu döndürürken, hatalı bir ifade ise tamamen normal bir sayfa ve boş bir sonuç döndürür. HTML sayfası mevcutsa ve XPath'iniz hiçbir şey bulamıyorsa, ağda bir sorun yoktur ve bu sayfa doğru yerdir.
Temel Sözdizimi
| İfade | Seçer |
|---|---|
/html/body/div | Kökten başlayan mutlak yol |
//div | Belgenin herhangi bir yerinde bulunan herhangi bir div |
//div/p | div öğesinin doğrudan alt öğeleri olan p öğeleri |
//div//p | div öğesinin içindeki herhangi bir derinlikte bulunan p öğeleri |
. | Geçerli bağlam düğümü |
.. | Bağlam düğümünün üst düğümü |
* | Herhangi bir öğe |
@href | href özniteliği |
//@href | Belgedeki her href özniteliği |
text() | Bağlam düğümünün metin düğümü alt öğeleri |
node() | Metin ve yorumlar dahil olmak üzere herhangi bir düğüm |
//a | //link | Birleşim — her iki ifadeyle de eşleşen her şey |
/ ile // arasındaki farkı iyice kavramak gerekir. Tek bir eğik çizgi "doğrudan alt düğüm" anlamına gelir; çift eğik çizgi ise "herhangi bir derinlikteki alt düğüm" anlamına gelir. //div/p, section ile sarılmış bir paragrafı bulamaz; //div//p ise bulur.
Mutlak yollar — /html/body/div[2]/div[1]/span — tarayıcının "XPath'i kopyala" seçeneğinin ürettiği ve bir sonraki yeniden tasarımda bozulacak olan yollardır. Tanımlanabilir bir noktadan başlayıp göreceli olarak gezinmeyi tercih edin.
Yüklemler
Köşeli parantezler bir düğüm kümesini filtreler. En yararlı işlemlerin çoğunun gerçekleştiği yer burasıdır.
| İfade | Seçer |
|---|---|
//div[1] | Her bir üst düğüm için, kardeşleri arasından ilk div |
(//div)[1] | Belgenin tamamında ilk div |
//div[last()] | Kardeşleri arasından son div |
//div[position() < 4] | İlk üç |
//a[@href] | href özniteliğine sahip bağlantılar |
//a[@href='/about'] | Tam olarak bu href değerine sahip bağlantılar |
//div[@class and @id] | Her iki özniteliğe de sahip öğeler |
//p[text()] | En az bir text-node alt öğesi olan paragraflar |
//div[p] | En az bir p alt öğesi içeren div'ler |
//div[not(@hidden)] | hidden özniteliği olmayan div'ler |
//td[.='42'][@class='qty'] | Sırayla uygulanan iki yüklem |
//div[1] ile (//div)[1] arasındaki fark, XPath'te en sık görülen karışıklık konusudur. İlki, her bir üst öğeye uygulanan bir yüklemdir; dolayısıyla, her üst öğenin altında bulunan ilk div öğesini seçer — bu, potansiyel olarak çok sayıda düğüm anlamına gelebilir. İkincisi ise tüm div'leri belge sırasına göre bir düğüm kümesinde toplar ve ilkini alır; bu da tam olarak tek bir düğümdür. Her ikisi de kullanışlıdır; ancak birbirlerinin yerine kullanılamazlar.
Yargılar zincirleme çalışır ve her biri bir öncekinin sonucuna uygulanır. //td[@class='price'][1], soldan sağa okunduğunda "class price sınıfına sahip hücreler arasında ilk olan" anlamına gelir.
Eksenler
Eksenler, bağlam düğümüne göre yönlendirilir. Çoğu kişi üçünü kullanır ve ara sıra diğerlerine ihtiyaç duyar.
| Eksen | Seçer |
|---|---|
child:: | Doğrudan alt düğümler — varsayılan, genellikle atlanır |
descendant:: | Her derinlikteki tüm alt düğümler |
parent:: | Üst düğüm |
ancestor:: | Kök düğüme kadar tüm üst düğümler |
ancestor-or-self:: | Üst düğümler artı düğümün kendisi |
following-sibling:: | Sonraki kardeş düğümler |
preceding-sibling:: | Önceki kardeş düğümler |
following:: | Belge sırasına göre alt düğümler hariç, sonradan gelen her şey |
preceding:: | Atalar hariç, öncesindeki her şey |
attribute:: | Öznitelikler — kısaltması @ |
self:: | Düğümün kendisi |
Dördü ters eksen'dir — ancestor, ancestor-or-self, preceding ve preceding-sibling — ve bunlarda konum numaralandırması geriye doğru ilerler. preceding-sibling::p[1], belgedeki ilk paragraf değil, en yakın önceki paragraftır. Bunun yerine belge sırasını elde etmek için parantez içine alın. Bu konuyu XPath preceding-sibling başlığı altında ayrıntılı olarak ele aldık.
following ve preceding, kardeş öğelere karşılık gelen öğelerden çok daha geniş kapsamlı ve çok daha yavaştır; ayrıca sırasıyla ataları ve alt öğeleri hariç tutarlar. Yalnızca ilişki gerçekten gevşek olduğunda bu öğeleri kullanın.
Dize İşlevleri
En çok kullanılan işlevler.
| İşlev | Ne Yapar |
|---|---|
contains(a, b) | a adresindeki dizenin bir alt dizesi olarak b içeriyorsa True |
starts-with(a, b) | a adresindeki dize b ile başlıyorsa True |
normalize-space(s) | İç boşlukları kırpar ve birleştirir |
string-length(s) | Karakter sayımı |
substring(s, start, len) | Alt dize, 1'den başlayan |
substring-before(a, b) | b ifadesinin ilk geçtiği yerden önceki her şey |
substring-after(a, b) | b ifadesinin ilk geçtiği yerden sonraki her şey |
translate(s, from, to) | Karakter bazında değiştirme |
concat(a, b, ...) | Dizeleri birleştirir |
string(node-set) | Yalnızca ilk düğümün dize değeri |
Gerçek hataları önleyen üç not.
substring(), 1'den başlayan indekslemeye sahiptir. substring('hello', 1, 3), hel değerini döndürür. Herkes bunu en az bir kez yanlış yapar.
normalize-space(), herhangi bir metin karşılaştırmasında varsayılan sarmalayıcınız olmalıdır. Gerçek HTML, düzgün bir şekilde biçimlendirilmiştir; bu nedenle bir hücrenin dize değeri genellikle "In stock" yerine "\n In stock\n" şeklindedir. Argüman verilmediğinde, bağlam düğümü üzerinde çalışır.
XPath 1.0’da ends-with(), lower-case() ve düzenli ifadeler yoktur. Büyük/küçük harf duyarlılığını ortadan kaldırmak için, açıkça belirtilen harfleri içeren translate() kullanımı standart bir çözümdür. lxml gibi sunucu tarafı kütüphaneleri, re:test() dahil olmak üzere EXSLT uzantılarını destekler; tarayıcılar ve Selenium ise desteklemez.
Sayısal ve Boole İşlevleri
| İşlev | Ne Yapar |
|---|---|
count(node-set) | Düğüm sayısı |
position() | Bağlam düğümünün konumu |
last() | Bağlam düğüm kümesinin boyutu |
number(s) | Sayıya dönüştürür |
sum(node-set) | Sayısal değerleri toplar |
round(), floor(), ceiling() | Adından da anlaşıldığı gibi |
not(expr) | Boole olumsuzlaması |
boolean(expr) | Boole değerine dönüştürür |
true(), false() | Sabit Boole değerleri |
Karşılaştırma operatörleri şunlardır: =, !=, <, >, <=, >=; bunların birleştirilmesi için ise and ve or kullanılır. XML bağlamlarında < ifadesinin < şeklinde kaçış karakteriyle yazılması gerektiğine dikkat edin; bu nedenle bazen ifadelerin position() < 4 şeklinde yazıldığını görebilirsiniz.
Bilmeniz gereken ince bir nokta: bir düğüm kümesini bir değerle karşılaştırmak varoluşsal bir testtir. *//p = 'Price', herhangi bir paragrafın "Price" değerine eşit olması durumunda doğru sonuç verir. Bu durum genellikle bir yüklem içinde istediğiniz şeydir, ancak en üst düzeyde neredeyse hiçbir zaman istediğiniz şey değildir.
Sürekli Karşılaşılan Kalıplar
Gerçek iş gören ifadeler.
Bir sınıfı doğru şekilde eşleştirin — basit contains(@class, 'btn')
ifadesi, btn-primary
ve unbtn
adreslerini de eşleştirir:
//div[contains(concat(' ', normalize-space(@class), ' '), ' btn ')]
Bir değeri etiketine göre bulun — en yaygın veri çıkarma gereksinimi budur:
//dt[normalize-space()='Price']/following-sibling::dd[1]
//th[normalize-space()='Weight']/following-sibling::td[1]
//td[preceding-sibling::td[1]='SKU']
Bir hücreye göre bir satırı bulun, ardından farklı bir sütunu alın:
//tr[td[normalize-space()='SKU-1234']]/td[3]
İçindekilere göre bir konteyneri bulun:
//div[contains(@class,'card')][.//span[contains(., 'Sold out')]]
Başlıktan sonraki ilk öğeyi bulun:
//h2[normalize-space()='Specifications']/following-sibling::table[1]
Büyük/küçük harf duyarlı olmayan metin eşleşmesi:
//p[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz'), 'price')]
Dahil etmek yerine hariç tutma — genellikle daha anlaşılırdır:
//tr[not(contains(@class,'header'))]
//li[not(contains(normalize-space(),'Advertisement'))]
İki öğe türünden herhangi biriyle eşleştirme:
//*[self::button or self::a][normalize-space()='Continue']
Boş değerleri atla:
//td[string-length(normalize-space()) > 0]
Eşleşen bir öğeden bir özniteliği çıkar:
//a[normalize-space()='Download']/@href
Tuzaklar
Kullanıcılara ne kadar sık zaman kaybettirdiklerine göre sıralanmıştır.
Düğüm kümesinden dizeye dönüştürme işlemi yalnızca ilk düğümü alır. contains(//p, 'Price'), //p düğüm kümesinin tamamını, ilk paragrafı alarak geri kalanını göz ardı ederek bir dizeye dönüştürür. Bunun yerine, her düğüm için ayrı ayrı yüklem uygulayın: //p[contains(., 'Price')]. Bu, en yaygın XPath hatasıdır.
. ve text() farklıdır. Bir öğenin dize değeri, tüm alt metin düğümlerinin birleştirilmesiyle elde edilir; text() yalnızca doğrudan metin düğümü alt öğelerini döndürür ve dize dönüştürme işlemi bunlardan ilkini alır. İç içe geçmiş içeriği özellikle hariç tutmak istemediğiniz sürece . kullanın.
Ters eksen numaralandırması. preceding-sibling::td[1], ilk değil, en yakın olanı verir.
Boşluklar. //td[.='In stock'], düzgün biçimlendirilmiş HTML'de hata verir. normalize-space() bu sorunu giderir.
Büyük/küçük harf duyarlılığı. XML belgelerindeki öğe adları da dahil olmak üzere, XPath 1.0'daki her şey büyük/küçük harfe duyarlıdır.
Varsayılan ad alanları. Varsayılan ad alanı olan bir XML'de, //item hiçbir şeyle eşleşmez — bir önek kaydetmeniz ve onu kullanmanız gerekir. HTML ayrıştırıcıları genellikle sizi bu zahmetten kurtarır; XML ayrıştırıcıları ise kurtarmaz.
Tarayıcı geliştirici araçlarından gelen mutlak yollar. Bunlar, belirli bir andaki tam yapıyı kodlar ve herhangi bir değişiklikte çalışmaz hale gelir.
contains() ile aşırı eşleşme. "Price" ile eşleşme, "Historic Price" ile de eşleşir. Eşitlik kastettiğinizde normalize-space() = 'Price' kullanın.
Güvenilmeyen dizelerin enjeksiyonu. Kullanıcı girdisini bir ifadeye interpolasyonla eklemek, XPath enjeksiyonudur. Kütüphaneniz destekliyorsa değişken bağlamayı kullanın. XPath 1.0'da dize sabiti içindeki tırnak işaretleri için kaçış karakteri yoktur; bu nedenle her iki tırnak işaretini de içeren bir değer için concat() kullanılması gerekir.
XPath 1.0 ve Sonraki Sürümler
İnternette bulduğunuz bir kod parçacığı, ihtiyacınız olan yerde çalışmayabileceğinden bunu bilmeniz önemlidir.
XPath 1.0, tarayıcıların document.evaluate aracılığıyla uyguladığı, Selenium’un kullandığı ve lxml’in genel API’sinin sağladığı sürümdür. Yukarıda listelenen işlevlere sahiptir, daha fazlasını içermez.
XPath 2.0 ve 3.1, düzenli ifadeler (matches(), replace()), büyük/küçük harf işlevleri (upper-case(), lower-case()), ends-with(), dizi türleri, for ifadeleri ve daha pek çok özelliği ekler. Bunlar, XSLT 2.0+ işlemcilerinde ve bazı XML araçlarında kullanılabilir; ancak tarayıcılarda kullanılamaz.
Pratik kural: Bir ifade, yukarıdaki tablolarda yer almayan bir işlev kullanıyorsa, neden hata verdiğini araştırmadan önce ortamınızın bu işlevi destekleyip desteklemediğini kontrol edin. "Çevrimiçi bir XPath test aracında çalışıyor ama Selenium'da çalışmıyor" durumu neredeyse her zaman bundan kaynaklanır.
XPath 1.0'ın bıraktığı boşluklar için çözüm genellikle ana dilinizdir. XPath ile verileri ayıklayın, ardından Python veya JavaScript'te bir düzenli ifade uygulayın; burada eşleşen öğeleri de inceleyebilirsiniz.
Yeniden Tasarımdan Etkilenmeyen Seçiciler Yazmak
Bir hile sayfası size nelerin mümkün olduğunu gösterir; bu bölüm ise bu seçeneklerden hangisini seçmeniz gerektiğiyle ilgilidir, çünkü bir yıl dayanan bir seçici ile önümüzdeki Salı bozulacak bir seçici arasındaki fark, tamamen neye dayandırdığınıza bağlıdır.
Konuma değil, anlama dayandırın. (//table)[3]/tr[2]/td[4], bir anlık sayfanın tam şeklini kodlar. Biri üstüne bir tablo eklediğinde, tüm sayılar yanlış olur — ancak bu fark edilmez, çünkü ifade hâlâ bir şeyle eşleşir. //th[normalize-space()='Weight']/following-sibling::td[1], yeniden sıralamadan etkilenmeyen bir ilişkiyi kodlar; çünkü etiket, değerle birlikte hareket eder.
Oluşturulan özniteliklere kıyasla sabit öznitelikleri tercih edin. Geliştirici tarafından seçilen kimlikler (ID'ler) ve data-* öznitelikleri, stil ayarlarına dokunulduğunda değişen sınıf adlarından çok daha kalıcıdır. Birçok modern ön uç çerçeve, her derlemede değişen karma sınıf adları — css-1x9dj2k — oluşturur; bunlara dayandırmak, bozulmayı garantiler.
Herhangi bir seçici yazmadan önce gömülü yapılandırılmış verileri kontrol edin. Çok sayıda sayfa, arama özelliklerini desteklediği için <script type="application/ld+json"> bloğunda JSON-LD taşır. Bu verileri ayrıştırmak, işlenmiş HTML'yi ayrıştırmaktan çok daha kararlıdır; çünkü makine tarafından okunacak şekilde tasarlanmıştır ve görsel yeniden tasarımlardan tamamen etkilenmez. Bunu kontrol etmek için harcadığınız otuz saniye, bir öğleden sonra boyunca seçici bakımı yapmaktan kurtarabilir.
Çıkardığınız verinin biçimini doğrulayın. Bu, gürültüyle hata veren bir iş akışını sessizce hata veren bir iş akışından ayıran alışkanlıktır. Bir fiyatın bir para birimi deseniyle eşleşmesi gerekiyorsa, bunu kontrol edin. Bir kategori sayfasında hiç yirmi öğeden az olmamışsa, yirmi öğeden az olması bir sonuç değil, bir hata olarak değerlendirin. Yanlış öğeyle eşleşmeye başlayan bir seçici, makul, iyi biçimlendirilmiş, ancak yanlış veriler üretir — ve hiçbir noktada istisna atılmaz.
Seçicileri tek bir yerde tutun. Kod tabanına dağılmış kırk XPath dizesi, kırk ayrı bakım yükümlülüğü demektir. İsimlendirilmiş tek bir modülde toplandıklarında, bağımlı olduğunuz öğelerin bir haritasını oluştururlar ve yeniden tasarımdan sonra güncelleme işlemi bir gün yerine bir saat sürer.
Ve kaydedilmiş HTML'ye göre test edin. Ayrıştırdığınız her sayfanın bir kopyasını saklamak, bir seçici bozulduğunda eski işaretlemeyi yenisiyle karşılaştırabilmenizi ve tam olarak neyin değiştiğini görebilmenizi sağlar. Hata ayıklama için yeniden veri almak daha yavaştır, bant genişliği tüketir ve hata veren sayfadan farklı bir sayfa ile karşılaşmanıza neden olabilir.
Ne Zaman CSS Kullanılmalı?
XPath daha güçlü ancak okunabilirliği daha düşüktür. Seçim işlemlerinin büyük bir kısmı için doğru varsayılan seçenek CSS’dir.
Şu durumlarda CSS kullanın: sınıf, kimlik, öznitelik veya alt öğe ilişkisine göre seçim yapıyorsanız. div.card > p.price, XPath’in karşılıklarından daha anlaşılırdır, araçlar tarafından daha iyi desteklenir ve genellikle daha hızlıdır.
XPath'i şu durumlarda kullanın: metin içeriğine göre eşleştirme yapmanız gerektiğinde (ki bu, CSS ile hiç yapılamaz); bir üst öğeye veya ataya gitmeniz gerektiğinde; :nth-child ile ifade edilemeyecek şekilde kardeş öğelere göre konumsal mantığa ihtiyacınız olduğunda; veya HTML yerine XML sorguluyorsanız.
CSS'in bu açığı kısmen kapattığını unutmayın. :has() modern tarayıcılarda kardeş ve alt öğelere dayalı koşullu seçim olanağı sağlar, bu sayede dt:has(+ dd) artık ifade edilebilir. CSS'in hâlâ yapamadığı şey metne göre seçim yapmaktır ve metin eşleştirme tam da etiket-değer modelinin gerektirdiği şeydir.
Her ikisini tek bir kod tabanında birleştirmek hem uygun hem de mantıklıdır: Basit %90’luk kısım için CSS, zor %10’luk kısım için XPath.
Sık Sorulan Sorular
XPath ne için kullanılır?
XML ve HTML belgelerinde düğümler arasında gezinmek ve düğümleri seçmek için kullanılır. Uygulamada, web veri toplama, tarayıcı test otomasyonu ve XML yapılandırma ile veri dosyalarında sorgulama yapmak için kullanılır. CSS seçicilerinin ifade edemediği ilişkileri — üst düğümler, kardeş düğümler, metin içeriği — ifade eder.
XPath’te / ile // arasındaki fark nedir?
Tek eğik çizgi doğrudan alt düğümleri seçer; çift eğik çizgi ise herhangi bir derinlikteki alt düğümleri seçer. //div/p, hemen üst düğümü div olan paragraflarla eşleşirken, //div//p, div içindeki herhangi bir yerdeki paragraflarla eşleşir.
Neden XPath contains() ifadem hiçbir sonuç vermiyor?
Çoğu zaman bunun nedeni, ifadenize bir düğüm kümesi geçirmiş olmanızdır. XPath, bir düğüm kümesini belge sırasına göre ilk düğümü alarak ve geri kalanını atarak bir dizeye dönüştürür; bu nedenle contains(//p, 'x') her zaman yalnızca ilk paragrafı inceler. Bunun yerine //p[contains(., 'x')] yazın.
XPath'te sınıfa göre nasıl seçim yapabilirim?
//div[contains(concat(' ', normalize-space(@class), ' '), ' name ')] kullanın; bu, özniteliği doldurur ve böylece yalnızca tam token'lar eşleşir. Basit bir contains(@class, 'btn') ifadesi de btn-primary ile eşleşir. Yalnızca sınıflara göre seçim yapıyorsanız, bir CSS seçicisi varsayılan olarak daha açık ve doğrudur.
XPath, düzenli ifadeleri destekler mi?
Tarayıcılar ve Selenium'un uyguladığı XPath 1.0'da desteklenmez. XPath 2.0 ve sonraki sürümlerde matches() ve replace() eklenmiştir; ayrıca lxml gibi sunucu tarafı kütüphaneleri EXSLT'nin re:test()'sini destekler. Tarayıcı otomasyonu için, verileri XPath ile ayıklayın ve düzenli ifadeyi ana dilinizde uygulayın.
//div[1] ile (//div)[1] arasındaki fark nedir?
//div[1], her bir üst öğe için bir yüklem uygular ve her üst öğenin altında bir (muhtemelen birden fazla) düğüm içeren ilk div öğesini seçer. (//div)[1] ise tüm div öğelerini belge sırasına göre toplar ve ilkini alır; bu da tam olarak tek bir düğümdür.
XPath büyük/küçük harfe duyarlı mıdır?
Evet, her yerde — öğe adlarında, öznitelik adlarında ve dize karşılaştırmalarında. XPath 1.0'da lower-case() işlevi bulunmadığından, büyük/küçük harfe duyarlı olmayan eşleştirme için translate() kullanılarak büyük ve küçük harfler açıkça belirtilmelidir.
XPath mi yoksa CSS seçicileri mi kullanmalıyım?
Sınıflar, kimlikler, öznitelikler ve alt öğe ilişkileri için CSS kullanın; daha anlaşılır ve daha iyi desteklenir. Metin içeriğiyle eşleştirme yapmanız, üst öğelere gitmeniz veya CSS’in yapamadığı konumsal mantığı ifade etmeniz gerektiğinde XPath kullanın. Tek bir kod tabanında her ikisini de kullanmak normaldir.
Sonuç
XPath’in kullanışlı temel bileşenleri azdır: Herhangi bir yerde arama yapmak için //, filtreleme için köşeli parantez içindeki yüklemler, öznitelikler için @, birkaç dize işlevi ve tanımlayabileceğiniz bir öğeye göre gezinmek için kardeş eksenleri.
Üç alışkanlık, sorunların çoğunu önler. Metin karşılaştırmalarını normalize-space() içine alın, çünkü gerçek HTML düzgün biçimlendirilmiştir ve tam eşleşme başarısız olur. Bir predikatın içinde contains() uygulayın, böylece her düğüm için ayrı ayrı değerlendirilir; çünkü bir düğüm kümesinin dizeye dönüştürülmesi, sessizce sadece ilkini alır. Ayrıca, konuma göre sabitlemek yerine metne veya tanımlayıcılara göre sabitlemeyi tercih edin; çünkü yapı değişir, metin ise genellikle değişmez.
Sonra, hangi sürüm için yazdığınızı unutmayın. Tarayıcılar ve Selenium size XPath 1.0 sunar — düzenli ifadeler, lower-case() veya ends-with() yoktur — ve çevrimiçi bir test aracında çalışan bir ifade bunların hiçbirini kullanmasa bile, listenin ilerleyen kısımlarında belirtilen bir nedenden dolayı yine de başarısız olabilir. 1.0'da eksik olan bir şeye ihtiyacınız olduğunda, XPath ile veriyi ayıklayın ve geri kalanını ana dilinizde yapın.
