DataLens

Mengambil teks dan atribut

Baca teks, atribut, dan data tersembunyi dari elemen, ubah nilai yang ditampilkan seperti "Rp 5.999.000" menjadi angka, dan bangun list record yang bersih dari elemen halaman yang berulang.

Level: Menengah · 8 menit baca

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

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']

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.

Ide utama

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

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

Berikutnya: Menunggu elemen dan konten (auto-waiting) →