Bunu kimin yazdığına ve bunun burada neden pek önemi olmadığına dair bir not: Biz Geonode olarak, veri çıkarma çalışmaları yapan kişilere proxy satıyoruz. Seçici seçimi, sattığımız ürünlerle hiçbir şekilde ilgisi yoktur ve bahsetmeye değer tek ortak nokta teşhis amaçlıdır — hiçbir şeyle eşleşmeyen bir seçici ile engellenen bir istek, dışarıdan bakıldığında aynı görünür, ancak bunlar birbiriyle tamamen ilgisiz sorunlardır. Sayfa yüklendiyse ve seçiciniz hiçbir şey bulamadıysa, bu referans doğru yerdir ve sayfanın nasıl getirildiğiyle ilgili hiçbir şeyin değiştirilmesine gerek yoktur.
Temel Seçiciler
| Seçici | Eşleşenler |
|---|---|
* | Her öğe |
div | Her div öğesi |
.card | card sınıfına sahip öğeler |
#main | main kimliğine sahip öğe |
.card.featured | Her iki sınıfa sahip öğeler |
div.card | card sınıfına sahip div öğeleri |
div, p | div sınıfına sahip öğeler ve p sınıfına sahip öğeler |
Bunlardan ikisi üzerinde durmaya değer.
** Boşluksuz.card.featured, tek bir öğede her iki sınıfın da bulunduğunu ifade eder.** Boşluklu .card .featured, card sınıfına sahip bir öğenin içinde featured sınıfına sahip bir öğe olduğunu ifade eder. Tek bir karakter, anlamı tamamen değiştirir ve bu, bu alandaki en yaygın yazım hatasıdır.
div, p bir seçici listesidir, bir ilişki değildir. Her iki gruptaki her şeyle eşleşir ve her bir parça bağımsız olarak değerlendirilir — dolayısıyla div, p.note, note sınıfına sahip div'leri değil, note sınıfına sahip tüm div'leri ve paragrafları eşleştirir.
Birleştiriciler
Bunlar, öğeler arasındaki ilişkileri ifade eder.
| Seçici | Eşleşmeler |
|---|---|
div p | div öğesinin herhangi bir yerinde bulunan p (alt öğe) |
div > p | div öğesinin doğrudan alt öğesi olan p |
h2 + p | h2 öğesinin hemen ardından gelen p (bitişik kardeş) |
h2 ~ p | Aynı ebeveyne sahip bir h2 öğesinin hemen ardından gelen her p öğesi (genel kardeş) |
Türdürücü bir boşluktur; bu da onu yanlışlıkla eklemeyi kolaylaştırır ve okurken gözden kaçırmayı kolaylaştırır.
> daha kesin ve genellikle daha sağlamdır. div > p, gelecek ay birinin <section> içine sığdırdığı bir paragrafla eşleşmeyecektir — bu bir dezavantaj gibi görünse de genellikle tam tersidir; çünkü amaçlanandan daha fazla öğeyle eşleşmeye sessizce başlayan bir seçici, eşleşmeyi durdurup size bildiren bir seçiciden daha kötüdür.
+ ve ~ kardeş birleştiricilerdir ve yalnızca ileriye doğru bakar. CSS’de “önceki kardeş” birleştiricisi yoktur; :has() bu etkiyi dolaylı olarak sağlar; bu konu aşağıda ele alınacaktır.
Öznitelik Seçicileri
Öznitelikler genellikle bilinçli olarak seçildiğinden, çoğu zaman mevcut en güvenilir seçenektir.
| Seçici | Eşleşenler |
|---|---|
[href] | href özniteliğine sahip öğeler |
[href="/about"] | Tam değer |
[href^="/docs"] | Değer ile başlar |
[href$=".pdf"] | Değer ile biter |
[href*="download"] | Değer içerir |
[class~="card"] | Değer, card kelimesini içeren boşlukla ayrılmış bir listedir |
[lang|="en"] | Değer en şeklindedir veya en- ile başlar |
[data-state="open" i] | Büyük/küçük harf duyarlı olmayan eşleşme |
İkisi de yeterince kullanılmamaktadır.
[class~="card"], .card ile tam olarak eşdeğerdir — ~= operatörü, sınıf özniteliğinin ihtiyaç duyduğu boşluklarla ayrılmış kelime eşleşmesini yapar. Bunun varlığını bilmek, esas olarak .card'in gerçekte ne yaptığını anlamak ve değişken içinde bir öznitelik adı bulunan durumlarda programlı olarak seçiciler oluşturmak için yararlıdır.
i bayrağı, eşleşmede büyük/küçük harf duyarlılığını ortadan kaldırır; bu, site genelinde büyük/küçük harf kullanımı değişkenlik gösteren öznitelik değerleri için gerçekten kullanışlıdır.
Özellikle veri çıkarma söz konusu olduğunda, data-* öznitelikleri, sınıflara göre açıkça tercih edilmelidir. Bir [data-testid="product-card"], geliştirici tarafından istikrarlı bir bağlantı noktası olarak seçilmiştir; bir sınıf adı ise her dağıtımda bir derleme aracı tarafından yeniden oluşturulabilir.
Sözde Sınıflar: Yapısal
| Seçici | Eşleşmeler |
|---|---|
:first-child | Üst öğesinin ilk alt öğesi olan bir öğe |
:last-child | Son alt öğe |
:only-child | Kardeş öğesi olmayan bir öğe |
:nth-child(3) | Üçüncü alt öğe |
:nth-child(2n) | Çift numaralı alt öğeler |
:nth-child(2n+1) | Tek numaralı alt öğeler |
:nth-child(-n+3) | İlk üç öğe |
:nth-last-child(2) | Sondan ikinci öğe |
:first-of-type | Kardeş öğeler arasında kendi türünün ilk öğesi |
:nth-of-type(2) | Kendi türünün ikinci öğesi |
:empty | Metin dahil olmak üzere alt öğesi olmayan bir öğe |
:root | Belgenin kökü, genellikle html |
-of-type varyantları, insanların fark ettiğinden daha önemlidir. p:first-child, yalnızca ebeveyn öğesinin ilk alt öğesi ise bir paragrafla eşleşir — yani başlık önce geliyorsa, hiçbir şeyle eşleşmez. p:first-of-type, öncesinde ne olursa olsun ilk paragrafla eşleşir. Veri çıkarma için, neredeyse her zaman ikinci seçenek istediğiniz sonuçtur.
** "İlk üç" için:nth-child(-n+3)**, An+B sözdiziminden ezberlemeye değer bir deyimdir; "dördüncü ve sonrası" için ise :nth-child(n+4) kullanılır. Bunları birleştirerek bir aralık elde edersiniz: :nth-child(n+2):nth-child(-n+5), ikinci ile beşinci alt öğeleri seçer.
Sözde Sınıflar: Mantıksal
Son yıllarda en çok değişime uğrayan grup.
| Seçici | Eşleşmeler |
|---|---|
:not(.hidden) | hidden sınıfı olmayan öğeler |
:is(h1, h2, h3) | Listelenen seçicilerden herhangi biri |
:where(h1, h2, h3) | :is() ile aynıdır, ancak özgüllüğü sıfırdır |
:has(> img) | Eşleşen bir alt öğe veya kardeş öğe içeren öğeler |
:is() ve :where(), tekrarlayan listeleri kısaltır. :is(article, section) > h2, article > h2, section > h2 yerine geçer ve liste uzadıkça bu kısaltma sayesinde zaman tasarrufu hızla artar. Aralarındaki tek fark özgüllüktür: :is() en özgül argümanının özgüllüğünü alır, :where() ise her zaman sıfır değer verir.
:not(), modern tarayıcılarda bir seçici listesi kabul eder, bu nedenle :not(.a, .b) çalışır ve "hiçbiri" anlamına gelir. Eski tek argümanlı davranışta :not(.a):not(.b) zincirlenmesi gerekiyordu.
:has() en önemlisidir. MDN belgeleri, bunu "argüman olarak geçirilen göreceli seçicilerden herhangi biri, bu öğeye göre sabitlendiğinde en az bir öğeyle eşleşirse o öğeyi temsil eder" şeklinde tanımlar ve "referans öğeye göre bir üst öğeyi veya önceki bir kardeş öğeyi seçmenin bir yolunu" sağlar. Durumu "Baseline widely available" olup, Aralık 2023'ten beri tüm tarayıcılarda desteklenmektedir.
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 */
label:has(input:checked) /* a label wrapping a checked input */
Belgelenmiş üç sınırlama vardır: başka bir :has() içinde "iç içe yerleştirilemez", içinde veya bağlantı noktası olarak sözde öğeler geçerli değildir ve özgüllüğü, :is() ve :not() ile aynı kurala uyar — en özgül argüman.
Sözde Sınıflar: Durum ve Form
| Seçici | Eşleşmeler |
|---|---|
:hover, :focus, :active | Etkileşim durumları |
:focus-visible | Görünür bir halka göstermesi gereken odak |
:focus-within | Odaklanmış bir alt öğe içeren öğe |
:checked | İşaretlenmiş onay kutusu, radyo düğmesi veya seçili seçenek |
:disabled, :enabled | Form denetim durumu |
:required, :optional | Form doğrulama öznitelikleri |
:valid, :invalid | Kısıtlama doğrulama durumu |
:placeholder-shown | Yer tutucusunu gösteren bir giriş |
:target | URL parçasıyla eşleşen öğe |
:visited, :link | Bağlantı durumları |
İki istisna dışında, bunların çoğu veri çıkarma sorunlarından ziyade stil sorunlarıdır. :checked, test otomasyonunda form durumunu okumak için gerçekten kullanışlıdır ve :disabled, bir kontrolün etkileşimli olup olmadığını gösterir — bu da genellikle bir tıklamanın neden hiçbir şey yapmadığını açıklar.
:hover ve :focus adreslerinin, veri kazıma bağlamında querySelectorAll ile anlamlı bir şekilde sorgulanamayacağını unutmayın; çünkü sadece ayrıştırdığınız bir belgede bu durumlar mevcut değildir.
Özgüllük, Kısaca
Seçiciler stil uygulaması konusunda rekabet ettiğinde önemlidir; sorgulama için ise önemsizdir — bu iki bağlam arasında sık sık karışıklık yaşandığı için bilinmesi faydalıdır.
Özgüllük, üç sayı olarak hesaplanır:
| Bileşen | Katkısı |
|---|---|
| Kimlikler | #main |
| Sınıflar, öznitelikler, sözde sınıflar | .card, [href], :hover |
| Öğeler, sözde öğeler | div, ::before |
Soldan sağa karşılaştırıldığında: herhangi bir sayıda sınıf, tek bir kimlikten asla daha ağır basmaz. Satır içi stiller hepsinden üstündür ve !important da onlardan üstündür; bu nedenle her ikisi de bir araçtan ziyade son çare olarak kullanılır.
İki modern not. :where(), özgüllük katkısı sağlamaz; bu da onu, kullanıcıların zorlanmadan geçersiz kılabilmesi gereken kütüphane varsayılanları için doğru seçim haline getirir. Ayrıca :is(), :not() ve :has(), en özgül argümanlarının özgüllüğünü alır; dolayısıyla :is(#main, div), #main kadar özgüldür.
querySelectorAll ve scraping için ise özgüllük kavramı hiç geçerli değildir. Seçici, bir öğeyle eşleşir ya da eşleşmez; hiçbir şey birbiriyle rekabet etmez.
Veri Çıkarma Şablonları
Bir veri kazıyıcıda asıl işi yapan seçiciler.
Izgara düzenindeki ürün kartları:
div.product-grid > article.product-card
PDF’lere bağlantılar:
a[href$=".pdf"]
Dış bağlantılar:
a[href^="http"]:not([href*="example.com"])
Sınıf yerine kararlı bir test kancası:
[data-testid="price"]
İçinde bir şey bulunan bir kap:
tr:has(td.error)
article:has(img)
form:has(input:invalid)
Öncesinde ne olursa olsun, bir makalenin ilk paragrafı:
article p:first-of-type
Başlık satırını atla:
tbody tr:not(:first-child)
İkinci satırdan on birinci satıra kadar olan satırları al:
tbody tr:nth-child(n+2):nth-child(-n+11)
Programla oluşturulmuş, belirli bir sınıf belirtecine sahip öğeler:
[class~="card"]
Hariç tutmak istediğiniz boş hücreler:
td:not(:empty)
Bozulmayan Seçiciler Yazmak
Bir referans, nelerin mevcut olduğunu gösterir. Seçenekler arasından seçim yapmak, dayanıklılığın kaynağıdır; yeniden tasarımdan sağ çıkacak bir seçici ile gelecek hafta bozulacak bir seçici arasındaki fark, tamamen o seçicinin neye bağlı olduğuna bağlıdır.
Bağlantılarınızı, ne kadar kasıtlı olarak seçildiklerine göre sıralayın. Bir data-testid, bir geliştiricinin otomasyon amacıyla oraya koyması nedeniyle mevcuttur; dolayısıyla sayfadaki en kararlı öğedir. İkinci sırada id gelir — genellikle kasıtlı olarak verilir, bazen de otomatik olarak oluşturulur. Anlamsal bir öğe veya ARIA rolü, anlam taşıdığı için kararlıdır. Elle yazılmış bir sınıf adı orta derecede kararlıdır. css-1x9dj2k gibi otomatik olarak oluşturulan bir sınıf adı her derlemede değişir ve hiçbir değeri yoktur. Konumsal seçiciler ise en kararsız olanlardır, çünkü sayfanın o anki tam şeklini kodlarlar.
Anlamın gerektirdiği kadar spesifik olun, daha fazlasını değil. body > div > div > div.content > p kesin ama kırılgandır: bu zincirin herhangi bir yerine eklenen herhangi bir sarmalayıcı onu bozar. .content p gerçek gereksinimi ifade eder ve yeniden yapılandırmalardan etkilenmez. Güvenlik için spesifiklik ekleme içgüdüsü, güvenliğin tam tersini doğurur.
Hatalı olduğunda bunu açıkça gösteren seçicileri tercih edin. Sıfır öğeyle eşleşen bir seçici, bariz bir sorun yaratır. Çok fazla öğeyle eşleşen bir seçici ise sessizce ilk öğeyi döndürür; bu makul görünse de muhtemelen yanlıştır. Seçim yapma imkânınız varsa, sapma gösterecek versiyon yerine hata verecek versiyonu tercih edin — ilişki gerçekten doğrudansa, div > p yerine div p kullanın.
Seçicileri tek bir yerde tutun. Kod tabanına dağılmış kırk seçici dizesi, kırk bağımsız sorundur. Açıklayıcı adlarla tek bir modülde toplandıklarında, bağımlı olduğunuz şeylerin bir haritası haline gelirler ve site değişikliğinden sonra güncelleme yapmak, bir günlük arkeolojik çalışma yerine bir saatlik bir iş haline gelir.
Çıkardığınız şeyin biçimini doğrulayın. Bu, fark edilmeyen sapmaları yakalayan bir alışkanlıktır. Bir fiyatın belirli bir para birimi formatına uyması gerekiyorsa, bunu kontrol edin. Bir listede hiç yirmiden az öğe bulunmamışsa, yirmiden az olanları hata olarak değerlendirin. Bu yapılmazsa, yanlış öğeyle eşleşmeye başlayan bir seçici, sonsuza kadar biçimsel olarak doğru ancak yanlış veriler üretir ve hiçbir aşamada bir istisna ortaya çıkmaz.
Ayrıştırdığınız HTML'yi kaydedin. Bir seçici bozulduğunda, eski işaretlemeyi yenisiyle karşılaştırmak, saniyeler içinde tam olarak neyin değiştiğini gösterir. Araştırmak için yeniden veri almak daha yavaştır, bant genişliği tüketir ve size aslında hata veren sayfadan farklı bir sayfa sunabilir.
CSS’in Hâlâ Yapamadığı Şeyler
Kısa bir liste; listedeki her madde, XPath’e başvurmak için geçerli bir nedendir.
Metin içeriğine göre eşleştirme. Standart bir :contains() yoktur. Önerilmiş ancak reddedilmiştir ve hiçbir tarayıcı bunu uygulamamaktadır. Bu, en büyük eksikliktir ve "etiketine göre bir değer bulma" modelinin XPath'in alanı olmasının nedenidir — //dt[normalize-space()='Price']/following-sibling::dd[1]'in CSS'de bir karşılığı yoktur.
İstenilen bir üst öğeye gitme. :has() koşullu üst öğe seçimi sağlar — "bu span'i içeren div" — ancak genel bir üst öğe ekseni yoktur. Belirli bir kapsayıcıya birkaç seviye yukarı çıkmak için XPath gerekir.
Dize işlevleri. Dize kesme, alt dize alma, büyük/küçük harf dönüştürme veya birleştirme yoktur. Eşleştirdiğiniz her ne olursa olsun, bunu daha sonra ana dilinizde işlersiniz.
Ad alanları içeren XML sorguları. CSS’de kayda değer bir ad alanı desteği yoktur. Site haritaları, RSS veya SOAP için XPath kullanın.
Önceki kardeş öğeleri doğrudan seçme. :has() bu işlevi sağlar — h2:has(+ p) başlığı seçer — ancak +'i yansıtacak bir - birleştiricisi yoktur.
Pratik amaçlar için liste bu kadar. Kullanıcıların XPath'i kullanarak yapmaya çalıştıkları diğer her şeyi artık CSS yapabilir ve genellikle daha okunaklı bir şekilde.
Kontrol Edilmesi Gereken Ortam Farklılıkları
Seçicilerle ilgili en sık karşılaşılan sürpriz, bir özelliğin onu çalıştırdığınız yerden başka bir yerde mevcut olmasıdır.
Tarayıcılar, :has(), :is(), :where() ve çoklu argümanlı :not() dahil olmak üzere burada listelenen her şeyi destekler.
Sunucu tarafındaki ayrıştırıcılar önemli ölçüde farklılık gösterir. lxml ve Scrapy tarafından kullanılan Python'un cssselect, CSS'yi XPath'e çevirir — bu, klasik seçiciler için iyi çalışır, ancak yeni seçiciler için o kadar iyi değildir. Özellikle :has() desteği evrensel değildir ve bir tarayıcı konsolunda çalışan bir seçici, bir ayrıştırıcıda hata verebilir veya sessizce hiçbir şey döndürmeyebilir.
BeautifulSoup, select() yöntemi için soupsieve kullanır; bu yöntem iyi bir kapsama alanına sahiptir ancak tam olarak aynı değildir ve XPath desteği hiç yoktur.
Playwright ve Selenium, tarayıcının motorunu kullanır; bu nedenle destek, tarayıcıyla aynıdır. Ayrıca Playwright, CSS'in ifade edemediği birçok şeyi kapsayan metin ve rol konum belirleyicileri de sunar.
Pratik bir alışkanlık: seçicileri, çalışacakları ortamda test edin. Tarayıcı konsolu kullanışlıdır, ancak JavaScript sonrası DOM'a karşı farklı bir uygulamayı test eder; bu da sizi yanıltabilecek iki faktördür.
Sık Sorulan Sorular
.card.featured ile .card .featured arasındaki fark nedir?
Boşluk içermeyen ilk seçici, her iki sınıfa da sahip tek bir öğeyi eşleştirir. Boşluk içeren ikinci seçici ise, card sınıfına sahip bir öğenin içindeki featured sınıfına sahip bir öğeyi eşleştirir. Tek bir boşluk, anlamı tamamen değiştirir ve bu, CSS seçicilerinde en sık yapılan hatadır.
CSS'de üst öğeyi nasıl seçerim?
:has() ile — div:has(> span.price), span yerine div öğesini seçer. Bu özellik, Aralık 2023'ten beri tarayıcılarda yaygın olarak kullanılmaktadır. Genel bir atası seçici bulunmadığından, birkaç seviye yukarıdaki belirli bir kapsayıcıya ulaşmak için hâlâ XPath kullanılması gerekir.
CSS seçicileri metinle eşleşebilir mi?
Hayır. Standart bir :contains() sözde sınıfı yoktur; bu önerilmiş ancak hiçbir zaman benimsenmemiştir. Metin içeriğine göre eşleştirme yapmak için XPath veya Playwright'ın getByText() gibi kütüphaneye özgü bir konum belirleyici gerekir.
:nth-child ile :nth-of-type arasındaki fark nedir?
:nth-child, üst öğenin tüm alt öğelerini sayar; :nth-of-type ise yalnızca aynı türdeki öğeleri sayar. Dolayısıyla, başlık önce geliyorsa p:first-child hiçbir şeyle eşleşmezken, p:first-of-type başlık gelse de gelmese de ilk paragrafla eşleşir.
:is() ne işe yarar ve :where()'dan farkı nedir?
Her ikisi de bir seçici listesi alır ve bunlardan herhangi biriyle eşleşir; böylece tekrarlayan seçicileri kısaltır. Tek fark, özgüllüktür: :is() en özgül argümanının özgüllüğünü benimserken, :where() her zaman sıfır değer verir — bu da :where()'i, kolayca geçersiz kılınabilmesi gereken varsayılan değerler için ideal hale getirir.
CSS özgüllüğü nasıl çalışır?
Bileşenler tek tek karşılaştırıldığında, kimlikler (id'ler) sınıfları, öznitelikleri ve sözde sınıfları geride bırakır; bunlar da öğe ve sözde öğe seçicilerini geride bırakır. Satır içi stiller seçicilerden üstündür ve !important ise seçicilerden üstündür. Stil vermek yerine sorgu yapmak için seçiciler kullandığınızda bunların hiçbiri geçerli değildir.
Veri kazıma için CSS seçicileri XPath'ten daha mı iyidir?
Sınıf, kimlik, öznitelik ve yapısal seçim için evet — daha kısa ve daha nettir. Metin içeriğini eşleştirmek veya rastgele bir üst öğeye doğru ilerlemek için XPath gereklidir. Çoğu veri kazıma kütüphanesi her ikisini de destekler ve her birini uygun olduğu yerde kullanmak normaldir.
Seçicim tarayıcıda çalışıyor ama veri kazıyıcımda neden çalışmıyor?
Bunun iki yaygın nedeni vardır. Tarayıcının DOM’u JavaScript işledikten sonraki halini yansıtırken, ayrıştırıcınız ham HTML’yi görür; bu nedenle, öğe aldığınız verilerde mevcut olmayabilir. Ayrıca, sunucu tarafındaki CSS uygulamaları tüm modern seçicileri kapsamaz — özellikle :has() seçicisi, tarayıcılar dışında tutarsız bir şekilde desteklenir.
Sonuç
CSS seçicilerinin işlevsel çekirdeği küçüktür: kimlik için tür, sınıf ve kimlik (id); ilişkiler için dört birleştirici; kasıtlı olarak seçilen her şey için öznitelik seçicileri; konum ve durum için ise bir avuç sözde sınıf.
Ek sözdizimini bilmekten çok, üç alışkanlık gerçek seçicileri daha da iyileştirir. İlişkinin gerçekten doğrudan olduğu durumlarda, boşluk yerine > kullanmayı tercih edin; çünkü eşleşmeyi durduran bir seçici, gereğinden fazla eşleşmeye başlayan bir seçiciden daha kullanışlıdır. Sınıf adları yerine data-* özniteliklerini tercih edin; çünkü sınıflar derleme araçları tarafından yeniden oluşturulurken, öznitelikler insanlar tarafından seçilir. Ayrıca, “ikinci çocuk” yerine “ikinci paragraf” demek istediğinizde, :nth-child yerine :nth-of-type kullanmayı tercih edin.
:has() Bu ekleme, bilinçli olarak benimsenmeye değer mi? Düz CSS’deki üst öğe seçimi ve kardeş öğe koşulları, daha önce kullanıcıları XPath’e yönlendiren sorunların büyük bir kısmını ortadan kaldırır ve tarayıcılarda güvenilir bir şekilde kullanılabilmesi için yeterince uzun süredir yaygın olarak kullanılmaktadır. Tarayıcıların dışında ise önce ayrıştırıcınızı kontrol edin — geriye kalan sürprizler genellikle orada yatmaktadır.
