PuppeteerとPlaywrightは、ブラウザの自動化とウェブスクレイピングのための人気のあるツールです。大量のリクエストを処理したり、保護されたサイトをパースする際には、IPブロックを回避するためにプロキシの使用が極めて重要です。このガイドでは、基本的な設定からローテーションやエラーハンドリングを含む高度なシナリオまで、両方のツールにおけるプロキシの統合方法を詳しく説明します。
ヘッドレスブラウザにおけるプロキシの基本
PuppeteerとPlaywrightは、DevToolsプロトコルを介して実際のブラウザ(Chromium、Firefox、WebKit)を制御します。これは、プロキシが個々のリクエストではなく、ブラウザの起動時に設定されることを意味します。両方のツールはHTTP、HTTPS、SOCKS5プロキシをサポートしていますが、それらを設定するためのAPIは異なります。
通常のHTTPライブラリ(axios、fetch)との主な違いは次のとおりです:
- プロキシはブラウザの起動時に設定される — ブラウザのセッション内でプロキシを動的に変更することはできません
- JavaScriptのサポートとレンダリング — プロキシはページのすべてのリソース(画像、スクリプト、XHR)に適用されます
- リダイレクトとクッキーの自動処理 — ブラウザは実際のユーザーのように振る舞います
- フィンガープリンティング — プロキシを使用しても、サイトはブラウザの特性に基づいて自動化を特定することができます
重要: IPを頻繁に変更する必要があるタスク(数千ページのパース、大量登録)には、ローテーション付きのレジデンシャルプロキシを使用する方が効果的です — これにより、ブラウザを再起動することなく、各リクエストごとにIPを変更できます。
Puppeteerにおけるプロキシ設定
Puppeteerは、Chrome/Chromiumを制御するためのGoogleのライブラリです。プロキシは、--proxy-serverフラグを使用してブラウザの起動引数を介して設定されます。
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();
// IPアドレスの確認
await page.goto('https://api.ipify.org?format=json');
const content = await page.content();
console.log('現在のIP:', content);
await browser.close();
})();
このコードは、プロキシサーバーを介してブラウザを起動します。すべてのHTTPおよびHTTPSリクエストは、指定されたプロキシを通過します。動作確認には、外部IPアドレスを返すipifyサービスが使用されます。
SOCKS5プロキシの設定
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=socks5://proxy.example.com:1080'
]
});
// 残りのコードは同様です
SOCKS5プロキシは、より低いレベルで動作し、UDPトラフィックをサポートしているため、一部のウェブアプリケーションに役立つ場合があります。構文はHTTPプロキシと同じで、URL内のプロトコルのみが変更されます。
特定のドメインのためのプロキシの使用
const browser = await puppeteer.launch({
args: [
'--proxy-server=http://proxy1.example.com:8080',
'--proxy-bypass-list=localhost;127.0.0.1;*.internal.com'
]
});
// ローカルリクエストと*.internal.comへのリクエストは直接行われます
// その他はすべてプロキシを通過します
--proxy-bypass-listフラグは、特定のドメインをプロキシから除外することを可能にします。これは、直接リクエストとプロキシリクエストを組み合わせる必要がある場合に便利です。
Playwrightにおけるプロキシ設定
Playwrightは、Chromium、Firefox、WebKitをサポートするMicrosoftのより現代的なライブラリです。プロキシは設定オブジェクトを介して設定されるため、APIがより理解しやすく、型付けされています。
プロキシの基本設定
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:', ip);
await browser.close();
})();
Playwrightは、コマンドライン引数の代わりにproxyオブジェクトを使用します。これにより、TypeScriptでのより良い型付けと、よりクリーンなコードが実現されます。
コンテキストレベルでのプロキシ設定
const browser = await chromium.launch();
// コンテキスト1 - プロキシあり
const context1 = await browser.newContext({
proxy: {
server: 'http://proxy1.example.com:8080'
}
});
// コンテキスト2 - 別のプロキシ
const context2 = await browser.newContext({
proxy: {
server: 'http://proxy2.example.com:8080'
}
});
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// 各ページはそれぞれのプロキシを使用します!
Playwrightの主な利点の1つは、1つのプロセス内で異なるプロキシを持つ複数のブラウザコンテキストを作成できることです。これにより、多数のIPアドレスを扱う際にリソースを節約できます。
プロキシからのドメイン除外
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
bypass: 'localhost,127.0.0.1,*.internal.com'
}
});
// localhostおよび*.internal.comへのリクエストは直接行われます
ユーザー名とパスワードによる認証
ほとんどの商用プロキシは認証を必要とします。両方のツールは認証をサポートしていますが、実装方法は異なります。
Puppeteerにおける認証
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxy.example.com:8080']
});
const page = await browser.newPage();
// プロキシの資格情報を設定
await page.authenticate({
username: 'your_username',
password: 'your_password'
});
await page.goto('https://httpbin.org/ip');
const content = await page.content();
console.log(content);
page.authenticate()メソッドは、このページのすべての後続リクエストのための資格情報を設定します。最初のpage.goto()を呼び出す前に呼び出すことが重要です。
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は、プロキシ設定オブジェクト内で直接資格情報を指定できるため、追加のメソッド呼び出しが不要で、より便利で安全です。
代替方法:URL内の資格情報
// 両方のフレームワークで動作します
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 }
});
この方法は機能しますが、プロダクション環境では推奨されません — 資格情報がログに記録される可能性があります。機密データを保存するために環境変数を使用してください。
アドバイス:商業サイトのパースを行う場合は、レジデンシャルプロキシの使用をお勧めします — これらは実際の家庭ユーザーのIPを持ち、ボット対策システムによってブロックされることが少ないです。
プロキシのローテーションとIPプールの管理
大規模なパースには、定期的にIPアドレスを変更する必要があります。プロキシはブラウザの起動時に設定されるため、ローテーションにはブラウザセッションの再起動が必要です。
プロキシの配列を使用した簡単なローテーション(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(`エラーが発生しました ${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);
このコードは、リストからプロキシを循環的に選択し、各URLごとに新しいブラウザを起動します。i % proxyList.lengthメソッドは、循環ローテーションを保証します。
コンテキストプールを使用したローテーション(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();
// 異なるプロキシを持つコンテキストプールを作成
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} (プロキシ ${i % contexts.length}):`, title);
} catch (error) {
console.error(`エラーが発生しました ${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は、1つのブラウザ内で複数のコンテキストを作成でき、それぞれに独自のプロキシを持たせることができます。これにより、ブラウザを完全に再起動することなく、メモリと起動時間を節約できます。
エラー追跡によるスマートローテーション
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('すべてのプロキシが利用できません');
}
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(`プロキシ ${index} が利用できないとマークされました`);
}
resetFailed() {
this.failedProxies.clear();
}
}
// 使用例
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(`プロキシ ${index} でエラーが発生しました:`, error.message);
rotator.markFailed(index);
await browser.close();
if (attempt === maxRetries - 1) {
throw new Error(`${url} を ${maxRetries} 回の試行後に読み込めませんでした`);
}
}
}
}
このクラスは、動作しないプロキシを追跡し、それらをローテーションから除外します。エラーが発生した場合は、自動的にリストの次のプロキシに切り替わります。
エラーハンドリングと動作確認
プロキシを使用する際には、接続タイムアウト、認証拒否、ターゲットサイトによるIPブロックなどの特有のエラーが発生します。エラーを正しく処理することは、パーサーの安定した動作にとって重要です。
一般的なエラーとその処理
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(`試行 ${attempt} が失敗しました:`, error.message);
// エラーのタイプを分析
if (error.message.includes('ERR_PROXY_CONNECTION_FAILED')) {
throw new Error('プロキシが利用できません');
}
if (error.message.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
throw new Error('プロキシのトンネリングエラー');
}
if (error.message.includes('407')) {
throw new Error('プロキシ認証エラー (407)');
}
if (error.message.includes('Navigation timeout')) {
console.log(`読み込みタイムアウト、${config.retryDelay}ms 後に再試行します`);
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
continue;
}
// 最後の試行であればエラーをスロー
if (attempt === config.maxRetries) {
throw error;
}
}
}
}
プロキシの動作確認
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;
}
// プロキシリストのテスト
async function validateProxyList(proxyList) {
console.log('プロキシを確認中...');
const results = await Promise.all(
proxyList.map(async (proxy, index) => {
const result = await testProxy(proxy);
console.log(`プロキシ ${index + 1}:`, result.working ? `✓ ${result.ip} (${result.responseTime}ms)` : `✗ ${result.error}`);
return { proxy, ...result };
})
);
const workingProxies = results.filter(r => r.working);
console.log(`\n動作中のプロキシ: ${workingProxies.length}/${proxyList.length}`);
return workingProxies.map(r => r.proxy);
}
この関数は、使用前に各プロキシを確認し、応答時間を測定し、動作するプロキシのみを返します。アプリケーションの起動時にバリデーションを実行することをお勧めします。
モニタリングとロギング
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++;
}
}
// 各プロキシの統計
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`
};
}
}
// 使用例
const monitor = new ProxyMonitor();
async function monitoredScrape(url, proxyIndex) {
const startTime = Date.now();
try {
// ... パースコード ...
const responseTime = Date.now() - startTime;
monitor.recordRequest(proxyIndex, true, responseTime);
} catch (error) {
monitor.recordRequest(proxyIndex, false, 0, error.message);
throw error;
}
}
高度なシナリオ:ジオロケーションとフィンガープリンティング
現代のボット対策システムは、IPアドレスだけでなく、ジオロケーション、タイムゾーン、ブラウザの言語などのパラメータも確認します。別の国のプロキシを使用する場合は、これらのすべてのパラメータを正しく設定することが重要です。
プロキシに合わせたジオロケーションと言語の設定
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 };
}
// 例:ドイツのプロキシ
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はベルリンの結果を表示するドイツ語版を表示します
このコードは、ブラウザをドイツの実際のユーザーのように見せるように設定します:インターフェースのドイツ語、ベルリンのタイムゾーン、ベルリンの中心の座標。
フィンガープリンティングの完全設定
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;
}
// 米国のWindows 10 + Chrome用のプロファイル
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 }
};
// 英国のiPhone用のプロファイル
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 }
};
フィンガープリンティングの包括的な設定は、自動化の検出の可能性を低下させます。すべてのパラメータが互いに一致することが重要です:たとえば、モバイルのUser-Agentは、モバイルの画面解像度とともに使用する必要があります。
重要:広告プラットフォーム(Google Ads、Facebook Ads)や金融サービスを扱う場合は、モバイルプロキシの使用をお勧めします — これらは実際のモバイルオペレーターの信頼スコアを持ち、ほとんどブロックされません。
WebRTCリークの回避
// Puppeteer: 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: WebRTC APIをオーバーライド
const context = await browser.newContext({ proxy: proxyConfig });
await context.addInitScript(() => {
// RTCPeerConnectionをブロック
window.RTCPeerConnection = undefined;
window.RTCDataChannel = undefined;
window.RTCSessionDescription = undefined;
// getUserMediaをオーバーライド
navigator.mediaDevices.getUserMedia = undefined;
navigator.getUserMedia = undefined;
});
WebRTCは、プロキシを使用していても、実際のIPアドレスを漏らす可能性があります。このコードは、ブラウザ内のWebRTC APIを完全に無効にします。
プロキシ作業のためのPuppeteerとPlaywrightの比較
| 基準 | Puppeteer | Playwright |
|---|---|---|
| プロキシ設定 | コマンドライン引数を介して | 設定オブジェクトを介して |
| 認証 | 起動後にpage.authenticate() | 作成時のproxyオブジェクト内 |
| 1つのブラウザ内での複数プロキシ | いいえ、別のブラウザが必要 | はい、異なるコンテキストを介して |
| ブラウザのサポート | Chromium/Chromeのみ | Chromium、Firefox、WebKit |
| パフォーマンス | 1つのブラウザの迅速な起動 | 複数のコンテキストでの効率的な処理 |
| TypeScript | @types/puppeteerを介した型 | TypeScriptの組み込みサポート |
| ドキュメント | 良好で、多くの例があります | 優れた、より構造化されています |
| エコシステム | より多くのプラグインと拡張機能 | より迅速に進化し、新機能が追加されます |
選択に関する推奨事項
Puppeteerを選択する場合:
- Chrome/Chromiumのみを使用している
- 1つのプロキシを同時に使用している
- 既存のツールとの互換性が最大限に必要
- Puppeteerに対する大規模なコードベースがある
Playwrightを選択する場合:
- FirefoxやSafari(WebKit)との互換性が必要
- 複数のプロキシを同時に使用する必要がある
- スケーリング時のパフォーマンスが重要
- TypeScriptで書いている
- 新しいプロジェクトをゼロから開始する
プロキシローテーション時のパフォーマンス
テスト:MacBook Pro M1で10プロキシのローテーションを使用して100ページをパースする:
| メソッド | 実行時間 | RAM消費量 |
|---|---|---|
| Puppeteer(ブラウザ再起動) | 8分23秒 | ~1.2 GB ピーク |
| Playwright(ブラウザ再起動) | 7分54秒 | ~1.1 GB ピーク |
| Playwright(コンテキストプール) | 4分12秒 | ~800 MB 安定 |
Playwrightはコンテキストプールを使用することで、ブラウザの起動コストがないため、ほぼ2倍の速度で動作します。これは、数千ページのパース時に重要です。
結論
PuppeteerとPlaywrightにおけるプロキシの統合は、ウェブスクレイピング、テスト、自動化の標準的なプラクティスです。Puppeteerはシンプルさと広範なエコシステムを提供し、Playwrightは現代的なAPIと複数のプロキシを扱う際の優れたパフォーマンスを提供します。
私たちが考慮した重要なポイント:
- 両方のフレームワークにおけるHTTP、HTTPS、SOCKS5プロキシの基本設定
- ユーザー名とパスワードによる認証
- 大規模パースのためのプロキシローテーション
- エラーハンドリングと使用前のプロキシバリデーション
- ボット対策システムを回避するためのジオロケーションとフィンガープリンティングの設定
- さまざまなアプローチのパフォーマンス比較
プロダクションソリューションには、質の高いプロキシをフィンガープリンティング、エラーハンドリング、モニタリングの正しい設定と組み合わせることをお勧めします。これにより、保護されたサイトでもパーサーの安定した動作が保証されます。
商業サイト、マーケットプレイス、広告プラットフォームをパースする予定がある場合は、レジデンシャルプロキシの使用をお勧めします — これにより、実際の家庭ユーザーのIPアドレスを使用することで、最大の匿名性と最小のブロックリスクが保証されます。