Puppeteer dan Playwright adalah alat populer untuk otomatisasi browser dan pengambilan data web. Saat bekerja dengan volume permintaan yang besar atau mengikis situs yang dilindungi, penggunaan proxy menjadi sangat penting untuk menghindari pemblokiran berdasarkan IP. Dalam panduan ini, kita akan membahas semua cara untuk mengintegrasikan proxy ke dalam kedua alat ini, dari pengaturan dasar hingga skenario lanjutan dengan rotasi dan penanganan kesalahan.
Dasar-dasar kerja proxy di browser headless
Puppeteer dan Playwright mengelola browser nyata (Chromium, Firefox, WebKit) melalui Protokol DevTools. Ini berarti bahwa proxy diatur pada tingkat peluncuran browser, bukan permintaan individu. Kedua alat mendukung proxy HTTP, HTTPS, dan SOCKS5, tetapi memiliki API yang berbeda untuk pengaturannya.
Perbedaan utama dari pustaka HTTP biasa (axios, fetch):
- Proxy diatur saat peluncuran browser — tidak dapat mengubah proxy secara langsung dalam satu sesi browser
- Dukungan JavaScript dan rendering — proxy diterapkan ke semua sumber daya halaman (gambar, skrip, XHR)
- Penanganan otomatis untuk pengalihan dan cookie — browser berperilaku seperti pengguna nyata
- Fingerprinting — bahkan dengan proxy, situs dapat mendeteksi otomatisasi berdasarkan karakteristik browser
Penting: Untuk tugas yang memerlukan seringnya perubahan IP (mengikis ribuan halaman, pendaftaran massal), lebih efektif menggunakan proxy residensial dengan rotasi — mereka memungkinkan perubahan IP untuk setiap permintaan tanpa perlu memulai ulang browser.
Pengaturan proxy di Puppeteer
Puppeteer adalah pustaka dari Google untuk mengelola Chrome/Chromium. Proxy diatur melalui argumen peluncuran browser dengan menggunakan flag --proxy-server.
Pengaturan dasar proxy HTTP/HTTPS
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=http://proxy.example.com:8080'
]
});
const page = await browser.newPage();
// Memeriksa alamat IP
await page.goto('https://api.ipify.org?format=json');
const content = await page.content();
console.log('IP saat ini:', content);
await browser.close();
})();
Kode ini meluncurkan browser melalui server proxy. Semua permintaan HTTP dan HTTPS akan melewati proxy yang ditentukan. Untuk memeriksa fungsionalitas, digunakan layanan ipify, yang mengembalikan alamat IP eksternal.
Pengaturan proxy SOCKS5
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=socks5://proxy.example.com:1080'
]
});
// Kode lainnya sama
Proxy SOCKS5 bekerja pada tingkat yang lebih rendah dan mendukung lalu lintas UDP, yang bisa berguna untuk beberapa aplikasi web. Sintaksisnya identik dengan proxy HTTP, hanya protokol dalam URL yang berubah.
Menggunakan proxy untuk domain tertentu
const browser = await puppeteer.launch({
args: [
'--proxy-server=http://proxy1.example.com:8080',
'--proxy-bypass-list=localhost;127.0.0.1;*.internal.com'
]
});
// Permintaan lokal dan permintaan ke *.internal.com akan langsung
// Semua yang lain — melalui proxy
Flag --proxy-bypass-list memungkinkan pengecualian domain tertentu dari pemrosesan proxy. Ini berguna ketika perlu menggabungkan permintaan langsung dan yang diproses melalui proxy.
Pengaturan proxy di Playwright
Playwright adalah pustaka yang lebih modern dari Microsoft, mendukung Chromium, Firefox, dan WebKit. Proxy diatur melalui objek konfigurasi, yang membuat API lebih mudah dipahami dan terstruktur.
Pengaturan dasar proxy
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080'
}
});
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://api.ipify.org?format=json');
const ip = await page.textContent('body');
console.log('IP melalui proxy:', ip);
await browser.close();
})();
Playwright menggunakan objek proxy alih-alih argumen baris perintah. Ini memberikan pengetikan yang lebih baik dalam TypeScript dan kode yang lebih bersih.
Pengaturan proxy pada tingkat konteks
const browser = await chromium.launch();
// Konteks 1 - dengan proxy
const context1 = await browser.newContext({
proxy: {
server: 'http://proxy1.example.com:8080'
}
});
// Konteks 2 - dengan proxy lain
const context2 = await browser.newContext({
proxy: {
server: 'http://proxy2.example.com:8080'
}
});
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// Setiap halaman menggunakan proxy mereka sendiri!
Salah satu keuntungan utama Playwright adalah kemampuan untuk membuat beberapa konteks browser dengan proxy yang berbeda dalam satu proses. Ini menghemat sumber daya saat bekerja dengan banyak alamat IP.
Pengecualian domain dari pemrosesan proxy
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
bypass: 'localhost,127.0.0.1,*.internal.com'
}
});
// Permintaan ke localhost dan *.internal.com akan langsung
Autentikasi dengan nama pengguna dan kata sandi
Sebagian besar proxy komersial memerlukan autentikasi. Kedua alat mendukung otorisasi, tetapi mengimplementasikannya dengan cara yang berbeda.
Autentikasi di Puppeteer
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxy.example.com:8080']
});
const page = await browser.newPage();
// Mengatur kredensial untuk proxy
await page.authenticate({
username: 'your_username',
password: 'your_password'
});
await page.goto('https://httpbin.org/ip');
const content = await page.content();
console.log(content);
Metode page.authenticate() mengatur kredensial untuk semua permintaan berikutnya di halaman ini. Penting untuk memanggilnya SEBELUM page.goto() pertama.
Autentikasi di Playwright
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
username: 'your_username',
password: 'your_password'
}
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
Playwright memungkinkan Anda untuk menentukan kredensial langsung dalam objek konfigurasi proxy — ini lebih nyaman dan aman, karena tidak memerlukan panggilan metode tambahan.
Metode alternatif: kredensial dalam URL
// Bekerja di kedua kerangka kerja
const proxyUrl = 'http://username:password@proxy.example.com:8080';
// Puppeteer
const browser = await puppeteer.launch({
args: [`--proxy-server=${proxyUrl}`]
});
// Playwright
const browser = await chromium.launch({
proxy: { server: proxyUrl }
});
Metode ini berfungsi, tetapi tidak disarankan untuk produksi — kredensial dapat masuk ke log. Gunakan variabel lingkungan untuk menyimpan data sensitif.
Tips: Saat bekerja dengan pengambilan data dari situs komersial, kami merekomendasikan untuk menggunakan proxy residensial — mereka memiliki IP nyata dari pengguna rumah dan lebih jarang diblokir oleh sistem anti-bot.
Rotasi proxy dan pengelolaan kumpulan IP
Untuk pengambilan data skala besar, perlu untuk secara teratur mengganti alamat IP. Karena proxy diatur saat peluncuran browser, rotasi memerlukan pemulihan sesi browser.
Rotasi sederhana dengan array proxy (Puppeteer)
const puppeteer = require('puppeteer');
const proxyList = [
'http://user1:pass1@proxy1.example.com:8080',
'http://user2:pass2@proxy2.example.com:8080',
'http://user3:pass3@proxy3.example.com:8080'
];
async function scrapeWithRotation(urls) {
for (let i = 0; i < urls.length; i++) {
const proxyUrl = proxyList[i % proxyList.length];
const browser = await puppeteer.launch({
args: [`--proxy-server=${proxyUrl}`]
});
try {
const page = await browser.newPage();
await page.goto(urls[i], { waitUntil: 'networkidle0' });
const data = await page.evaluate(() => {
return {
title: document.title,
url: window.location.href
};
});
console.log(`URL ${i + 1}:`, data);
} catch (error) {
console.error(`Kesalahan di ${urls[i]}:`, error.message);
} finally {
await browser.close();
}
}
}
const urlsToScrape = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
'https://example.com/page4'
];
scrapeWithRotation(urlsToScrape);
Kode ini secara siklis mengulangi proxy dari daftar dan meluncurkan browser baru untuk setiap URL. Metode i % proxyList.length memastikan rotasi siklis.
Rotasi dengan kumpulan konteks (Playwright)
const { chromium } = require('playwright');
const proxyList = [
{ server: 'http://proxy1.example.com:8080', username: 'user1', password: 'pass1' },
{ server: 'http://proxy2.example.com:8080', username: 'user2', password: 'pass2' },
{ server: 'http://proxy3.example.com:8080', username: 'user3', password: 'pass3' }
];
async function scrapeWithContextPool(urls) {
const browser = await chromium.launch();
// Membuat kumpulan konteks dengan proxy yang berbeda
const contexts = await Promise.all(
proxyList.map(proxy => browser.newContext({ proxy }))
);
for (let i = 0; i < urls.length; i++) {
const context = contexts[i % contexts.length];
const page = await context.newPage();
try {
await page.goto(urls[i], { waitUntil: 'networkidle' });
const title = await page.title();
console.log(`URL ${i + 1} (proxy ${i % contexts.length}):`, title);
} catch (error) {
console.error(`Kesalahan di ${urls[i]}:`, error.message);
} finally {
await page.close();
}
}
await browser.close();
}
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
];
scrapeWithContextPool(urls);
Playwright memungkinkan pembuatan beberapa konteks dalam satu browser, masing-masing dengan proxy-nya sendiri. Ini menghemat memori dan waktu peluncuran dibandingkan dengan memulai ulang browser sepenuhnya.
Rotasi cerdas dengan pelacakan kesalahan
class ProxyRotator {
constructor(proxyList) {
this.proxyList = proxyList;
this.currentIndex = 0;
this.failedProxies = new Set();
}
getNext() {
const availableProxies = this.proxyList.filter(
(_, index) => !this.failedProxies.has(index)
);
if (availableProxies.length === 0) {
throw new Error('Semua proxy tidak tersedia');
}
const proxy = this.proxyList[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.proxyList.length;
return { proxy, index: this.currentIndex - 1 };
}
markFailed(index) {
this.failedProxies.add(index);
console.log(`Proxy ${index} ditandai sebagai tidak tersedia`);
}
resetFailed() {
this.failedProxies.clear();
}
}
// Penggunaan
const rotator = new ProxyRotator(proxyList);
async function scrapeWithSmartRotation(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const { proxy, index } = rotator.getNext();
const browser = await chromium.launch({ proxy });
const page = await browser.newPage();
try {
await page.goto(url, { timeout: 30000 });
const data = await page.content();
await browser.close();
return data;
} catch (error) {
console.error(`Kesalahan dengan proxy ${index}:`, error.message);
rotator.markFailed(index);
await browser.close();
if (attempt === maxRetries - 1) {
throw new Error(`Gagal memuat ${url} setelah ${maxRetries} percobaan`);
}
}
}
}
Kelas ini melacak proxy yang tidak berfungsi dan mengecualikannya dari rotasi. Saat terjadi kesalahan, secara otomatis beralih ke proxy berikutnya dalam daftar.
Penanganan kesalahan dan pemeriksaan fungsionalitas
Saat bekerja dengan proxy, muncul kesalahan spesifik: waktu habis koneksi, penolakan otorisasi, pemblokiran IP oleh situs target. Penanganan kesalahan yang tepat sangat penting untuk stabilitas pengambilan data.
Kesalahan umum dan penanganannya
async function safePageLoad(page, url, options = {}) {
const defaultOptions = {
timeout: 30000,
waitUntil: 'networkidle0',
maxRetries: 3,
retryDelay: 2000
};
const config = { ...defaultOptions, ...options };
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
try {
await page.goto(url, {
timeout: config.timeout,
waitUntil: config.waitUntil
});
return { success: true, attempt };
} catch (error) {
console.error(`Percobaan ${attempt} gagal:`, error.message);
// Analisis jenis kesalahan
if (error.message.includes('ERR_PROXY_CONNECTION_FAILED')) {
throw new Error('Proxy tidak tersedia');
}
if (error.message.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
throw new Error('Kesalahan tunneling proxy');
}
if (error.message.includes('407')) {
throw new Error('Kesalahan otorisasi proxy (407)');
}
if (error.message.includes('Navigation timeout')) {
console.log(`Waktu habis pemuatan, ulang dalam ${config.retryDelay}ms`);
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
continue;
}
// Jika ini adalah percobaan terakhir - lempar kesalahan
if (attempt === config.maxRetries) {
throw error;
}
}
}
}
Pemeriksaan fungsionalitas proxy
async function testProxy(proxyConfig) {
const browser = await chromium.launch({ proxy: proxyConfig });
const page = await browser.newPage();
const result = {
working: false,
ip: null,
responseTime: null,
error: null
};
const startTime = Date.now();
try {
await page.goto('https://api.ipify.org?format=json', { timeout: 10000 });
const content = await page.textContent('body');
const data = JSON.parse(content);
result.working = true;
result.ip = data.ip;
result.responseTime = Date.now() - startTime;
} catch (error) {
result.error = error.message;
} finally {
await browser.close();
}
return result;
}
// Pengujian daftar proxy
async function validateProxyList(proxyList) {
console.log('Memeriksa proxy...');
const results = await Promise.all(
proxyList.map(async (proxy, index) => {
const result = await testProxy(proxy);
console.log(`Proxy ${index + 1}:`, result.working ? `✓ ${result.ip} (${result.responseTime}ms)` : `✗ ${result.error}`);
return { proxy, ...result };
})
);
const workingProxies = results.filter(r => r.working);
console.log(`\nProxy yang berfungsi: ${workingProxies.length}/${proxyList.length}`);
return workingProxies.map(r => r.proxy);
}
Fungsi ini memeriksa setiap proxy sebelum digunakan, mengukur waktu respons, dan hanya mengembalikan proxy yang berfungsi. Disarankan untuk menjalankan validasi saat memulai aplikasi.
Pemantauan dan pencatatan
class ProxyMonitor {
constructor() {
this.stats = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
proxyErrors: 0,
averageResponseTime: 0,
requestsByProxy: new Map()
};
}
recordRequest(proxyIndex, success, responseTime, error = null) {
this.stats.totalRequests++;
if (success) {
this.stats.successfulRequests++;
this.stats.averageResponseTime =
(this.stats.averageResponseTime * (this.stats.successfulRequests - 1) + responseTime) /
this.stats.successfulRequests;
} else {
this.stats.failedRequests++;
if (error && error.includes('proxy')) {
this.stats.proxyErrors++;
}
}
// Statistik untuk setiap proxy
if (!this.stats.requestsByProxy.has(proxyIndex)) {
this.stats.requestsByProxy.set(proxyIndex, { success: 0, failed: 0 });
}
const proxyStats = this.stats.requestsByProxy.get(proxyIndex);
success ? proxyStats.success++ : proxyStats.failed++;
}
getReport() {
const successRate = (this.stats.successfulRequests / this.stats.totalRequests * 100).toFixed(2);
return {
...this.stats,
successRate: `${successRate}%`,
averageResponseTime: `${this.stats.averageResponseTime.toFixed(0)}ms`
};
}
}
// Penggunaan
const monitor = new ProxyMonitor();
async function monitoredScrape(url, proxyIndex) {
const startTime = Date.now();
try {
// ... kode pengambilan data ...
const responseTime = Date.now() - startTime;
monitor.recordRequest(proxyIndex, true, responseTime);
} catch (error) {
monitor.recordRequest(proxyIndex, false, 0, error.message);
throw error;
}
}
Skenario lanjutan: geolokasi dan fingerprinting
Sistem anti-bot modern tidak hanya memeriksa alamat IP, tetapi juga kesesuaian geolokasi, zona waktu, bahasa browser, dan parameter lainnya. Saat menggunakan proxy dari negara lain, penting untuk mengatur semua parameter ini dengan benar.
Pengaturan geolokasi dan bahasa untuk proxy
const { chromium } = require('playwright');
async function createContextWithGeo(proxy, geoData) {
const browser = await chromium.launch({ proxy });
const context = await browser.newContext({
locale: geoData.locale, // 'en-US', 'de-DE', 'fr-FR'
timezoneId: geoData.timezone, // 'America/New_York', 'Europe/Berlin'
geolocation: {
latitude: geoData.latitude,
longitude: geoData.longitude
},
permissions: ['geolocation']
});
return { browser, context };
}
// Contoh: proxy dari Jerman
const germanyProxy = {
server: 'http://de-proxy.example.com:8080',
username: 'user',
password: 'pass'
};
const germanyGeo = {
locale: 'de-DE',
timezone: 'Europe/Berlin',
latitude: 52.520008,
longitude: 13.404954
};
const { browser, context } = await createContextWithGeo(germanyProxy, germanyGeo);
const page = await context.newPage();
await page.goto('https://www.google.com');
// Google akan menampilkan versi Jerman dengan hasil untuk Berlin
Kode ini mengatur browser agar terlihat seperti pengguna nyata dari Jerman: bahasa antarmuka Jerman, zona waktu Berlin, dan koordinat pusat Berlin.
Pengaturan lengkap fingerprint
async function createStealthContext(proxy, profile) {
const context = await chromium.launch({ proxy }).then(b =>
b.newContext({
locale: profile.locale,
timezoneId: profile.timezone,
userAgent: profile.userAgent,
viewport: profile.viewport,
deviceScaleFactor: profile.deviceScaleFactor,
isMobile: profile.isMobile,
hasTouch: profile.hasTouch,
colorScheme: profile.colorScheme,
geolocation: profile.geolocation,
permissions: ['geolocation']
})
);
return context;
}
// Profil untuk Windows 10 + Chrome dari AS
const desktopUSProfile = {
locale: 'en-US',
timezone: 'America/New_York',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 },
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
colorScheme: 'light',
geolocation: { latitude: 40.7128, longitude: -74.0060 }
};
// Profil untuk iPhone dari Inggris
const mobileUKProfile = {
locale: 'en-GB',
timezone: 'Europe/London',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
viewport: { width: 390, height: 844 },
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
colorScheme: 'light',
geolocation: { latitude: 51.5074, longitude: -0.1278 }
};
Pengaturan fingerprint yang komprehensif mengurangi kemungkinan deteksi otomatisasi. Penting agar semua parameter saling sesuai: misalnya, User-Agent seluler harus disertai dengan resolusi layar seluler.
Penting: Saat bekerja dengan platform iklan (Google Ads, Facebook Ads) atau layanan keuangan, kami merekomendasikan untuk menggunakan proxy seluler — mereka memiliki trust score dari operator seluler nyata dan hampir tidak pernah diblokir.
Menghindari kebocoran WebRTC
// Puppeteer: memblokir WebRTC
const browser = await puppeteer.launch({
args: [
'--proxy-server=http://proxy.example.com:8080',
'--disable-webrtc',
'--disable-webrtc-hw-encoding',
'--disable-webrtc-hw-decoding'
]
});
// Playwright: mendefinisikan ulang API WebRTC
const context = await browser.newContext({ proxy: proxyConfig });
await context.addInitScript(() => {
// Memblokir RTCPeerConnection
window.RTCPeerConnection = undefined;
window.RTCDataChannel = undefined;
window.RTCSessionDescription = undefined;
// Mendefinisikan ulang getUserMedia
navigator.mediaDevices.getUserMedia = undefined;
navigator.getUserMedia = undefined;
});
WebRTC dapat mengungkapkan alamat IP nyata Anda bahkan saat menggunakan proxy. Kode ini sepenuhnya menonaktifkan API WebRTC di browser.
Perbandingan Puppeteer dan Playwright untuk bekerja dengan proxy
| Kriteria | Puppeteer | Playwright |
|---|---|---|
| Pengaturan proxy | Melalui args baris perintah | Melalui objek konfigurasi |
| Autentikasi | page.authenticate() setelah peluncuran | Dalam objek proxy saat pembuatan |
| Beberapa proxy dalam satu browser | Tidak, perlu browser terpisah | Ya, melalui konteks yang berbeda |
| Dukungan browser | Hanya Chromium/Chrome | Chromium, Firefox, WebKit |
| Kinerja | Peluncuran cepat satu browser | Lebih efisien saat banyak konteks |
| TypeScript | Tipe melalui @types/puppeteer | Dukungan TypeScript bawaan |
| Dokumentasi | Baik, banyak contoh | Luar biasa, lebih terstruktur |
| Ekosistem | Lebih banyak plugin dan ekstensi | Lebih cepat berkembang, fitur baru |
Rekomendasi pemilihan
Pilih Puppeteer jika:
- Anda hanya bekerja dengan Chrome/Chromium
- Anda menggunakan satu proxy sekaligus
- Anda memerlukan kompatibilitas maksimum dengan alat yang ada
- Anda sudah memiliki basis kode besar di Puppeteer
Pilih Playwright jika:
- Anda memerlukan kerja dengan Firefox atau Safari (WebKit)
- Anda memerlukan penggunaan beberapa proxy secara bersamaan
- Performa penting saat skala besar
- Anda menulis dalam TypeScript
- Anda memulai proyek baru dari awal
Kinerja saat rotasi proxy
Uji: pengambilan data 100 halaman dengan rotasi 10 proxy di MacBook Pro M1:
| Metode | Waktu eksekusi | Konsumsi RAM |
|---|---|---|
| Puppeteer (memulai ulang browser) | 8 menit 23 detik | ~1.2 GB puncak |
| Playwright (memulai ulang browser) | 7 menit 54 detik | ~1.1 GB puncak |
| Playwright (kumpulan konteks) | 4 menit 12 detik | ~800 MB stabil |
Playwright dengan kumpulan konteks hampir dua kali lebih cepat karena tidak ada biaya overhead untuk memulai browser. Ini sangat penting saat mengikis ribuan halaman.
Kesimpulan
Integrasi proxy dengan Puppeteer dan Playwright adalah praktik standar untuk pengambilan data web, pengujian, dan otomatisasi. Puppeteer menawarkan kesederhanaan dan ekosistem yang luas, sementara Playwright menawarkan API modern dan kinerja yang lebih baik saat bekerja dengan banyak proxy melalui konteks browser.
Poin-poin kunci yang telah kita bahas:
- Pengaturan dasar proxy HTTP, HTTPS, dan SOCKS5 di kedua kerangka kerja
- Autentikasi dengan nama pengguna dan kata sandi
- Rotasi proxy untuk pengambilan data skala besar
- Penanganan kesalahan dan validasi proxy sebelum digunakan
- Pengaturan geolokasi dan fingerprint untuk menghindari sistem anti-bot
- Perbandingan kinerja berbagai pendekatan
Untuk solusi produksi, kami merekomendasikan untuk menggabungkan proxy berkualitas dengan pengaturan fingerprint yang tepat, penanganan kesalahan, dan pemantauan. Ini akan memastikan stabilitas pengambilan data bahkan di situs yang dilindungi.
Jika Anda berencana untuk mengikis situs komersial, marketplace, atau bekerja dengan platform iklan, kami merekomendasikan untuk menggunakan proxy residensial — mereka memberikan anonimitas maksimum dan risiko pemblokiran minimal berkat alamat IP nyata dari pengguna rumah.