In this Article
Panduan ini menjelaskan cara melakukan web scraping dengan JavaScript menggunakan Node.js, runtime yang paling sering dipilih engineer saat ingin mengumpulkan data publik dalam skala besar. Mempelajari cara melakukan web scraping dengan JavaScript berarti memilih tool yang tepat untuk halaman tersebut: HTTP client yang ringan untuk HTML statis, atau headless browser untuk konten yang dimuat melalui skrip.
Di bawah ini Anda akan menemukan kode Node.js yang berfungsi dan dapat dijalankan untuk kedua kasus tersebut, beserta penyiapan proyek, ekspor terstruktur ke JSON dan CSV, pagination, retry dengan backoff, rate limiting, autentikasi proxy, intersepsi XHR, serta isu etika dan pemblokiran yang menentukan apakah scraper tetap berfungsi seiring waktu.
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 banyak akun.
Fakta Utama
- Dua toolchain: Halaman statis hanya memerlukan HTTP client dan parser seperti fetch serta cheerio, sedangkan halaman dinamis yang merender konten di browser memerlukan engine headless seperti Puppeteer atau Playwright.
- Jenis proxy terbaik: proxy residential rotating, 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 nilai 4,8 dari 5 di G2.
- Protokol dan targeting: HTTP, HTTPS, dan SOCKS5, dengan country targeting yang sudah termasuk.

Apa itu web scraping dengan JavaScript?
Web scraping dengan JavaScript adalah proses meminta halaman web secara terprogram dan mengekstrak data terstruktur darinya menggunakan JavaScript, yang biasanya dijalankan pada runtime Node.js, bukan di dalam tab browser.
JavaScript sangat sesuai karena bahasa ini sudah memodelkan Document Object Model, sehingga pemilihan elemen terasa familier bagi developer front-end. Dalam praktiknya, scraper melakukan tiga hal: mengambil halaman melalui HTTP, mengurai markup yang diterima menjadi pohon yang dapat dikueri, lalu mengambil field yang Anda perlukan, seperti judul, harga, atau link. Kerumitannya berasal dari cara halaman target menyajikan datanya, sehingga pemilihan tool yang tepat lebih penting daripada penguraiannya sendiri. Halaman statis mengirim data dalam respons HTML pertama, sedangkan halaman dinamis menyusunnya di browser setelah dimuat, dan kedua kasus tersebut memerlukan toolchain yang berbeda.
Bagaimana menyiapkan proyek scraping Node.js?
Buat folder proyek, inisialisasi dengan npm, lalu instal library yang Anda perlukan sebelum menulis kode scraping.
Node.js 18 dan versi lebih baru menyediakan global bawaan fetch, sehingga Anda tidak memerlukan library HTTP untuk request GET sederhana, meskipun banyak tim tetap memilih axios karena interceptor dan penanganan proxy-nya. Perintah di bawah ini membuat kerangka proyek dan menginstal cheerio untuk parsing, axios sebagai client alternatif, p-limit untuk pengendalian concurrency, serta Puppeteer dan Playwright untuk halaman dinamis:
mkdir js-scraper && cd js-scraper
npm init -y
npm install cheerio axios p-limit
npm install puppeteer playwright
# enable modern import syntax
npm pkg set type=module
Menetapkan type=module memungkinkan Anda menggunakan import statement. Jika Anda lebih memilih CommonJS, pertahankan pengaturan default dan gunakan require sebagai gantinya. Contoh dalam panduan ini menggunakan sintaks import, tetapi masing-masing dapat langsung dipetakan ke pemanggilan require . Setelah instalasi selesai, Anda memiliki semua yang diperlukan untuk scraping statis dan dinamis dalam satu proyek.
Bagaimana melakukan scraping halaman statis dengan fetch dan cheerio?
Untuk halaman statis, unduh HTML dengan fungsi fetch bawaan dan urai dengan cheerio, yang memberi Anda API bergaya jQuery di server.
Halaman statis langsung mengembalikan kontennya dalam respons HTML awal, sehingga rendering browser tidak diperlukan. Pendekatan ini cepat dan murah karena Anda hanya mengunduh markup, bukan gambar, font, atau skrip. Contoh di bawah mengambil halaman, memuatnya ke cheerio, dan membaca setiap kartu produk:
import * as cheerio from 'cheerio';
async function scrape(url) {
const res = await fetch(url, {
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' }
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const html = await res.text();
const $ = cheerio.load(html);
const items = [];
$('.product').each((i, el) => {
items.push({
title: $(el).find('h2').text().trim(),
price: $(el).find('.price').text().trim(),
link: $(el).find('a').attr('href')
});
});
return items;
}
console.log(await scrape('https://example.com/catalog'));
Jika Anda lebih memilih axios, bagian parsing tetap sama dan hanya request-nya yang berubah. Axios otomatis menghasilkan error untuk kode status non-2xx dan memudahkan konfigurasi proxy, sehingga banyak scraper production menggunakannya:
import axios from 'axios';
import * as cheerio from 'cheerio';
const { data: html } = await axios.get('https://example.com/catalog', {
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' },
timeout: 15000
});
const $ = cheerio.load(html);
const titles = $('.product h2').map((i, el) => $(el).text().trim()).get();
console.log(titles);
Sebelum mengarahkan scraper ke situs mana pun, sebaiknya pastikan target mengizinkannya. Panduan kami tentang cara memeriksa apakah situs web mengizinkan scraping membahas robots.txt dan ketentuan layanan.
Bagaimana mengekstrak data terstruktur ke JSON dan CSV?
Kumpulkan setiap record ke dalam array objek biasa, lalu tulis array tersebut ke file JSON, atau ratakan menjadi baris yang dipisahkan koma untuk file CSV.
Teks mentah hasil scraping baru berguna setelah disimpan dalam bentuk yang konsisten. JSON adalah target paling sederhana karena array JavaScript dapat diserialisasi secara langsung. Cuplikan di bawah menulis item hasil scraping ke disk menggunakan modul file system bawaan:
import { writeFile } from 'node:fs/promises';
const items = await scrape('https://example.com/catalog');
await writeFile('products.json', JSON.stringify(items, null, 2), 'utf-8');
console.log(`Saved ${items.length} records to products.json`);
CSV lebih baik ketika data digunakan untuk spreadsheet atau data warehouse. Anda dapat membuatnya tanpa library dengan melakukan escape pada tanda kutip dan menggabungkan field, meskipun escape penting karena harga dan judul dapat mengandung koma:
import { writeFile } from 'node:fs/promises';
function toCsv(rows) {
const headers = Object.keys(rows[0]);
const escape = (v) => `"${String(v ?? '').replace(/"/g, '""')}"`;
const lines = [headers.join(',')];
for (const row of rows) {
lines.push(headers.map((h) => escape(row[h])).join(','));
}
return lines.join('\n');
}
const items = await scrape('https://example.com/catalog');
await writeFile('products.csv', toCsv(items), 'utf-8');
Jaga agar nama field tetap konsisten antarproses agar tool lanjutan tidak rusak ketika layout halaman sedikit berubah. Jika Anda juga perlu mengambil aset biner, lihat panduan kami tentang cara melakukan scraping gambar dari situs web.
Bagaimana melakukan scraping beberapa halaman dengan pagination?
Ulangi URL halaman situs atau ikuti link halaman berikutnya pada setiap halaman, lakukan scraping pada masing-masing halaman, dan berhenti ketika tidak ada lagi hasil.
Sebagian besar katalog membagi hasil ke halaman bernomor menggunakan parameter query seperti ?page=2, atau menyediakan link berikutnya. Pola paling aman membaca setiap halaman, mengumpulkan record-nya, lalu mencari halaman berikutnya dan berhenti ketika halaman itu tidak ada. Contoh di bawah menelusuri halaman bernomor sampai suatu halaman tidak mengembalikan item:
import * as cheerio from 'cheerio';
async function scrapeAll(base) {
const all = [];
for (let page = 1; page <= 50; page++) {
const res = await fetch(`${base}?page=${page}`, {
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' }
});
if (!res.ok) break;
const $ = cheerio.load(await res.text());
const items = $('.product').map((i, el) => ({
title: $(el).find('h2').text().trim(),
price: $(el).find('.price').text().trim()
})).get();
if (items.length === 0) break;
all.push(...items);
await new Promise((r) => setTimeout(r, 1000));
}
return all;
}
console.log((await scrapeAll('https://example.com/catalog')).length);
Perhatikan jeda satu detik antarhalaman dan batas tegas 50 iterasi, yang mencegah loop tak terkendali jika situs tidak pernah mengembalikan halaman kosong. Ketika konten dimuat dengan scrolling, bukan melalui halaman bernomor, Anda memerlukan headless browser, sebagaimana dibahas pada bagian Puppeteer di bawah.
Bagaimana menangani retry, backoff, dan rate limiting?
Bungkus setiap request dalam helper retry yang menunggu lebih lama setelah setiap kegagalan, lalu batasi jumlah request yang berjalan bersamaan agar tetap dalam batas toleransi situs.
Jaringan tidak selalu andal dan server mengembalikan error sementara, sehingga scraper yang menyerah pada kegagalan pertama akan kehilangan data tanpa perlu. Helper retry dengan exponential backoff menunggu semakin lama di antara percobaan, yang juga memberi server yang terkena rate limiting waktu untuk pulih:
async function fetchWithRetry(fn, retries = 4, delay = 1000) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === retries) throw err;
const wait = delay * 2 ** (attempt - 1);
console.warn(`Attempt ${attempt} failed, retrying in ${wait}ms`);
await new Promise((r) => setTimeout(r, wait));
}
}
}
const html = await fetchWithRetry(async () => {
const res = await fetch('https://example.com/catalog');
if (res.status === 429) throw new Error('Rate limited');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
});
Backoff menangani request tunggal, tetapi Anda juga perlu mengendalikan berapa banyak request yang berjalan paralel. Mengirim ratusan request serentak menurunkan kinerja server target dan membuat lalu lintas Anda ditandai. Library p-limit membatasi concurrency sehingga sejumlah worker tetap memproses antrean URL:
import pLimit from 'p-limit';
const limit = pLimit(5); // at most 5 requests in flight
const urls = Array.from({ length: 100 }, (_, i) =>
`https://example.com/item/${i + 1}`
);
const results = await Promise.all(
urls.map((url) =>
limit(() => fetchWithRetry(async () => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
}))
)
);
console.log(`Fetched ${results.length} pages with a cap of 5 in parallel`);
Bersama-sama, backoff dan batas concurrency menjaga jejak aktivitas Anda tetap wajar serta tingkat keberhasilan tetap tinggi. Untuk checklist yang lebih lengkap, lihat panduan kami tentang praktik terbaik web scraping .
Bagaimana melakukan scraping halaman dinamis dengan Puppeteer?
Untuk halaman dinamis yang membangun konten dengan JavaScript setelah dimuat, jalankan Chrome headless dengan Puppeteer, tunggu selector target, lalu baca DOM yang telah dirender sepenuhnya.
Banyak situs modern mengembalikan kerangka HTML yang nyaris kosong, lalu mengambil data melalui request di latar belakang. HTTP client tidak melihat apa pun yang berguna, sehingga Anda memerlukan engine yang menjalankan JavaScript milik halaman itu sendiri. Puppeteer mengendalikan Chrome headless dan dapat menunggu elemen, melakukan klik, serta scrolling. Contoh di bawah menunggu selector, lalu melakukan scrolling berulang kali untuk memicu pemuatan infinite scroll sebelum mengekstrak data dalam konteks browser:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (compatible; MyScraper/1.0)');
await page.goto('https://example.com/feed', { waitUntil: 'networkidle2' });
// wait until the first batch of cards is present
await page.waitForSelector('.product');
// infinite scroll: scroll to the bottom until height stops growing
let previousHeight = 0;
for (let i = 0; i < 20; i++) {
const height = await page.evaluate('document.body.scrollHeight');
if (height === previousHeight) break;
previousHeight = height;
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
await new Promise((r) => setTimeout(r, 1500));
}
const items = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product')).map((el) => ({
title: el.querySelector('h2')?.innerText.trim(),
price: el.querySelector('.price')?.innerText.trim()
}))
);
console.log(`Extracted ${items.length} items`);
await browser.close();
Loop berhenti segera setelah tinggi halaman tidak lagi bertambah, yang menandakan tidak ada konten baru yang dimuat, dan batas tegas 20 kali scrolling mencegah loop tanpa akhir. Untuk pembahasan lebih lanjut tentang jenis target ini, lihat panduan kami tentang cara melakukan scraping halaman web dinamis.
Bagaimana menggunakan Playwright sebagai alternatif Puppeteer?
Playwright bekerja hampir identik dengan Puppeteer, tetapi menambahkan dukungan kelas satu untuk Chromium, Firefox, dan WebKit, serta locator yang menunggu secara otomatis untuk mengurangi selector yang tidak stabil.
Playwright dibuat oleh beberapa engineer asli Puppeteer dan berbagi sebagian besar API-nya, sehingga migrasi biasanya hanya perlu mengganti nama beberapa pemanggilan. Locator-nya otomatis menunggu elemen dapat digunakan, sehingga menghilangkan banyak pemanggilan manual waitForSelector . Scraper yang setara terlihat seperti ini:
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (compatible; MyScraper/1.0)'
});
const page = await context.newPage();
await page.goto('https://example.com/feed', { waitUntil: 'networkidle' });
await page.locator('.product').first().waitFor();
const items = await page.locator('.product').evaluateAll((els) =>
els.map((el) => ({
title: el.querySelector('h2')?.innerText.trim(),
price: el.querySelector('.price')?.innerText.trim()
}))
);
console.log(`Extracted ${items.length} items`);
await browser.close();
Pilih Playwright ketika Anda perlu menguji scraper yang sama di beberapa engine browser atau menginginkan network mocking bawaannya. Pilih Puppeteer ketika Anda hanya menargetkan Chrome dan menginginkan dependency yang lebih kecil. Keduanya menggunakan memori jauh lebih besar daripada fetch dan cheerio, jadi gunakan hanya untuk halaman yang benar-benar memerlukan rendering.
Tool scraping JavaScript mana yang sebaiknya Anda pilih?
Gunakan fetch atau axios dengan cheerio untuk HTML statis, dan gunakan Puppeteer atau Playwright hanya ketika halaman merender datanya di browser.
Tool yang tepat bergantung pada cara halaman menyajikan data dan seberapa besar Anda mengutamakan kecepatan dibanding fidelitas rendering. HTTP client cepat dan ringan, tetapi tidak dapat menjalankan JavaScript; headless browser menjalankan semua yang dilihat pengguna nyata, tetapi membutuhkan lebih banyak memori dan waktu. Tabel di bawah merangkum trade-off tersebut:
| Tool | Menjalankan JS | Kecepatan | Paling cocok untuk |
|---|---|---|---|
| fetch + cheerio | Tidak | Paling cepat | HTML statis, volume tinggi |
| axios + cheerio | Tidak | Cepat | HTML statis, konfigurasi proxy mudah |
| Puppeteer | Ya | Lambat | Halaman dinamis khusus Chrome |
| Playwright | Ya | Lambat | Dinamis, pengujian lintas browser |
Pola production yang umum menggabungkan keduanya: gunakan headless browser hanya untuk mencapai halaman atau endpoint yang memerlukan rendering, lalu gunakan kembali HTTP client biasa untuk sebagian besar request. JavaScript adalah salah satu dari beberapa stack yang layak untuk pekerjaan ini; jika Anda membandingkan ekosistem, panduan kami tentang web scraping dengan Rust membahas alternatif yang dikompilasi.
Bagaimana merutekan scraper JavaScript melalui proxy?
Berikan pengaturan proxy kepada HTTP client atau opsi peluncuran browser agar request keluar dari alamat IP yang berbeda, sehingga beban tersebar dan kemungkinan satu alamat terkena rate limiting berkurang.
Melakukan scraping banyak halaman dari satu IP adalah cara tercepat untuk diblokir. Merotasi request di seluruh pool alamat membuat lalu lintas terlihat seperti banyak pengunjung terpisah. Dengan axios, Anda memberikan host, port, dan kredensial proxy dalam konfigurasi request. Contoh di bawah merutekan melalui gateway DataImpulse:
import axios from 'axios';
const { data } = await axios.get('https://example.com', {
proxy: {
protocol: 'http',
host: 'gw.dataimpulse.com',
port: 823,
auth: { username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' }
},
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' }
});
console.log(data.length, 'bytes received through the proxy');
Puppeteer menerima proxy sebagai argumen peluncuran lalu melakukan autentikasi pada halaman, karena Chrome tidak menerima kredensial di dalam URL proxy. Polanya sama di Playwright, yang langsung menerima objek proxy dengan username dan password:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=gw.dataimpulse.com:823']
});
const page = await browser.newPage();
await page.authenticate({
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD'
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
console.log(await page.title());
await browser.close();
DataImpulse menawarkan proxy residential, proxy datacenter, dan proxy mobile melalui HTTP, HTTPS, dan SOCKS5, dengan rotating dan sticky session. Country targeting sudah termasuk, sedangkan targeting state, city, ZIP, dan ASN adalah add-on berbayar. Jika request proxy mengembalikan error autentikasi, catatan kami tentang error HTTP 407 menjelaskan penyebab yang paling umum, dan panduan kami tentang autentikasi proxy membahas format kredensial secara lebih rinci.
Bagaimana mengintersepsi endpoint XHR dan JSON?
Alih-alih melakukan scraping HTML yang telah dirender, pantau lalu lintas jaringan di headless browser dan baca respons JSON yang diambil halaman di latar belakang, yang sering kali lebih bersih dan cepat.
Halaman dinamis biasanya memperoleh data dari API tersembunyi yang mengembalikan JSON. Membaca respons tersebut secara langsung sepenuhnya melewati parsing DOM dan memberi Anda record yang terstruktur dengan baik. Puppeteer dan Playwright sama-sama memungkinkan Anda mendengarkan respons dan menangkap respons yang diinginkan. Contoh Puppeteer di bawah menangkap setiap respons JSON dari path API:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
const captured = [];
page.on('response', async (response) => {
const url = response.url();
if (url.includes('/api/') && response.headers()['content-type']?.includes('application/json')) {
try {
captured.push(await response.json());
} catch (err) {
// non-JSON body, ignore
}
}
});
await page.goto('https://example.com/feed', { waitUntil: 'networkidle2' });
console.log(`Captured ${captured.length} JSON payloads`);
await browser.close();
Setelah mengetahui URL endpoint dan parameternya, Anda sering kali dapat tidak menggunakan browser sama sekali dan memanggil API JSON dengan fetch biasa, sambil meneruskan header yang sama seperti yang dikirim halaman. Ini memberi Anda kecepatan scraper statis dengan kelengkapan scraper yang dirender, dan merupakan peningkatan performa terbesar yang tersedia untuk situs dinamis.
Apa batasan etika dan hukum dalam scraping?
Lakukan scraping hanya pada data publik, hormati robots.txt dan ketentuan layanan situs, hindari data pribadi, dan jangan pernah membebani server secara berlebihan. Praktik ini membuat proyek Anda dapat dipertanggungjawabkan dan berkelanjutan.
Periksa file robots.txt di root domain untuk melihat path yang diminta situs agar dihindari oleh crawler, dan tanggapi permintaan tersebut dengan serius. Identifikasi scraper Anda dengan User-Agent yang jujur, batasi request, dan cache respons agar Anda tidak mengambil ulang halaman yang sama tanpa perlu. Dari sisi infrastruktur, sumber alamat IP Anda penting. DataImpulse menjalankan proxy etis yang bersumber dari pengguna yang memberikan persetujuan dan menerima kompensasi, selaras dengan GDPR, serta tersedia perjanjian pemrosesan data. Etika bukan sekadar persoalan kepatuhan; scraper yang sopan dan transparan juga lebih mampu terus berfungsi. Untuk prinsip di balik pengumpulan yang berkelanjutan, lihat panduan kami tentang web scraping etis.

Pertanyaan yang sering diajukan
Apakah saya memerlukan Node.js untuk melakukan scraping dengan JavaScript?
Untuk hal yang lebih dari sekadar pengujian cepat satu halaman, ya. Node.js memungkinkan Anda menjalankan scraper di server, mengulang banyak URL, menangani retry, mengekspor data, dan menggunakan proxy, yang semuanya tidak ditangani dengan baik oleh konsol browser.
Kapan saya sebaiknya menggunakan Puppeteer alih-alih fetch dan cheerio?
Gunakan fetch atau axios dengan cheerio ketika data tersedia dalam respons HTML awal. Beralihlah ke Puppeteer atau Playwright hanya ketika konten dirender oleh JavaScript setelah halaman dimuat, karena headless browser jauh lebih berat.
Bagaimana mengekspor data hasil scraping ke CSV di Node.js?
Kumpulkan record sebagai array objek, lalu buat baris yang dipisahkan koma dengan menggabungkan nilai setiap objek dan melakukan escape pada tanda kutip serta koma. Tulis hasilnya ke file dengan modul fs bawaan, atau gunakan library seperti csv-stringify.
Mengapa saya diblokir saat melakukan scraping dengan JavaScript?
Penyebab umumnya adalah terlalu banyak request dari satu IP, User-Agent yang tidak ada atau mencurigakan, serta tidak ada jeda antarrequest. Proxy rotating, header realistis, retry dengan backoff, dan rate limiting mengurangi pemblokiran.
Jenis proxy apa yang terbaik untuk scraping JavaScript?
Proxy datacenter cepat dan murah untuk target yang toleran, sedangkan proxy residential dan mobile tampak lebih seperti pengguna nyata pada situs yang ketat. Session rotating membantu menyebarkan request ke banyak alamat.
Kapan DataImpulse bukan pilihan yang tepat?
Jika Anda memerlukan proxy ISP statis, API scraping yang dikelola sepenuhnya, atau akses ke situs perbankan dan pemerintah, DataImpulse bukan tool yang tepat. DataImpulse berfokus pada proxy residential, mobile, dan datacenter rotating untuk mengumpulkan data publik dan mengakses konten.
Mulai scraping dengan proxy andal
Siap menjalankan scraper JavaScript Anda tanpa pemblokiran terus-menerus? DataImpulse menyediakan IP residential, mobile, dan datacenter yang diperoleh secara etis mulai dari 1 dolar per GB dengan lalu lintas yang tidak kedaluwarsa dan tanpa langganan. Buat akun dan rute scraper pertama Anda melalui proxy hari ini.
