Idenya
Situs jarang menampilkan semuanya sekaligus. Daftar dipecah dengan salah satu dari tiga cara:
- Halaman bernomor dengan link Next (
?page=2). - Tombol Load more yang menambahkan item ke halaman.
- Infinite scroll: item baru dimuat saat kamu menggulir ke bawah.
Masing-masing butuh loop dengan kondisi berhenti yang jelas. Kondisi berhenti yang salah adalah bug klasik: kamu melewatkan halaman terakhir, atau loop-nya tidak pernah berakhir.
1. Mengikuti link Next
Daftar produk toko terbagi ke beberapa halaman, dengan link "Next" di bagian bawah masing-masing. Cara paling andal untuk mengumpulkan semuanya adalah melakukan apa yang dilakukan manusia: scrape halamannya, klik Next, lalu ulangi sampai tidak ada link Next lagi:
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/products")
names = []
while True:
names += page.locator(".product-card .title").all_inner_texts()
next_link = page.locator("a.next")
if next_link.count() == 0: # on the last page, "Next" is not a link
break
next_link.click()
print(len(names), names[0], "...", names[-1])
print(page.url)
browser.close()
36 Galaxy A55 5G ... Fitbit Inspire 3
https://nusantara-gadget.test/products?page=3
Kondisi berhentinya berasal dari halaman itu sendiri: di halaman terakhir, Next berupa <span> yang nonaktif, bukan <a>. Mengikuti link milik situs itu sendiri adalah pendekatan paling tangguh, karena tetap berfungsi bagaimanapun URL-nya dibangun.
Jebakan pola URL
Melakukan loop sendiri atas ?page=1, 2, 3... memang menggoda. Tapi apa yang terjadi setelah halaman terakhir?
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
for n in (3, 4, 50):
page.goto(f"https://nusantara-gadget.test/products?page={n}")
print(n, "->", page.locator(".result-count").inner_text(), "|", page.locator(".product-card .title").first.inner_text())
browser.close()
3 -> Showing 12 of 36 products (page 3 of 3) | K380 Keyboard
4 -> Showing 12 of 36 products (page 3 of 3) | K380 Keyboard
50 -> Showing 12 of 36 products (page 3 of 3) | K380 Keyboard
Situs ini diam-diam menampilkan halaman terakhir lagi untuk nomor halaman yang terlalu tinggi. Loop yang berhenti "saat sebuah halaman tidak berisi produk" akan berjalan selamanya, mengumpulkan duplikat. Setiap situs berperilaku berbeda (ada yang mengembalikan 404, ada yang list kosong, ada yang halaman terakhir), jadi baca informasi dari halaman itu sendiri: di sini, "page 3 of 3".
import re
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/products?category=audio")
total_pages = int(re.search(r"of (\d+)\)", page.locator(".result-count").inner_text()).group(1))
print("audio pages:", total_pages)
browser.close()
audio pages: 1
2. Tombol Load more
Katalog menampilkan 12 produk dan tombol Load more yang mengambil 12 produk lagi setiap kali diklik dan menghilang saat tidak ada yang tersisa:
from playwright.sync_api import expect
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/catalog")
cards = page.locator(".product-card")
cards.first.wait_for()
load_more = page.get_by_role("button", name="Load more")
clicks = 0
while load_more.is_visible():
before = cards.count()
load_more.click()
cards.nth(before).wait_for() # wait for the first NEW card
clicks += 1
print(clicks, "clicks,", cards.count(), "products")
browser.close()
2 clicks, 36 products
Baris kuncinya adalah cards.nth(before).wait_for(): setelah mengklik, tunggu sampai ada kartu di posisi before (kartu baru yang pertama). Tanpa itu, loop akan mengecek tombolnya lagi sebelum item-item baru datang.
3. Infinite scroll
Feed "New arrivals" memuat 12 produk, lalu lebih banyak lagi setiap kali kamu menggulir mendekati bagian bawah, dan menampilkan "You've seen everything." di akhir:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/feed")
cards = page.locator(".product-card")
cards.first.wait_for()
end_message = page.locator("#feed-end")
scrolls = 0
while not end_message.is_visible():
before = cards.count()
page.mouse.wheel(0, 10_000) # scroll down, like a user
cards.nth(before).wait_for()
scrolls += 1
ids = [card.get_attribute("data-id") for card in cards.all()]
print(scrolls, "scrolls,", len(ids), "products,", len(set(ids)), "unique")
browser.close()
2 scrolls, 36 products, 36 unique
Bentuknya sama dengan Load more: bertindak, tunggu item baru, cek kondisi berhenti. Cara lain untuk menggulir: page.keyboard.press("End"), atau locator.last.scroll_into_view_if_needed().
Tidak setiap feed punya pesan akhir. Kalau begitu, berhenti saat sebuah guliran tidak menghasilkan apa pun yang baru dalam batas timeout (tangkap TimeoutError dari wait_for), dan selalu tambahkan jumlah iterasi maksimum supaya halaman yang berulah tidak bisa menjebak scraper-mu.
Setiap loop pagination adalah bertindak → tunggu konten baru → cek kondisi berhenti yang berasal dari halaman (tidak ada link Next, tombol hilang, pesan akhir, total halaman). Tambahkan jumlah iterasi maksimum sebagai jaring pengaman, dan hapus duplikat berdasarkan id yang stabil.
Mengapa ini penting untuk kode yang bersih
Tulis setiap strategi sekali, sebagai fungsi (scrape_all_pages(page), load_all(page)), dengan kondisi berhenti dan batas pengaman terlihat di bagian atas loop. Saat sebuah situs beralih dari halaman bernomor ke infinite scroll (itu terjadi), kamu menukar satu fungsi dan bagian lain scraper tidak menyadarinya.
Rangkuman
- Halaman bernomor: ikuti link Next sampai tidak lagi berupa link.
- Loop URL: pelajari cara situs menangani halaman di luar rentang; gunakan informasi "page X of Y" miliknya.
- Load more: klik,
cards.nth(before).wait_for(), ulangi selama tombolnya terlihat. - Infinite scroll:
page.mouse.wheel(0, 10_000), tunggu item baru, berhenti di penanda akhir atau saat tidak ada yang baru dimuat. - Selalu batasi jumlah iterasi dan hapus duplikat berdasarkan id.