Idenya
Di halaman dinamis, konten datang setelah halaman "termuat": spinner muncul, JavaScript mengambil data, kartu muncul setengah detik kemudian. Scraper yang membaca terlalu cepat tidak melihat apa-apa. Perbaikan klasiknya time.sleep(5), yang lambat saat halamannya cepat dan rusak saat halamannya lambat. Playwright punya jawaban yang lebih baik: tunggu hal yang kamu butuhkan, dan sebagian besar waktu ia melakukannya untukmu.
Masalahnya
Halaman katalog toko memuat produk-produknya sesaat setelah halamannya sendiri muncul. Bagi manusia ini tidak terlihat: kamu melihat spinner sepersekian detik, lalu produk-produknya. Inilah yang dilihat script kalau langsung menghitung produknya:
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/catalog")
print(page.locator(".product-card").count())
print(page.locator(".loading").inner_text())
browser.close()
0
Loading products…
goto kembali begitu halaman termuat, tetapi kartu-kartu katalog datang dari request latar belakang yang selesai sesaat kemudian. count() melaporkan apa yang ada saat ini juga: belum ada apa-apa.
Auto-waiting
Aksi yang butuh sebuah elemen (inner_text, get_attribute, click, fill...) menunggunya secara otomatis, sampai 30 detik secara default:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/catalog")
print(page.locator(".product-card .title").first.inner_text()) # waits for the first card
print(page.locator(".product-card").count()) # now they're all there
browser.close()
Galaxy A55 5G
12
Aturan yang perlu diingat:
| Menunggu otomatis | Langsung kembali |
|---|---|
inner_text(), text_content(), get_attribute(), click(), fill(), screenshot() | count(), all(), all_inner_texts(), is_visible() |
Jadi sebelum memakai method "langsung kembali" pada konten dinamis, tunggu kontennya lebih dulu.
Menunggu secara eksplisit
Halaman flash deals menampilkan spinner selama 2,5 detik sebelum deals-nya muncul. Tiga cara untuk menunggu:
import time
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/deals")
start = time.time()
page.locator(".deal").first.wait_for() # 1. wait for an element to be visible
print(f"deals appeared after {time.time() - start:.1f}s")
page.wait_for_selector(".spinner", state="detached") # 2. wait for something to disappear
expect(page.locator(".deal")).to_have_count(5) # 3. wait for an exact condition
print(page.locator(".deal .deal-name").all_inner_texts())
browser.close()
deals appeared after 2.5s
['Redmi Note 13 Pro', 'Galaxy Buds FE', 'Soundcore Life Q30', '65W GaN Charger', 'Redmi Watch 4']
locator.wait_for()menunggu sampai elemennya terlihat (state="attached","hidden"atau"detached"untuk kondisi lain).page.wait_for_selector(selector, state=...)melakukan hal yang sama dengan string selector.expect(locator).to_have_count(n)(danto_have_text,to_be_visible...) terus mencoba ulang sampai kondisinya terpenuhi atau 5 detik berlalu. Berguna saat kamu tahu persis seperti apa kondisi "selesai".
Halamannya butuh 2,5 detik, dan script-nya menunggu 2,5 detik: tidak lebih, tidak kurang. sleep(5) akan membuang separuh waktunya; sleep(2) akan membaca halaman kosong.
Saat konten tidak pernah datang
Waiting punya timeout. Kalau terlewati, Playwright memunculkan TimeoutError, yang bisa kamu tangkap:
from playwright.sync_api import TimeoutError as PlaywrightTimeout
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/deals")
try:
page.locator(".deal").first.wait_for(timeout=1000) # milliseconds
print("deals found")
except PlaywrightTimeout:
print("no deals within 1 second")
browser.close()
no deals within 1 second
Timeout dalam satuan milidetik. Atur per panggilan (timeout=...), atau untuk seluruh halaman dengan page.set_default_timeout(10_000). Timeout yang lebih pendek membuat halaman yang rusak gagal dengan cepat alih-alih menggantung selama 30 detik; pilih nilai yang cukup jauh di atas waktu muat normal halamannya.
(Mengimpornya sebagai PlaywrightTimeout mencegah kebingungan dengan TimeoutError bawaan Python.)
Tunggu elemen atau kondisi spesifik yang kamu butuhkan, jangan pernah menunggu dalam waktu yang tetap. Aksi menunggu otomatis; count() dan all() tidak, jadi tunggu dulu. Tangkap TimeoutError untuk konten yang memang mungkin tidak ada.
Bagaimana dengan wait_for_timeout dan networkidle?
page.wait_for_timeout(2000)adalah sleep dengan waktu tetap. Tidak masalah untuk demonstrasi dan untuk sengaja memperlambat (bersikap sopan pada server), tetapi jangan pernah sebagai cara menunggu konten.page.goto(url, wait_until="networkidle")menunggu sampai tidak ada request selama 500 ms. Ini berfungsi di halaman sederhana tetapi lambat, dan halaman dengan polling, analytics, atau update langsung mungkin tidak pernah idle. Menunggu sebuah elemen lebih presisi.
Mengapa ini penting untuk kode yang bersih
Sleep dengan waktu tetap adalah sumber nomor satu scraper yang flaky: lulus di laptopmu yang cepat dan gagal di server yang lambat, atau sebaliknya. Menunggu kondisi yang eksplisit membuat kodenya menyatakan maksudnya ("tunggu sampai deals-nya tampil") dan membuatnya secepat yang diizinkan situsnya. Itu lebih mudah dibaca sekaligus lebih andal.
Rangkuman
- Konten dinamis muncul setelah
gotokembali;count()mungkin 0 pada awalnya. - Aksi (
inner_text,click,get_attribute...) menunggu otomatis sampai 30 detik. locator.wait_for(state=...),page.wait_for_selector(...),expect(locator).to_have_count(...)menunggu secara eksplisit.- Timeout dalam milidetik; tangkap
playwright.sync_api.TimeoutError. - Hindari sleep dengan waktu tetap dan ketergantungan berlebihan pada
networkidle.