In this Article
Mempelajari cara melakukan web scraping pada halaman web dinamis berarti menangani konten yang tidak ada dalam HTML yang diunduh oleh request pertama Anda. Situs modern membangun grid produk, harga, komentar, dan feed di browser dengan JavaScript, sehingga HTTP fetch biasa hanya mengembalikan kerangka kosong di tempat data seharusnya berada.
Panduan ini membahas seluruh alur kerja di Python. Panduan ini dimulai dengan mendeteksi konten dinamis dengan membandingkan raw response dengan DOM yang dirender, lalu mengurutkan opsi ekstraksi dari yang paling murah hingga paling berat: menemukan JSON API tersembunyi, menjalankan Playwright atau Selenium, menangani infinite scroll dan tombol load more, mengurangi bandwidth proxy, menambahkan retry, melakukan scraping secara bersamaan, serta mengekspor JSON dan CSV yang rapi.
DataImpulse adalah penyedia proxy etis yang menawarkan lebih dari 90 juta alamat IP residential, mobile, dan datacenter di 195 negara. Layanan ini menggunakan model pay-as-you-go mulai dari 1 dolar per GB dengan lalu lintas yang tidak kedaluwarsa, serta digunakan untuk web scraping, verifikasi iklan, pemantauan harga, riset pasar, dan pengelolaan multi-akun.
Fakta utama
- Scraping dinamis: Konten yang dibangun oleh JavaScript tidak ada dalam raw HTML, sehingga Anda perlu memanggil JSON API yang diambil halaman tersebut atau merender halaman di browser headless.
- Jenis proxy terbaik: rotating residential proxies, yang menggunakan IP konsumen asli untuk melewati deteksi.
- Harga: mulai dari 1 dolar per GB, pay-as-you-go, dengan lalu lintas yang tidak kedaluwarsa dan tanpa langganan.
- Cakupan: lebih dari 90 juta IP yang diperoleh secara etis di 195 negara.
- Keandalan: tingkat keberhasilan 99,51%, dengan rating 4,8 dari 5 di G2.
- Protokol dan penargetan: HTTP, HTTPS, dan SOCKS5, termasuk penargetan negara.

Apa yang membuat halaman web menjadi dinamis?
Sebuah halaman bersifat dinamis ketika konten bermaknanya dibuat oleh JavaScript di browser, bukan dikirimkan dalam respons HTML awal. Server mengirimkan kerangka ringan, lalu kode sisi klien mengambil data dan menyisipkannya ke DOM setelah halaman dibuka.
Pemeriksaan manual tercepat adalah membandingkan dua tampilan halaman yang sama. Klik kanan dan pilih View Source untuk melihat raw HTML yang dikembalikan server, lalu buka developer tools dan periksa panel Elements, yang menampilkan DOM aktif setelah skrip berjalan. Jika harga atau listing yang Anda inginkan muncul di panel Elements tetapi tidak ada di View Source, kontennya dinamis dan satu request tidak akan menangkapnya.
- Konten statis ada di View Source dan dapat diurai langsung dari respons HTTP.
- Konten dinamis hanya muncul di DOM yang dirender dan dimuat melalui request latar belakang.
Anda dapat mengotomatiskan diagnosis ini dalam kode. Pertama, hitung elemen target dalam raw HTML yang diunduh oleh klien HTTP biasa:
import requests
from bs4 import BeautifulSoup
url = "https://example.com/products"
headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get(url, headers=headers, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
raw_cards = soup.select("div.product-card")
print("Cards in raw HTML:", len(raw_cards))
# If this prints 0 but the page clearly shows products in a browser,
# the content is injected by JavaScript and the page is dynamic.
Kemudian render URL yang sama di browser headless dan hitung lagi. Perbedaan antara kedua angka tersebut adalah buktinya:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com/products", wait_until="networkidle")
rendered_cards = page.query_selector_all("div.product-card")
print("Cards after JavaScript runs:", len(rendered_cards))
browser.close()
# Raw HTML 0, rendered DOM 48 means the page builds its content dynamically.
Sebelum menulis scraper lengkap, sebaiknya pastikan juga bahwa target mengizinkan akses otomatis. Panduan kami tentang cara memeriksa apakah situs web mengizinkan scraping membahas cara membaca aturan robots dan ketentuan layanan.
Metode mana yang sebaiknya digunakan untuk melakukan scraping pada halaman dinamis?
Pilih metode paling ringan yang dapat mengembalikan data secara andal: panggil JSON API tersembunyi jika ada, dan gunakan browser headless hanya jika tidak ada. Setiap opsi mempertukarkan kecepatan dan biaya dengan sejauh mana halaman dapat direproduksi.
Tabel di bawah membandingkan empat pendekatan umum agar Anda dapat memilihnya sebelum menulis kode. Dalam praktiknya, sebagian besar proyek menggabungkan dua pendekatan, menggunakan API tersembunyi untuk pemuatan massal dan browser untuk halaman yang sulit ditangani.
| Metode | Kecepatan | Biaya | Terbaik untuk | Keterbatasan utama |
|---|---|---|---|---|
| JSON API tersembunyi | Tercepat | Terendah | Data di balik panggilan XHR yang bersih | Endpoint dapat berubah atau memerlukan token |
| Playwright | Sedang | Lebih tinggi | Rendering penuh, kontrol async modern | Menggunakan CPU, memori, dan bandwidth |
| Selenium | Sedang | Lebih tinggi | Stack lama, dukungan bahasa yang luas | Penyiapan lebih lambat, penantian lebih panjang |
| requests-html | Lambat | Rendah | JavaScript ringan pada halaman sederhana | Sebagian besar tidak lagi dipelihara, rendering rapuh |
Biaya penting karena browser headless yang dialihkan melalui proxy berbayar berdasarkan penggunaan mengunduh jauh lebih banyak byte daripada satu panggilan API. Jika Anda melakukan scraping dalam skala besar, metode yang dipilih memengaruhi tagihan sama besar dengan kode. Untuk praktik yang lebih luas terkait pengaturan jeda dan header, lihat praktik terbaik web scraping.
Bagaimana menemukan API tersembunyi di balik halaman dinamis?
Cari request latar belakang yang dibuat halaman untuk mengambil datanya, karena endpoint tersebut biasanya mengembalikan JSON bersih yang dapat Anda panggil langsung. Ini adalah pendekatan tercepat dan paling andal, serta sepenuhnya menghindari menjalankan browser.
Buka developer tools, pindah ke tab Network, filter berdasarkan Fetch/XHR, lalu muat ulang halaman. Saat konten muncul, perhatikan request yang mengembalikan JSON berisi nilai yang Anda inginkan. Periksa URL request, metode, parameter query, serta header atau token yang dikirim, lalu klik kanan dan salin sebagai cURL untuk melihat strukturnya secara lengkap. Setelah Anda dapat mereproduksi panggilan itu dengan klien HTTP, Anda tidak perlu merender sama sekali dan bisa mengambil data terstruktur dengan sebagian kecil biaya.
Berikut contoh minimal yang memanggil endpoint JSON melalui proxy dan membaca field secara langsung:
import requests
proxy = "http://USERNAME:[email protected]:823"
proxies = {"http": proxy, "https": proxy}
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/json",
"Referer": "https://example.com/products",
}
params = {"category": "laptops", "page": 1}
r = requests.get(
"https://example.com/api/v2/products",
headers=headers,
params=params,
proxies=proxies,
timeout=30,
)
r.raise_for_status()
data = r.json()
for item in data["items"]:
print(item["name"], item["price"])
Jika endpoint dipaginasi, naikkan parameter page atau ikuti cursor berikutnya yang dikembalikan respons. Ketika request memerlukan token, tangkap tempat halaman pertama kali menerbitkannya dan gunakan kembali dalam session yang sama. Mengalihkan panggilan melalui DataImpulse berarti endpoint melihat IP residential, bukan alamat server Anda, yang penting ketika API menerapkan rate limit berdasarkan asal. Jika Anda menghadapi masalah autentikasi pada proxy itu sendiri, catatan kami tentang autentikasi proxy menjelaskan format kredensial.
Bagaimana melakukan scraping halaman dinamis dengan Playwright?
Ketika tidak ada API bersih yang dapat dipanggil, jalankan browser headless seperti Playwright agar JavaScript dieksekusi persis seperti untuk pengguna, lalu baca DOM yang telah selesai. Playwright adalah pilihan modern karena menyediakan auto-waiting yang andal, dukungan async kelas satu, dan opsi proxy sederhana.
Instal dan unduh binary browser terlebih dahulu:
pip install playwright
playwright install chromium
Prinsip utamanya adalah menunggu elemen spesifik yang Anda perlukan, bukan menebak dengan sleep tetap. Contoh di bawah meluncurkan Chromium, mengalihkannya melalui proxy dengan format dictionary, menunggu grid produk, lalu mengekstrak nilai yang dirender:
from playwright.sync_api import sync_playwright
PROXY = {
"server": "http://gw.dataimpulse.com:823",
"username": "USERNAME",
"password": "PASSWORD",
}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, proxy=PROXY)
context = browser.new_context(user_agent="Mozilla/5.0")
page = context.new_page()
page.goto("https://example.com/products", wait_until="domcontentloaded")
page.wait_for_selector("div.product-card", timeout=15000)
cards = page.query_selector_all("div.product-card")
for card in cards:
name = card.query_selector(".title").inner_text()
price = card.query_selector(".price").inner_text()
print(name, price)
browser.close()
Penantian adalah titik keberhasilan atau kegagalan sebagian besar scraper dinamis, jadi penting untuk mengetahui empat strategi dan kapan masing-masing sesuai. Utamakan menunggu selector atau kondisi konkret daripada networkidle, yang dapat macet pada halaman yang mempertahankan koneksi jangka panjang:
# Wait for a specific element (most reliable)
page.wait_for_selector("div.product-card", timeout=15000)
# Wait for the initial DOM to be built, before every script finishes
page.goto(url, wait_until="domcontentloaded")
# Wait until there are no network connections for 500 ms
page.goto(url, wait_until="networkidle")
# Wait for a custom condition, such as a minimum item count
page.wait_for_function(
"document.querySelectorAll('div.product-card').length > 20"
)
Jika target Anda sangat bergantung pada skrip yang dijalankan browser, artikel pendamping kami tentang web scraping dengan JavaScript membahas lebih dalam rendering sisi klien yang membuat halaman ini sulit.
Bagaimana menangani infinite scroll dan tombol load more?
Picu event yang sama seperti yang digunakan halaman untuk memuat lebih banyak konten, lalu tunggu setiap batch baru dirender sebelum membacanya. Infinite scroll dan tombol load more hanya mengambil data ketika pengguna bertindak, sehingga satu kali pemuatan halaman hanya menangkap layar pertama.
Sebelum menggunakan browser, periksa lagi tab Network, karena infinite scroll hampir selalu didukung oleh panggilan XHR yang dipaginasi, dan melakukan loop pada endpoint itu dengan pendekatan requests di atas jauh lebih murah daripada scrolling. Jika Anda harus melakukan scroll, lakukan dalam loop terkontrol yang berhenti ketika tidak ada item baru yang muncul:
def scroll_until_stable(page, item_selector, max_rounds=30):
seen = 0
for _ in range(max_rounds):
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(1500)
count = len(page.query_selector_all(item_selector))
if count == seen:
break # no new items loaded, stop
seen = count
return seen
Halaman yang menggunakan tombol eksplisit, bukan scroll, memerlukan loop klik. Baca jumlah item sebelum setiap klik, klik tombol, lalu tunggu jumlahnya bertambah agar Anda tidak mendahului proses rendering:
def click_load_more(page, button_selector, item_selector, max_clicks=50):
for _ in range(max_clicks):
button = page.query_selector(button_selector)
if button is None or not button.is_visible():
break
before = len(page.query_selector_all(item_selector))
button.click()
page.wait_for_function(
"([sel, n]) => document.querySelectorAll(sel).length > n",
arg=[item_selector, before],
)
return len(page.query_selector_all(item_selector))
Lakukan deduplikasi saat mengumpulkan data, karena membaca ulang node yang sama pada setiap putaran adalah hal umum. Simpan set ID atau URL unik dan hanya tambahkan record yang belum Anda lihat.
Apakah Anda dapat menggunakan Selenium sebagai pengganti Playwright?
Ya, Selenium mengendalikan browser asli dengan cara yang sama dan merupakan pilihan solid ketika tim Anda sudah menggunakannya atau memerlukan dukungan bahasanya yang luas. Polanya mencerminkan Playwright: muat halaman, tunggu kondisi dengan WebDriverWait, lalu baca elemen dari DOM yang dirender.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://gw.dataimpulse.com:823")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com/products")
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "div.product-card"))
)
for card in driver.find_elements(By.CSS_SELECTOR, "div.product-card"):
name = card.find_element(By.CSS_SELECTOR, ".title").text
price = card.find_element(By.CSS_SELECTOR, ".price").text
print(name, price)
driver.quit()
Satu catatan: flag –proxy-server biasa tidak menerima username dan password, sehingga proxy terautentikasi memerlukan bantuan. Library Selenium Wire menyisipkan kredensial dengan rapi, dan panduan kami tentang penyiapan proxy Selenium Wire menunjukkan konfigurasi persisnya. Selenium lebih berat dikonfigurasi daripada Playwright dan penantiannya lebih panjang, sehingga proyek baru sering dimulai dengan Playwright kecuali ada alasan untuk tetap menggunakan Selenium.
Bagaimana mengurangi bandwidth proxy saat merender halaman?
Blokir resource yang tidak Anda perlukan agar browser berhenti mengunduh gambar, font, dan media yang tidak pernah Anda urai. Browser headless mengambil setiap aset secara default, dan setiap byte tersebut menambah biaya ketika dialihkan melalui proxy yang dihargai per gigabyte.
Playwright memungkinkan Anda mencegat request dan membatalkan yang tidak relevan dengan ekstraksi. Memblokir gambar, media, dan font dapat mengurangi lalu lintas proxy secara drastis sambil membiarkan JSON dan HTML yang benar-benar Anda baca tetap utuh:
BLOCK = {"image", "media", "font"}
def block_heavy(route):
if route.request.resource_type in BLOCK:
route.abort()
else:
route.continue_()
context = browser.new_context()
context.route("**/*", block_heavy)
page = context.new_page()
page.goto("https://example.com/products", wait_until="domcontentloaded")
Anda dapat memperluas handler yang sama untuk melewati domain analitik dan iklan yang menambah beban tanpa menambah data. Dengan lalu lintas DataImpulse yang ditagih pay-as-you-go mulai dari 1 dolar per GB dan tidak kedaluwarsa, memangkas byte terbuang langsung menurunkan biaya tiap scraping. Menggunakan kembali satu konteks browser pada halaman terkait juga menghindari biaya startup berulang, dan request termurah tetaplah request yang Anda hindari dengan beralih ke API tersembunyi.
Bagaimana menambahkan penanganan error dan retry?
Bungkus setiap navigasi dalam loop retry dengan exponential backoff agar satu pemuatan lambat atau blokir sementara tidak menghentikan seluruh proses. Halaman dinamis gagal secara berkala, dan scraper yang menyerah pada timeout pertama akan kehilangan sebagian besar record dalam skala besar.
Tangkap timeout yang dihasilkan Playwright, tunggu interval yang makin panjang, dan lakukan retry beberapa kali dalam batas yang ditentukan sebelum mencatat kegagalan lalu melanjutkan:
import time
from playwright.sync_api import TimeoutError as PlaywrightTimeout
def fetch_with_retries(page, url, selector, retries=3, backoff=2):
for attempt in range(1, retries + 1):
try:
page.goto(url, wait_until="domcontentloaded", timeout=30000)
page.wait_for_selector(selector, timeout=15000)
return page.query_selector_all(selector)
except PlaywrightTimeout:
wait = backoff ** attempt
print(f"Attempt {attempt} timed out, retrying in {wait}s")
time.sleep(wait)
raise RuntimeError(f"Failed to load {url} after {retries} attempts")
Pasangkan retry dengan rotasi proxy agar setiap percobaan keluar melalui IP baru. Jika satu jalur keluar terkena rate limit, request berikutnya tiba di alamat lain dan sering berhasil. Pool rotating residential proxies atau mobile proxies secara otomatis menetapkan IP baru per request, yang mengubah banyak blokir keras menjadi retry sederhana. Untuk menangani kegagalan pada level proxy secara khusus, catatan kami tentang HTTP error 407 membahas respons autentikasi yang paling umum.
Bagaimana melakukan scraping banyak halaman dinamis secara bersamaan?
Gunakan async API Playwright dengan semaphore asyncio agar beberapa halaman dirender sekaligus tanpa membebani mesin atau target Anda. Concurrency membuat scraping dinamis mendapatkan kembali throughput-nya, karena setiap halaman browser menghabiskan sebagian besar waktunya untuk menunggu jaringan.
Semaphore membatasi jumlah halaman yang berjalan paralel. Bagikan satu browser dan konteks ke seluruh task, buka satu halaman per URL, lalu kumpulkan hasilnya:
import asyncio
from playwright.async_api import async_playwright
PROXY = {
"server": "http://gw.dataimpulse.com:823",
"username": "USERNAME",
"password": "PASSWORD",
}
async def scrape_one(context, url, sem):
async with sem:
page = await context.new_page()
try:
await page.goto(url, wait_until="domcontentloaded")
await page.wait_for_selector("div.product-card", timeout=15000)
cards = await page.query_selector_all("div.product-card")
return [await c.inner_text() for c in cards]
finally:
await page.close()
async def main(urls):
sem = asyncio.Semaphore(5) # at most 5 pages at once
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True, proxy=PROXY)
context = await browser.new_context()
tasks = [scrape_one(context, u, sem) for u in urls]
results = await asyncio.gather(*tasks)
await browser.close()
return results
urls = ["https://example.com/products?page=%d" % i for i in range(1, 21)]
data = asyncio.run(main(urls))
Pertahankan batas concurrency tetap moderat. Terlalu banyak halaman paralel menghabiskan memori dan memicu rate limit, yang membuat retry lebih banyak daripada penghematan dari paralelisme. Mulailah dari sekitar lima dan naikkan hanya selama tingkat keberhasilan tetap tinggi.
Bagaimana mengekspor data hasil scraping ke JSON dan CSV?
Kumpulkan hasil setiap halaman ke dalam daftar dictionary, lalu tulis daftar itu ke JSON untuk data bertingkat atau CSV untuk tabel datar yang ramah spreadsheet. Memisahkan ekstraksi dan ekspor membuat scraper lebih mudah diuji dan dijalankan ulang.
import json
import csv
records = [
{"name": "Laptop A", "price": "999"},
{"name": "Laptop B", "price": "1299"},
]
# Export to JSON
with open("products.json", "w", encoding="utf-8") as f:
json.dump(records, f, ensure_ascii=False, indent=2)
# Export to CSV
with open("products.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "price"])
writer.writeheader()
writer.writerows(records)
Pilih JSON ketika record berisi daftar atau objek bertingkat, dan CSV ketika setiap record memiliki field datar yang sama serta analis akan membukanya di spreadsheet. Normalisasi field sebelum menulis agar setiap record memiliki key yang sama, sehingga tahap ekspor tetap sederhana, apa pun cara halaman dinamis dirender. Dari sini, JSON yang sama dapat digunakan untuk memuat database atau menjalankan analisis tanpa penguraian tambahan.

Pertanyaan yang sering diajukan
Bagaimana saya dapat mengetahui apakah sebuah halaman dinamis sebelum menulis scraper?
Bandingkan View Source dengan panel Elements di developer tools, atau hitung elemen target dalam raw HTML dibandingkan dengan DOM yang dirender. Jika data hanya muncul setelah JavaScript berjalan, halaman tersebut dinamis dan memerlukan panggilan API atau browser headless.
Apakah lebih baik memanggil API tersembunyi atau menggunakan browser headless?
Panggil API tersembunyi ketika Anda dapat menemukannya, karena API tersebut mengembalikan JSON bersih dengan sebagian kecil biaya dan waktu rendering. Gunakan browser headless hanya ketika tidak ada API yang dapat direproduksi atau data bergantung pada skrip sisi klien yang kompleks.
Haruskah saya menggunakan Playwright atau Selenium untuk scraping dinamis?
Playwright adalah pilihan modern berkat auto-waiting yang andal dan dukungan async bawaan, sehingga proyek baru biasanya dimulai dari sana. Selenium cocok ketika stack Anda sudah menggunakannya atau Anda membutuhkan dukungan bahasanya yang lebih luas.
Bagaimana mengurangi bandwidth proxy saat menggunakan browser headless?
Cegat request dan batalkan gambar, font, media, serta analitik yang tidak Anda urai. Ini dapat mengurangi lalu lintas secara signifikan karena biaya proxy diukur per gigabyte, dan juga mempercepat setiap pemuatan halaman.
Bagaimana melakukan scraping konten yang dimuat melalui infinite scroll?
Pertama, periksa tab Network untuk panggilan XHR yang dipaginasi di balik scroll dan lakukan loop langsung pada endpoint tersebut. Jika Anda harus melakukan scroll, lakukan dalam loop terkontrol dan tunggu setiap batch baru dirender sebelum membacanya.
Kapan DataImpulse bukan pilihan yang tepat?
Jika Anda memerlukan static ISP proxies, scraping API yang dikelola penuh, atau akses ke situs perbankan dan pemerintah, DataImpulse bukan alat yang tepat. Layanan ini berfokus pada rotating residential, mobile, dan datacenter proxies untuk mengumpulkan data publik dan mengakses konten.
Lakukan scraping halaman dinamis dengan proxy andal
Jika scraper Anda memerlukan IP residential, mobile, atau datacenter yang berperilaku seperti pengunjung asli, Anda dapat memulai dengan lalu lintas pay-as-you-go mulai dari 1 dolar per GB. Buat akun DataImpulse dan alihkan scraping dinamis Anda melalui rotating atau sticky session.
