Geonode logo
Geonode Team

Geonode Team

Güncellenme: 7 Ekim 2026

Yayınlanma: 2 Eylül 2026

XPath contains(): Örneklerle Etraflı Bir Kılavuz

`contains()` Bu, kullanıcıların en sık başvurduğu ancak en az anladığı XPath işlevidir. Bir alt dize testi gibi görünür ve yalnızca ona bir dize verildiğinde öyle davranır. Bir düğüm kümesi verildiğinde — ki çoğu ifade bunu üretir — sessizce yalnızca ilk düğümü kullanır ve geri kalanını göz ardı eder. Bu kılavuz, söz konusu tuzağı, ilk seferde kimsenin doğru bir şekilde kullanamadığı sınıf eşleştirme kalıbını ve XPath 1.0'ın gereğinden fazla zorlaştırdığı boşluk ve büyük/küçük harf işleme konularını ele almaktadır.

Bunu neden yazdığımıza dair kısa bir not. Biz Geonode olarak, veri toplayan kişilere proxy satıyoruz; bu nedenle seçiciyle ilgili sorular bize sürekli geliyor. Konuyla ilgili uyarı kısa: Seçici hataları ile proxy sorunları tamamen farklı belirtiler gösterir ve bunları karıştırmak saatlerce zaman kaybına neden olur. Engellenen bir istek, bir doğrulama sayfası veya bir hata durumu döndürür. Bozuk bir contains() ise tamamen normal bir sayfa ve boş bir sonuç döndürür. HTML sayfası mevcutsa ve ifadeniz hiçbir şey bulamıyorsa, bu makale tam size göre ve ağınızda bir sorun yok demektir.

İşlevin Kendisi

XPath 1.0 spesifikasyonu oldukça kısa ve öz:

contains işlevi, ilk argüman dizesi ikinci argüman dizesini içeriyorsa true değerini, aksi takdirde false değerini döndürür.

Her iki argüman da dizedir. İki argüman, bir boole sonucu, büyük/küçük harf duyarlı, joker karakter yok, düzenli ifadeler yok.

//a[contains(@href, 'download')]
//div[contains(@class, 'product')]
//p[contains(text(), 'Price')]

İlginç olan davranış, sağladığınız argüman bir dize olmadığında neler olduğu ile ilgilidir; ki bu çoğu zaman böyledir.

Düğüm Kümesi Tuzağı: contains()

Yalnızca İlk Düğümü Görüyor Bu, "XPath'im bazı sayfalarda çalışıyor, bazılarında çalışmıyor" durumuna neden olan bir hatadır ve spesifikasyon bunu kesin olarak açıklamaktadır:

Bir düğüm kümesi, belge sırasına göre düğüm kümesindeki ilk düğümün dize değeri döndürülerek bir dizeye dönüştürülür. Düğüm kümesi boşsa, boş bir dize döndürülür.

Dolayısıyla, bir düğüm kümesi üreten bir kod yazıp bunu contains() adresine ilettiğinizde, XPath ilk düğüm dışındaki her şeyi sessizce atar.

<div>
  <p>Introduction</p>
  <p>Price: £42</p>
  <p>Availability</p>
</div>
contains(//p, 'Price')      → false

Yanlış, çünkü //p üç öğeden oluşan bir düğüm kümesidir; dize dönüştürme işlemi ilk öğeyi — "Introduction" — alır ve bu öğe "Price" kelimesini içermez. Diğer iki paragraf hiç dikkate alınmamıştır.

Düzeltme, kümeyi dönüştürmek yerine yüklemin her düğüm için ayrı ayrı uygulanmasını sağlamaktır:

//p[contains(., 'Price')]   → the second paragraph

Burada contains() , her p için bir kez değerlendirilir; . ise o tekil düğümdür. Bu, "küme bunu içeriyor mu?" ile "kümenin hangi elemanları bunu içeriyor?" soruları arasındaki farktır ve genellikle kastedilen sadece ikincisidir.

Aynı tuzak, yine bir düğüm kümesi olan text() ile de karşımıza çıkar:

//div[contains(text(), 'Price')]

text() , tüm doğrudan metin düğümü alt öğelerini döndürür, ancak dize dönüştürme işlemi yalnızca ilkini alır. Elemanın metni birkaç düğüme yayılmışsa — ki bu, iç içe geçmiş işaretleme olduğunda her zaman olur — yalnızca ilk parçayı test edersiniz.

.

ve text()

: Aynı Sorunun Diğer Yarı Spesifikasyon, bir öğenin dize değerini şu şekilde tanımlamaktadır:

belge sırasına göre, öğe düğümünün tüm metin düğümü alt öğelerinin dize değerlerinin birleştirilmesi

İşte bu, iki biçim arasındaki en önemli farktır.

<p>Total: <strong>£42.00</strong> including VAT</p>
contains(., '£42.00')          → true   (all descendant text, concatenated)
contains(text(), '£42.00')     → false  (first direct text node only: "Total: ")

.

, iç içe geçmiş öğelere ulaşır. text()

ise yalnızca doğrudan alt öğeleri ve bunlardan sadece ilkini görür.

**Varsayılan olarak .

kullanın.** Bu, bir okuyucunun "bu öğenin metni" olarak kabul edeceği şeyle eşleşir ve birinin bir değeri <span>

ile sarmalaması gibi işaretleme değişikliklerinden etkilenmez.

**text()

'i bilinçli olarak kullanın**, özellikle iç içe geçmiş içeriği hariç tutmak istediğinizde — örneğin, bir alt rozet veya araç ipucu içindeki metni eşleştirmeden bir etiketi eşleştirmek istediğinizde.

"Metin düğümlerinden herhangi biri bunu içeriyorsa" ifadesi için doğru biçim, yüklemi metin düğümlerinin kendisine uygular:

//div[text()[contains(., 'Price')]]

Ayrıntılı ve doğru.

Boşluklar: normalize-space() İsteğe Bağlı Değildir

Gerçek HTML, düzgün biçimlendirilmiştir ve boşluklar dize değerine dahil edilir.

<td>
    In stock
</td>

Bu hücrenin dize değeri "\n In stock\n" olduğundan, 'In stock' ile yapılan tam karşılaştırma başarısız olur.

Spesifikasyon, düzeltmeyi şu şekilde tanımlar:

normalize-space işlevi, baştaki ve sondaki boşlukları kaldırarak ve boşluk karakter dizilerini tek bir boşlukla değiştirerek boşlukları normalleştirilmiş argüman dizesini döndürür.

//td[normalize-space() = 'In stock']
//td[contains(normalize-space(), 'In stock')]

Argüman içermeyen normalize-space() işlevinin bağlam düğümü üzerinde çalıştığını unutmayın; bu, bir yüklem içinde istediğiniz şeydir.

Özellikle contains() için, boşluk karakterlerinin uçlarda önemi daha az, ortada ise çok daha fazladır. Önce normalleştirme yapmadığınız sürece, 'In stock' araması "In stock" ile karşılaştırıldığında başarısız olur. Seçicilerinizde tek bir alışkanlık yazacaksanız, metin karşılaştırmalarını normalize-space() ile sarmalamanızı öneririz.

Sınıfları Doğru Şekilde Eşleştirme

contains()

'ın en yaygın yanlış kullanımı ve sessizce hata vermesi en muhtemel olanıdır.

//div[contains(@class, 'btn')]

Bu, class="btn"

ile eşleşir. Ayrıca class="btn-primary"

, class="unbtn"

ve class="sidebar-btn-group"

ile de eşleşir. @class

, tek bir boşlukla ayrılmış bir dize olduğundan ve contains()

basit bir alt dize testi olduğundan, kelime sınırları hakkında hiçbir bilgisi yoktur.

Doğru yöntem, yalnızca tam tokenlerin eşleşmesi için hem özniteliği hem de hedefi boşluklarla doldurur:

//div[contains(concat(' ', normalize-space(@class), ' '), ' btn ')]

Şu şekilde okuyun: class özniteliğini alın, boşluklarını normalleştirin, her tokenin her iki tarafında da boşlukla sınırlandırılması için boşluklarla sarın ve boşluklarla çevrili hedefi arayın. class="btn-primary"

, " btn-primary "

olur; bu, " btn "

'i içermez. class="icon btn large"

, " icon btn large "

olur; bu ise içerir.

Bu çirkin bir çözümdür. Ancak aynı zamanda doğrudur ve her olgun veri kazıma kod tabanının sonunda ulaştığı sonuç budur. Bunu bir yardımcı fonksiyona sarın:

def has_class(name):
    return (f"contains(concat(' ', normalize-space(@class), ' '), ' {name} ')")

İki sınıfa ihtiyacınız olduğunda:

//div[contains(concat(' ', normalize-space(@class), ' '), ' btn ')
      and contains(concat(' ', normalize-space(@class), ' '), ' primary ')]

Bu noktada bir CSS seçicisi — div.btn.primary

— çok daha okunaklıdır ve tam olarak doğru işi yapar. Yalnızca sınıflara göre eşleştirme yapıyorsanız, CSS kullanın. XPath, metin eşleştirme veya geriye doğru gezinme gerektiğinde kullanılır; CSS’in zaten iyi bir şekilde yaptığı işler için değil. İkisini XPath preceding-sibling başlıklı yazımızda karşılaştırdık.

Büyük/Küçük Harf Duyarlılığı ve “translate()” Geçici Çözümü

“contains()” büyük/küçük harf duyarlıdır ve XPath 1.0’da “lower-case()” işlevi bulunmamaktadır. Tarayıcılar ve Selenium, XPath 1.0’ı uyguladığından, bu sınırlama en çok önem arz eden yerlerde geçerlidir.

Bu geçici çözüm, "ikinci argüman dizgesindeki karakterlerin, üçüncü argüman dizgesindeki karşılık gelen konumdaki karakterle değiştirildiği ilk argüman dizgesini" döndürmek üzere tanımlanan translate() işlevini kullanır:

//p[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                          'abcdefghijklmnopqrstuvwxyz'), 'price')]

Karakter bazında çeviri, yalnızca ASCII. Her iki dizgiyi de aksanlı karakterleri kapsayacak şekilde genişletmediğiniz sürece, aksanlı karakterleri küçük harfe dönüştürmez; bu da işleri hızla karmaşık hale getirir.

Mümkünse iki daha iyi seçenek vardır:

Büyük/küçük harf duyarlı olmayan bir alt dizgiyle eşleştirin. Sayfada "Price" veya "PRICE" yazıyor ama asla "price" yazmıyorsa, 'rice' ile eşleştirme çirkin görünse de işe yarar. Genellikle en pratik çözüm budur.

Daha zengin XPath özelliklerine sahip bir kütüphane kullanın. lxml gibi sunucu tarafı ayrıştırıcılar, büyük/küçük harf ayrımını ve daha pek çok şeyi işleyen düzenli ifadeler için re:test() dahil olmak üzere EXSLT uzantılarını destekler. Tarayıcılar ise desteklemez; bu nedenle lxml için bulduğunuz bir kod parçacığı, tam da bu nedenle Selenium'da çalışmayabilir.

Kendinizi uzun bir translate() yazarken bulursanız, bu, bu görev için XPath 1.0'ın sınırlarını aştığınızın bir işaretidir.

İlgili Dizgi İşlevleri

contains()

, küçük bir işlev ailesinin bir parçasıdır ve diğerleri genellikle daha kesindir.

**starts-with()

** — "ilk argüman dizgisi, ikinci argüman dizgisiyle başlıyorsa true değerini döndürür". contains()

işlevinden daha spesifiktir ve buna bağlı olarak aşırı eşleşme olasılığı daha düşüktür:

//a[starts-with(@href, 'https://')]

XPath 1.0'da ends-with()

işlevi bulunmamaktadır. Bu sorunu aşmak için substring()

ve string-length()

kullanılır; ancak bu yöntem o kadar zahmetlidir ki, genellikle farklı bir yaklaşım tercih edilir.

**substring-before()

ve substring-after()

** — ilki, "ikinci argüman dizesinin ilk geçişinden önceki birinci argüman dizesinin alt dizesini döndürür... ya da birinci argüman dizesi ikinci argüman dizesini içermiyorsa boş dizeyi döndürür". İfade içindeki bir değeri bölmek için kullanışlıdır:

substring-after(//span[@class='price'], '£')

**normalize-space()

** — yukarıda ele alınmıştır ve en çok kullanmanız gereken işlev budur.

**translate()

** — büyük/küçük harf dönüşümü ve karakterleri hiçbir şeye eşleyerek kaldırma:

translate(., ',', '')

**string-length()

** — boş veya kesik değerleri filtreleme:

//td[string-length(normalize-space()) > 0]

Bunları birleştirmek asıl değer yaratır:

//tr[contains(normalize-space(td[1]), 'Weight')]/td[2]

Herhangi bir satırın ilk hücresinde "Weight" geçen satırların ikinci hücresi, boşluklara duyarlı değildir.

Sıkça Karşılaşılan Kalıplar

Aşağıdaki ifadeler, gerçek veri çıkarma işlerinin çoğunu kapsar ve elinizin altında bulundurmanızda fayda vardır.

Bir etiketin yanındaki değeri bulun. Yapılandırılmış sayfaları tararken en sık karşılaşılan gereksinim:

//dt[contains(normalize-space(), 'Price')]/following-sibling::dd[1]
//th[contains(normalize-space(), 'Weight')]/following-sibling::td[1]

[1]

kısmına dikkat edin — bu olmadan, following-sibling::td

satırdaki sonraki tüm hücreleri döndürür ve siz tek hücre olduğunu varsayarken kodunuz sessizce ilkini alır.

Href yerine görünür metnine göre bir bağlantı bulun:

//a[contains(normalize-space(), 'Download')]

URL, karma bir tanımlayıcı olduğunda URL'yi eşleştirmekten daha sağlamdır, ancak site çevrildiğinde daha kırılgandır. Hangisinin daha sık değiştiğine göre seçim yapın.

İçindekilere göre bir konteyneri bulun:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')][.//span[contains(., 'Sold out')]]

Sırayla iki yüklem: "Sold out" yazan bir span içeren bir kart. Bu, tek bir koşulla ifade etmeye çalışmaktan daha iyi bir bileşim oluşturur ve daha akıcı okunur.

Dahil etmek yerine hariç tutun. Genellikle daha net bir ifade şekli:

//tr[not(contains(@class, 'header'))]
//li[not(contains(normalize-space(), 'Advertisement'))]

**Bir düğmeyi, ister button

ister a

olsun, eşleştirin:**

//*[self::button or self::a][contains(normalize-space(), 'Continue')]

Bir değeri içeren satırı bulun ve farklı bir sütunu alın:

//tr[td[contains(normalize-space(), 'SKU-1234')]]/td[3]

Dışa doğru okuyun: SKU'yu belirten bir hücreye sahip satırlar, ardından o satırın üçüncü hücresi. Bu, tablo arama modelidir ve sütunların yeniden sıralanmasından, tablonun tamamına yönelik mutlak bir indekse kıyasla çok daha iyi bir şekilde etkilenmez.

Boş eşleşmelere karşı önlem alın. Boşlukları filtreleyen bir predikat hiçbir maliyeti yoktur ve sonraki aşamalarda karışıklıkları önler:

//td[string-length(normalize-space()) > 0][contains(., 'Ltd')]

contains()'in Yanlış Araç Olduğu Durumlar

Eşitlik kastettiğinizde. contains(., 'Price'), "Historic Price" ve "Price excluding VAT" ile de eşleşir. Tam olarak bu etiketi istiyorsanız, normalize-space() = 'Price' kullanın. Aşırı eşleşme sessizce gerçekleşir — kodunuz ilk sonucu alır ve üç sonuç olduğunu asla bilmez.

Sadece sınıflarla eşleştirme yapıyorsanız. CSS bunu doğru ve okunabilir bir şekilde yapar. Yukarıya bakın.

Düzenli ifadeye ihtiyacınız olduğunda. XPath 1.0'da böyle bir şey yoktur. Gerekirse contains() ile veriyi ayıklayın, ardından ana dilinizde bir düzenli ifade uygulayın; burada neyin eşleştiğini de görebilirsiniz.

Kullanılabilir bir tanımlayıcı olduğunda. Bir id, bir data özniteliği veya gömülü JSON-LD, herhangi bir metin eşleşmesinden daha kararlıdır. Metin içeriktir ve içerik değişir — bir yeniden tasarım, bir çeviri veya bir metin düzenlemesi, metinle eşleşen bir seçiciyi bozar ve sizi uyaran hiçbir şey olmaz.

Dize kullanıcı girdisinden geldiğinde. Güvenilmeyen metni bir XPath ifadesine eklemek, XPath enjeksiyonudur. Varsa kütüphanenizin değişken bağlama özelliğini kullanın; yoksa uygun şekilde kaçış karakterleri ekleyin — özellikle tırnak işaretleri için, çünkü XPath 1.0'da dize sabiti içindeki tırnak işaretleri için kaçış dizisi yoktur ve bir tane oluşturmak içconcat()'i kullanmanız gerekir.

İnsanlar Ayrıca Şunu Soruyor

XPath'te contains() işlevi ne işe yarar?

İlk dizgi argümanı, ikinci argümanı bir alt dizgi olarak içeriyorsa true değerini döndürür. Her iki argüman da dizgidir, karşılaştırmada büyük/küçük harf duyarlılığı vardır ve joker karakterler veya düzenli ifadeler kullanılmaz. Çıkarma işlemlerinde genellikle, bir öğeyi metninin veya öznitelik değerinin bir kısmıyla eşleştirmek için kullanılır.

Neden XPath contains() işlevim hiçbir şey bulamıyor?

Çoğu zaman bunun nedeni, işlevine bir düğüm kümesi (node-set) geçirmiş olmanızdır. XPath, bir düğüm kümesini, belge sırasına göre ilk düğümü alarak geri kalanını yok sayarak bir dizeye dönüştürür; bu nedenle contains(//p, 'x') işlevi her zaman yalnızca ilk paragrafı inceler. Bunun yerine, yüklemi düğüm başına uygulayın: //p[contains(., 'x')].

contains(.) ile contains(text()) arasındaki fark nedir?

., spesifikasyonda tüm alt metin düğümlerinin birleştirilmesi olarak tanımlanan öğenin dize değerini kullanır; dolayısıyla iç içe geçmiş işaretleme yapısına da ulaşır. text() ise doğrudan metin düğümü alt öğelerini döndürür ve dize dönüştürme işlemi yalnızca ilkini alır. İç içe geçmiş içeriği özellikle hariç tutmak istemediğiniz sürece . kullanın.

XPath ile bir sınıfa nasıl eşleşme yapabilirim?

contains(concat(' ', normalize-space(@class), ' '), ' name ') kullanın; bu, özniteliği doldurur ve böylece yalnızca tam token'lar eşleşir. Düz bir contains(@class, 'btn'), btn-primary ve unbtn ile de eşleşir. Yalnızca sınıflara göre eşleşme yapıyorsanız, bir CSS seçicisi varsayılan olarak daha açık ve doğrudur.

XPath contains() işlevi büyük/küçük harfe duyarlı mıdır?

Evet, ayrıca XPath 1.0'da lower-case() işlevi yoktur. Standart çözüm, yalnızca ASCII karakterlerini işleyen ve büyük/küçük harfleri açıkça belirten translate() biçimidir. lxml gibi sunucu tarafı kütüphaneleri EXSLT düzenli ifadelerini destekler; tarayıcılar ve Selenium ise desteklemez.

contains() işlevini birden fazla koşulla nasıl kullanabilirim?

Yargıları and ve or ile birleştirin: //div[contains(@class, 'card') and contains(., 'In stock')]. Her bir contains(), aynı bağlam düğümü üzerinde değerlendirilen ayrı bir boole testidir.

XPath’te “başlangıçta” veya “sonunda” işlevi var mı?

starts-with() işlevi mevcuttur ve uygun olduğu durumlarda contains() yerine tercih edilmelidir, çünkü daha az fazla eşleşme verir. XPath 1.0’da ends-with() işlevi yoktur — geçici çözüm olarak substring() ile string-length() kullanılır, ancak bu yöntem o kadar zahmetlidir ki genellikle farklı bir yaklaşım daha iyidir.

contains() neden beklenenden daha fazla öğeyle eşleşiyor?

Çünkü bu, kelime sınırları kavramını içermeyen bir alt dize testi. contains(., 'Price'), "Historic Price" ve "Price excluding VAT" ile de eşleşir. Eşlik için normalize-space() = 'Price' kullanın veya sınıf belirteçleri için "padded-concat" deyimini kullanın.

Sonuç

contains(), teoride basit olsa da pratikte pek çok tuzak barındırır ve bunların neredeyse tamamı tek bir kaynaktan kaynaklanır: XPath, bir düğüm kümesini ilk düğümü alarak geri kalanını atarak bir dizeye dönüştürür. Bu tek kural, contains(//p, 'x') adresindeki ifadenin neden kesin bir şekilde yanlış sonuç verdiğini, contains(text(), 'x') adresindeki ifadenin neden düğümler arasına bölünmüş metni gözden kaçırdığını ve aynı ifadenin neden bir sayfada çalışıp bir sonraki sayfada başarısız olduğunu açıklar.

Bunu önleyecek birkaç alışkanlık vardır. contains() ifadesini bir yüklem içinde kullanın, böylece düğüm başına değerlendirilmesini sağlayın. Bir nedeniniz olmadığı sürece text() yerine . kullanın. Metin karşılaştırmalarını normalize-space() ile sarın, çünkü gerçek HTML düzgün bir şekilde biçimlendirilmiştir. Sınıf eşleştirme için ise ya padded-concat deyimini kullanın ya da — daha iyisi — tam da bu iş için tasarlanmış bir CSS seçicisi kullanın.

XPath'i, yalnızca kendisine özgü işlevleri için ayırın: metin eşleştirme ve geriye doğru gezinme. Bunlar, CSS'de karşılığı olmayan gerçek yeteneklerdir ve bu sözdizimini kullanmaya değer. div.card adresini seçmek için XPath'i kullanmak, faydasını görmeden bedelini ödemek anlamına gelir.