DataLens

Menangani pagination dan infinite scroll

Kumpulkan setiap item dari daftar multi-halaman dengan mengikuti link Next, memakai pola URL dengan aman, mengklik Load more, dan menggulir feed tanpa akhir sampai selesai.

Level: Menengah · 8 menit baca

Latihan modul ini

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

Idenya

Situs jarang menampilkan semuanya sekaligus. Daftar dipecah dengan salah satu dari tiga cara:

  1. Halaman bernomor dengan link Next (?page=2).
  2. Tombol Load more yang menambahkan item ke halaman.
  3. 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.

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.

Ide utama

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

Latihan modul ini

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