Idenya
Menemukan elemen baru separuh pekerjaan; separuh lainnya adalah membacanya menjadi nilai Python yang bersih. Sebuah kartu produk menampilkan Rp 5.999.000 dan ★ 4.6 (1243 reviews), tetapi analisismu butuh 5999000 dan 4.6. Modul ini membahas method-method untuk membaca dan kebiasaan yang paling penting: mengubah setiap elemen yang berulang menjadi dictionary yang rapi.
Teks: inner_text vs text_content
Begitu locator menunjuk elemen yang tepat, kamu ingin isinya, biasanya teksnya. Playwright punya dua cara membaca teks, dan keduanya tidak selalu memberi jawaban yang sama:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/product/1")
print(page.locator(".product-title").inner_text())
print(page.locator(".price").inner_text())
print(page.locator(".rating").inner_text())
print(page.locator(".specs tr").all_inner_texts())
browser.close()
Galaxy A55 5G
Rp 5.999.000
★ 4.6 · 1243 reviews · 3200 sold
['Brand Samsung', 'Category Phones', 'Warranty 1 year official warranty', 'SKU NG-0001']
inner_text()mengembalikan teks seperti yang ditampilkan: bagian tersembunyi dilewati, spasi dirapikan. Pakai ini secara default.text_content()mengembalikan teks mentah dari setiap node, termasuk yang tersembunyi dan semua spasi dari HTML-nya.all_inner_texts()mengembalikan list, satu string per kecocokan.
Atribut: link, gambar, dan data tersembunyi
Banyak informasi berguna sama sekali bukan teks yang terlihat:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/products")
card = page.locator(".product-card").first
print(card.get_attribute("data-id"), card.get_attribute("data-category"))
print(card.locator(".title a").get_attribute("href"))
print(card.locator("img").get_attribute("src"), "|", card.locator("img").get_attribute("alt"))
print(card.locator(".price").get_attribute("data-price"))
browser.close()
1 phones
/product/1
/img/products/1.jpg | Galaxy A55 5G
5999000
Perhatikan data-price="5999000": banyak situs menaruh nilai bersihnya di atribut data-* dan versi yang diformat di teks. Kalau ada, baca atributnya dan lewati parsing. Atribut selalu berupa string (atau None kalau tidak ada), jadi konversi: int(...), float(...).
Link dikembalikan seperti yang tertulis di HTML, sering kali relatif (/product/1). Jadikan absolut dengan urljoin:
from urllib.parse import urljoin
print(urljoin("https://nusantara-gadget.test/products?page=2", "/product/1"))
https://nusantara-gadget.test/product/1
Mem-parse teks yang ditampilkan
Saat nilai bersihnya tidak ada di atribut, parse teksnya. Jaga setiap parser tetap kecil dan bisa dites:
import re
def parse_rupiah(text):
"""'Rp 5.999.000' -> 5999000"""
return int(re.sub(r"[^\d]", "", text))
def parse_rating(text):
"""'★ 4.6 (1243 reviews)' -> (4.6, 1243)"""
rating, reviews = re.search(r"([\d.]+)\D+(\d+)", text).groups()
return float(rating), int(reviews)
print(parse_rupiah("Rp 5.999.000"))
print(parse_rating("★ 4.6 (1243 reviews)"))
5999000
(4.6, 1243)
Format Indonesia memakai . sebagai pemisah ribuan, jadi float("5.999.000") gagal dan float("5.999") akan salah 1.000 kali lipat. Membuang semua yang bukan digit adalah pendekatan yang aman untuk harga rupiah utuh.
Satu record per elemen
Pola standarnya: lakukan loop atas elemen yang berulang dengan .all(), dan baca setiap field relatif terhadap elemen itu:
def parse_card(card):
return {
"id": int(card.get_attribute("data-id")),
"name": card.locator(".title").inner_text(),
"brand": card.locator(".brand").inner_text(),
"price": int(card.locator(".price").get_attribute("data-price")),
"rating": float(card.locator(".rating").get_attribute("data-rating")),
"in_stock": card.locator(".in-stock").count() > 0,
"url": urljoin("https://nusantara-gadget.test", card.locator(".title a").get_attribute("href")),
}
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/products")
products = [parse_card(card) for card in page.locator(".product-card").all()]
browser.close()
print(len(products))
print(products[1])
12
{'id': 2, 'name': 'Redmi Note 13 Pro', 'brand': 'Xiaomi', 'price': 4299000, 'rating': 4.5, 'in_stock': False, 'url': 'https://nusantara-gadget.test/product/2'}
Kenapa per kartu, bukan mengumpulkan semua nama lalu semua harga? List paralel langsung tidak sejajar begitu satu kartu tidak punya sebuah field (rating yang hilang akan menggeser setiap rating sesudahnya ke produk yang salah). Membaca setiap kartu sebagai satu kesatuan menjaga setiap field tetap melekat pada produknya.
inner_text() untuk teks yang terlihat, get_attribute() untuk link, gambar, dan nilai data-*. Utamakan nilai atribut yang bersih daripada parsing; kalau harus parsing, tulis fungsi kecil yang sudah dites. Bangun satu dictionary per elemen yang berulang.
Mengapa ini penting untuk kode yang bersih
parse_card adalah fungsi murni dengan satu tujuan: ia menerima satu elemen dan mengembalikan satu record. Kamu bisa mengetesnya pada satu kartu, memakainya ulang di setiap halaman yang menampilkan kartu (produk, pencarian, katalog), dan memperbaikinya di satu tempat saat markup-nya berubah. Loop scraping-nya tetap satu baris.
Rangkuman
inner_text()(teks yang ditampilkan),text_content()(mentah),all_inner_texts()(list).get_attribute(name)untukhref,src,altdandata-*; mengembalikan string atauNone.- Jadikan link relatif absolut dengan
urljoin. - Parse teks yang diformat dengan fungsi kecil (
re.sub(r"[^\d]", "", text)untuk rupiah). - Loop
for card in locator.all()dan baca field relatif terhadap setiap kartu ke dalam dict.