Geonode logo
Geonode Team

Geonode Team

Diperbarui: 7 Oktober 2026

Diterbitkan: 2 September 2026

XPath Berdasarkan Kelas: Panduan dengan Contoh

Pemilihan berdasarkan kelas adalah hal yang paling umum dilakukan oleh siapa pun dengan selektor, dan hal itulah yang paling buruk dilakukan oleh XPath. Ekspresi yang pertama kali ditulis oleh semua orang — `//div[@class='card']` — hanya cocok dengan elemen yang atribut class-nya persis sama dengan string tersebut, sehingga tidak mencocokkan `class="card featured"`. Perbaikan yang tampak jelas, `contains(@class, 'card')`, juga mencocokkan `card-large` dan `discard`. Keduanya tidak benar. Berikut ini yang benar, beserta kapan sebaiknya menggunakan selektor CSS sebagai gantinya.

Sikap kami mudah dijelaskan: kami adalah Geonode dan kami menjual proxy, yang sama sekali tidak ada hubungannya dengan pemilihan kelas. Satu hal yang perlu diperhatikan adalah bahwa selektor berbasis kelas adalah jenis yang paling rentan, dan kerentanan ini terlihat persis seperti masalah jaringan dari luar — permintaan berhasil, halaman dimuat, namun ekstraksi Anda tidak menghasilkan apa-apa atau menghasilkan data yang salah. Jika Anda sedang men-debug scraper yang berhenti berfungsi, periksa apakah nama kelasnya berubah sebelum memeriksa hal lain terkait cara halaman tersebut diambil. Pada situs yang menggunakan pipeline pembangunan modern, nama kelas tersebut mungkin berubah setiap kali dilakukan deploy.

Masalah dengan Atribut Class

class berisi daftar token yang dipisahkan spasi, dan XPath tidak mengenal konsep daftar tersebut. XPath hanya melihat satu string.

<div class="card featured large">...</div>

Bagi XPath, @class adalah string "card featured large". Tidak ada cara bawaan untuk menanyakan "apakah card merupakan salah satu token", yang justru merupakan pertanyaan yang dijawab secara native oleh selektor CSS dengan .card.

Kesenjangan tersebut menimbulkan dua mode kegagalan.

Pencocokan tepat terlalu ketat:

//div[@class='card']

Hanya mencocokkan class="card" tanpa yang lain dan tanpa spasi tambahan. Ini melewatkan class="card featured", class="featured card", dan class=" card ". Pada halaman nyata, ini akan melewatkan sebagian besar yang Anda inginkan.

Pencocokan substring terlalu longgar:

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

Mencocokkan class="card" dengan benar, serta class="card-large", class="postcard", class="discard", dan class="card-footer". Pada halaman dengan penamaan yang terkait, fungsi ini mengembalikan himpunan yang lebih luas dari yang Anda minta — dan kode Anda mengambil hasil pertama tanpa pemberitahuan.

Kegagalan kedua lebih berbahaya, karena menghasilkan hasil. Sesuatu dikembalikan, terlihat masuk akal, namun sebenarnya merupakan elemen yang salah.

Ungkapan yang Benar

Solusi standar menambahkan spasi di kedua sisi sehingga hanya token utuh yang dapat cocok:

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

Bacalah langkah demi langkah:

**normalize-space(@class)

** memotong spasi kosong di awal dan akhir serta menggabungkan rangkaian spasi kosong di tengah menjadi satu spasi. " card featured "

menjadi "card featured"

.

**concat(' ', ..., ' ')

** membungkus hasilnya dengan spasi, sehingga menghasilkan " card featured "

. Kini setiap token dibatasi oleh spasi di kedua sisinya.

**contains(..., ' card ')

** mencari target yang dikelilingi oleh spasi.

Periksa hal ini terhadap kasus-kasus yang membuat versi naif gagal:

| Atribut kelas | String yang diisi spasi | Mengandung ' card '

? | |---|---|---| | card

| " card "

| Ya | | card featured

| " card featured "

| Ya | | featured card

| " featured card "

| Ya | | card-large

| " card-large "

| Tidak | | discard

| " discard "

| Tidak | | postcard footer

| " postcard footer "

| Tidak | | card

| " card "

| Ya |

Benar dalam setiap kasus. Langkah normalize-space()

bukanlah sekadar hiasan — tanpa langkah tersebut, class="card featured"

dengan spasi ganda akan menghasilkan " card featured "

, yang masih mengandung " card "

dan kebetulan berfungsi, tetapi class="card\nfeatured"

dengan baris baru tidak akan berfungsi.

Kode ini terlalu panjang. Gunakan pembungkus:

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

tree.xpath(f"//div[{has_class('card')}]")
const hasClass = name =>
  `contains(concat(' ', normalize-space(@class), ' '), ' ${name} ')`;

Setiap basis kode yang melakukan ekstraksi serius dengan XPath pada akhirnya akan memiliki versi helper seperti ini. Menulisnya sekali lebih baik daripada salah menentukan spasi pengisi pada satu ekspresi dari dua puluh ekspresi.

Beberapa Kelas

Gabungkan dengan and

:

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

Di sinilah kerumitan kode menjadi benar-benar menyulitkan — dibutuhkan 140 karakter untuk mengekspresikan apa yang dapat ditulis dengan CSS dalam div.card.featured

.

Untuk "salah satu dari dua kelas", gunakan or

:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')
      or contains(concat(' ', normalize-space(@class), ' '), ' tile ')]

Untuk "memiliki kelas ini tetapi tidak yang itu":

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')
      and not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]

Jika ada dua atau lebih kondisi kelas, pertimbangkan dengan serius apakah selektor CSS bisa digunakan. div.card.featured:not(.hidden)

menggunakan logika yang sama dengan hanya seperlima jumlah karakter, dan setiap pustaka parsing utama mendukung selektor CSS selain XPath. Tidak ada aturan yang mewajibkan Anda memilih satu bahasa untuk seluruh berkas.

Nama Kelas yang Dihasilkan dan Di-hash

Masalah di era modern ini mengubah rekomendasi yang ada.

Banyak alat build front-end menghasilkan nama kelas dengan cakupan tertentu untuk menghindari tabrakan — seperti CSS Modules, styled-components, dan berbagai pustaka CSS-in-JS. Hasilnya terlihat seperti ini:

<div class="ProductCard_container__3xK9p">...</div>
<div class="css-1x9dj2k">...</div>

Bagian hash berubah setiap kali gaya komponen berubah, yang dalam praktiknya berarti pada banyak proses deploy. Selektor yang diikat ke nama lengkap akan rusak tanpa peringatan.

Tiga cara untuk mengatasinya, berdasarkan urutan prioritas.

Ikat pada prefiks yang stabil, jika alat tersebut menghasilkan prefiks tersebut:

//div[starts-with(@class, 'ProductCard_container__')]

CSS Modules secara konvensional menghasilkan ComponentName_elementName__hash

, sehingga bagian sebelum dua garis bawah terakhir tetap stabil di seluruh build. Cara ini bekerja dengan baik jika dapat diterapkan.

**Cari atribut ``data-*`

sebagai gantinya.** Banyak aplikasi menyertakan atribut sepertidata-testid`

, data-test

, atau yang serupa, tepatnya agar alat otomatis memiliki titik acuan yang stabil:

//div[@data-testid='product-card']

Jika ada, gunakanlah. Atribut ini lebih stabil daripada nama kelas mana pun karena dipilih secara sengaja, bukan dihasilkan secara otomatis.

Gunakan acuan pada hal lain sepenuhnya. Gunakan struktur relatif terhadap judul, konten teks, atau jenis elemen. //h2[normalize-space()='Featured']/following-sibling::div[1]

tidak peduli apa nama kelasnya.

Dan untuk kasus yang benar-benar tidak jelas — css-1x9dj2k

tanpa komponen yang stabil — pemilihan berbasis kelas sama sekali tidak layak, dan berpura-pura sebaliknya hanya akan menghasilkan scraper yang rusak setiap minggu. Carilah data terstruktur di halaman tersebut, atau cari API yang dipanggil oleh halaman itu sendiri.

Urutan Kelas dan Spasi Kosong

Dua hal yang sering membingungkan orang dan perlu diperhatikan dengan cermat.

Urutan kelas dalam atribut tidak berpengaruh. class="card featured" dan class="featured card" dianggap sama oleh browser maupun CSS. Pola "padded-concat" menangani keduanya dengan benar; sedangkan pencocokan persis (exact matching) tidak dapat diandalkan untuk keduanya. Jika Anda mendapati diri Anda menulis ekspresi yang bergantung pada urutan, itu merupakan tanda bahwa ada yang salah.

Spasi kosong bisa berupa apa saja. Tab dan baris baru adalah pemisah yang valid dalam atribut kelas, dan keduanya muncul dalam HTML yang diformat secara manual:

<div class="card
            featured">

normalize-space() menggabungkan semuanya, itulah sebabnya idiom tersebut mencakupnya. Ekspresi yang menggunakan ``concat(' ', @class, ' ') tanpa normalisasi akan gagal pada markup tersebut, dan kegagalan tersebut tidak terlihat pada halaman yang telah dirender.

Huruf besar-kecil diperhitungkan. Nama kelas peka huruf besar-kecil dalam dokumen HTML yang diparsing sebagai XHTML dan diperlakukan tanpa membedakan huruf besar-kecil dalam mode standar HTML untuk pencocokan CSS — tetapi perbandingan string XPath selalu peka huruf besar-kecil. Jika sebuah halaman mencampurkan Card dan card , idiom dengan spasi tambahan akan menganggap keduanya berbeda. XPath 1.0 tidak memiliki fungsi lower-case()` `, sehingga solusinya adalah translate() dengan huruf-huruf yang dinyatakan secara eksplisit, di mana pada titik ini ekspresi tersebut menjadi benar-benar tidak terbaca dan selektor CSS jelas merupakan pilihan yang lebih baik.

Mendapatkan Kelas Induk atau Kelas Turunan dari Sebuah Kelas

Pemilihan kelas biasanya merupakan sarana, bukan tujuan akhir. Ada dua pola yang mencakup sebagian besar kasus ini.

Dari sebuah kelas ke sesuatu di dalamnya:

//div[contains(concat(' ',normalize-space(@class),' '),' card ')]//span[@class='price']

Atau, dalam kode yang sudah memiliki elemen card, gunakan ekspresi relatif — dan titik di awal sangat penting:

for card in tree.xpath(f"//div[{has_class('card')}]"):
    price = card.xpath(".//span[@class='price']/text()")

`

.//span

mencari di dalam elemencard. ``//span

` mencari di seluruh dokumen mulai dari akar, yang mengembalikan harga pertama di halaman untuk setiap kartu. Hal ini menghasilkan keluaran yang seragam, tampak masuk akal, namun salah, dan merupakan salah satu bug paling umum dalam kode ekstraksi.

Dari sebuah elemen hingga wadahnya:

//span[@class='price']/ancestor::div[contains(concat(' ',normalize-space(@class),' '),' card ')][1]

[1]

sangat penting: ancestor::

menggunakan sumbu terbalik, sehingga posisi 1 adalah leluhur yang paling dekat yang cocok, bukan yang paling luar. Tanpa parameter ini, Anda akan mendapatkan semua leluhur yang cocok, dan pada wadah bersarang, hal itu jarang yang Anda inginkan.

Catatan Khusus Perpustakaan

Pola pemrogramannya sama di mana-mana; namun, API yang mengelilinginya berbeda, dan beberapa perbedaan antarperpustakaan dapat menimbulkan kebingungan yang sebenarnya bisa dihindari.

lxml (Python). Mendukung kedua bahasa tersebut, dan cssselect merupakan pilihan praktis untuk tugas kelas:

from lxml import html
tree = html.fromstring(source)

tree.cssselect('div.card.featured')                  # clear
tree.xpath(f"//div[{has_class('card')}]")            # when inside a larger expression

cssselect menerjemahkan CSS ke XPath secara internal, sehingga keduanya setara dalam kemampuan untuk selektor yang didukungnya. Perlu dicatat bahwa ini adalah paket terpisah dari lxml itu sendiri dan perlu diinstal.

BeautifulSoup (Python). Sama sekali tidak mendukung XPath — sebuah fakta yang mengejutkan orang-orang yang datang dari ekosistem lain. BeautifulSoup menawarkan select() untuk selektor CSS dan API find_all(class_='card') miliknya sendiri, yang secara native melakukan pencocokan token dengan benar. Jika Anda secara khusus membutuhkan XPath, Anda memerlukan lxml.

Selenium. Menerima baik By.XPATH maupun By.CSS_SELECTOR , dan menggunakan mesin browser untuk masing-masing. Artinya hanya mendukung XPath 1.0, dan dukungan CSS-nya bergantung pada apa yang didukung browser — termasuk :has() .

Playwright. Secara otomatis mendeteksi jenis penentu lokasi dari string, sehingga page.locator('div.card') dan page.locator('//div[@id="x"]') keduanya berfungsi tanpa awalan. Playwright juga menawarkan lokator berbasis teks — page.getByText() , page.getByRole() — yang mencakup sebagian besar kebutuhan pengguna yang sebelumnya memerlukan pencocokan teks XPath, dan lebih mudah dibaca daripada kedua bahasa tersebut.

Scrapy. Menyediakan .css() dan .xpath() pada selektor, dan keduanya dapat dirangkai, yang benar-benar berguna:

for card in response.css('div.card'):
    price = card.xpath(".//dt[normalize-space()='Price']/following-sibling::dd[1]/text()").get()

CSS untuk tahap struktural, XPath untuk pencarian label, dalam rantai ekspresi yang sama. Perhatikan penggunaan . di awal pada XPath — selektor berantai Scrapy memiliki jebakan "root versus relatif" yang sama seperti halnya selektor lainnya.

Konsol browser. $x('//div[@class="card"]') mengevaluasi XPath di alat pengembang Chrome dan Firefox, sedangkan document.querySelectorAll('div.card') menangani CSS. Menguji ekspresi di sini sebelum memasukkannya ke dalam kode layak dilakukan selama sepuluh detik, dengan catatan bahwa DOM browser sudah diproses oleh JavaScript sedangkan parser Anda belum — ekspresi yang berfungsi di konsol mungkin tidak menemukan apa pun dalam HTML mentah.

Kapan Sebaiknya Menggunakan CSS

Secara sederhana, karena untuk tugas spesifik ini jawabannya biasanya ya.

Gunakan CSS jika kriteria Anda berupa kelas. div.card, div.card.featured, div.card:not(.hidden) — semuanya lebih jelas, lebih singkat, dan secara default sudah benar. CSS memahami atribut class sebagai daftar token, yang justru merupakan hal yang tidak dimiliki oleh XPath.

Gunakan XPath ketika kelas bersifat insidental dan kriteria sebenarnya adalah sesuatu yang tidak dapat diekspresikan oleh CSS — terutama konten teks. //div[contains(@class,'card')][.//span[contains(., 'Sold out')]] memerlukan XPath untuk kondisi teks, dan bagian kelas ikut serta sebagai tambahan.

Gabungkan keduanya. Setiap pustaka parsing utama mendukung keduanya. lxml memiliki cssselect bersamaan dengan xpath; Selenium menerima kedua strategi penentu lokasi tersebut; Playwright juga menerima keduanya. Menggunakan CSS untuk pemilihan struktural dan XPath untuk kondisi teks bukanlah kompromi, melainkan pengaturan yang menghasilkan kode terbaca terpendek.

Satu-satunya kasus untuk pemilihan kelas dengan XPath adalah ketika Anda membutuhkannya di dalam ekspresi XPath yang lebih besar dan tidak memungkinkan untuk mengganti bahasa di tengah ekspresi. Itu adalah batasan nyata dan itulah mengapa idiom tersebut ada — tetapi cakupannya lebih sempit daripada jumlah pencocokan kelas XPath yang akan Anda temukan di dunia nyata.

Pertanyaan Terkait

Bagaimana cara memilih elemen berdasarkan kelas di XPath?

Gunakan //div[contains(concat(' ', normalize-space(@class), ' '), ' card ')]. Spasi di sekitarnya memastikan hanya token kelas yang utuh yang cocok. Penggunaan @class='card' tanpa spasi akan melewatkan elemen dengan kelas tambahan, sedangkan contains(@class,'card') juga akan cocok dengan card-large dan discard.

Mengapa contains(@class, 'name') mencocokkan elemen yang salah?

Karena ini adalah pengujian substring biasa tanpa konsep batas kata. Atribut class adalah daftar yang dipisahkan spasi, tetapi XPath melihatnya sebagai satu string, sehingga contains(@class,'card') mencocokkan nama kelas apa pun yang mengandung keempat karakter tersebut di mana pun.

Bagaimana cara memilih elemen dengan dua kelas di XPath?

Gabungkan dua kondisi padded-concat dengan and. Cara ini berfungsi dan panjangnya sekitar 140 karakter. Jika ada dua atau lebih kondisi kelas, selektor CSS — div.card.featured — jauh lebih jelas, dan sebagian besar pustaka parsing memungkinkan Anda menggunakannya.

Apakah urutan kelas berpengaruh dalam XPath?

Tidak dengan idiom padded-concat, yang mencocokkan token di mana pun muncul dalam atribut. Urutan kelas berpengaruh pada perbandingan eksak, yang merupakan salah satu dari beberapa alasan mengapa @class='card featured' adalah pilihan yang buruk. Urutan kelas tidak berarti apa-apa dalam HTML, jadi selektor yang bergantung padanya adalah bug yang menunggu untuk terjadi.

Bagaimana cara menangani nama kelas yang dihasilkan secara acak?

Gunakan prefiks yang stabil sebagai acuan dengan starts-with() jika alat yang digunakan menghasilkan prefiks tersebut, gunakan atribut data-testid jika aplikasi menyediakannya, atau gunakan acuan lain sepenuhnya seperti teks atau struktur. Untuk nama yang di-hash sepenuhnya dan tidak transparan tanpa bagian yang stabil, pemilihan berbasis kelas tidak dapat diterapkan.

Apakah XPath atau CSS lebih baik untuk pemilihan berdasarkan kelas?

Jelas CSS. CSS memperlakukan atribut class sebagai daftar token, yang memang sesuai dengan definisinya, sehingga div.card lebih singkat dan tepat. XPath memerlukan idiom sepanjang tujuh puluh karakter untuk mengekspresikan hal yang sama. Gunakan XPath jika Anda juga memerlukan sesuatu yang tidak dapat dilakukan oleh CSS, seperti mencocokkan teks.

Bagaimana cara menemukan elemen induk berdasarkan kelas di XPath?

//span[@class='price']/ancestor::div[contains(concat(' ',normalize-space(@class),' '),' card ')][1]. [1] memilih leluhur terdekat yang cocok, karena ancestor:: merupakan sumbu terbalik di mana posisi 1 berarti yang terdekat, bukan yang paling luar.

Mengapa XPath relatif saya mengembalikan nilai yang sama untuk setiap elemen?

Karena Anda menggunakan // alih-alih .// di dalam loop. // yang berada di awal mencari dari akar dokumen terlepas dari konteksnya, sehingga setiap iterasi menemukan kecocokan pertama di seluruh halaman. .// mencari di dalam elemen saat ini, yang sesuai dengan maksud Anda.

Kesimpulan

Pemilihan kelas adalah titik di mana XPath menunjukkan keterbatasannya. Atribut kelas berupa daftar token, dan XPath tidak memiliki operasi token; sehingga untuk menyatakan "memiliki kelas ini", kita harus menambahkan spasi pada string dan mencari token yang telah ditambahkan spasi tersebut — sebuah idiom sepanjang tujuh puluh karakter untuk hal yang dapat diungkapkan CSS hanya dalam sembilan karakter.

Pelajari idiom ini, karena Anda akan membutuhkannya di dalam ekspresi XPath yang lebih besar, dan bungkuslah dalam fungsi bantu agar Anda hanya perlu menuliskannya sekali, bukan dua puluh kali. Langkah "normalize-space()" bukanlah opsional: markup asli seringkali mengandung baris baru dan tab dalam atribut class, dan ekspresi yang tidak dinormalisasi akan gagal secara tersembunyi saat menemui elemen-elemen tersebut.

Namun, kesimpulan yang lebih berguna adalah yang menghindari penggunaan idiom tersebut. Jika kriteria pemilihan Anda hanya kelas dan tidak ada yang lain, tulislah selektor CSS. Setiap pustaka utama mendukung keduanya, menggabungkannya dalam satu berkas adalah hal yang wajar, dan memilih alat yang tepat untuk setiap ekspresi akan menghasilkan kode yang lebih ringkas dan mudah dibaca oleh rekan kerja.

Dan anggaplah nama kelas sebagai jangkar yang paling tidak stabil. Nama yang dihasilkan dan di-hash berubah saat deployment; bahkan nama yang ditulis manual pun berubah setiap kali seseorang mengubah gaya komponen. Sebuah data-testid, teks judul, atau hubungan elemen dengan sesuatu yang dapat diidentifikasi akan bertahan lebih lama daripada nama kelas — dan mode kegagalan saat sebuah kelas menghilang bukanlah kesalahan, melainkan output kosong yang terstruktur dengan baik dan tidak menimbulkan peringatan.