Puppeteer et Playwright sont des outils populaires pour l'automatisation des navigateurs et le web scraping. Lorsqu'il s'agit de traiter de grands volumes de requêtes ou de scraper des sites protégés, l'utilisation de proxy devient critique pour éviter les blocages par IP. Dans ce guide, nous examinerons toutes les méthodes d'intégration de proxy dans les deux outils, de la configuration de base aux scénarios avancés avec rotation et gestion des erreurs.
Principes de base du fonctionnement des proxy dans les navigateurs headless
Puppeteer et Playwright contrôlent de vrais navigateurs (Chromium, Firefox, WebKit) via le DevTools Protocol. Cela signifie que le proxy est configuré au niveau du lancement du navigateur, et non des requêtes individuelles. Les deux outils prennent en charge les proxy HTTP, HTTPS et SOCKS5, mais ont des API différentes pour leur configuration.
Les principales différences par rapport aux bibliothèques HTTP classiques (axios, fetch) :
- Le proxy est défini lors du lancement du navigateur — il n'est pas possible de changer le proxy à la volée dans une seule session de navigateur
- Support de JavaScript et rendu — le proxy s'applique à toutes les ressources de la page (images, scripts, XHR)
- Gestion automatique des redirections et des cookies — le navigateur se comporte comme un véritable utilisateur
- Fingerprinting — même avec un proxy, les sites peuvent détecter l'automatisation par les caractéristiques du navigateur
Important : Pour les tâches nécessitant un changement fréquent d'IP (scraping de milliers de pages, enregistrement massif), il est plus efficace d'utiliser des proxy résidentiels avec rotation — ils permettent de changer d'IP à chaque requête sans redémarrer le navigateur.
Configuration de proxy dans Puppeteer
Puppeteer est une bibliothèque de Google pour contrôler Chrome/Chromium. Le proxy est configuré via les arguments de lancement du navigateur à l'aide du drapeau --proxy-server.
Configuration de base des 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();
// Vérification de l'adresse IP
await page.goto('https://api.ipify.org?format=json');
const content = await page.content();
console.log('IP actuelle :', content);
await browser.close();
})();
Ce code lance le navigateur via un serveur proxy. Toutes les requêtes HTTP et HTTPS passeront par le proxy spécifié. Pour vérifier la fonctionnalité, nous utilisons le service ipify, qui renvoie l'adresse IP externe.
Configuration des proxy SOCKS5
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=socks5://proxy.example.com:1080'
]
});
// Le reste du code est similaire
Les proxy SOCKS5 fonctionnent à un niveau plus bas et prennent en charge le trafic UDP, ce qui peut être utile pour certaines applications web. La syntaxe est identique à celle des proxy HTTP, seul le protocole dans l'URL change.
Utilisation de proxy pour des domaines spécifiques
const browser = await puppeteer.launch({
args: [
'--proxy-server=http://proxy1.example.com:8080',
'--proxy-bypass-list=localhost;127.0.0.1;*.internal.com'
]
});
// Les requêtes locales et les requêtes vers *.internal.com passeront directement
// Toutes les autres passeront par le proxy
Le drapeau --proxy-bypass-list permet d'exclure certains domaines du proxy. Cela est utile lorsque vous devez combiner des requêtes directes et des requêtes proxy.
Configuration de proxy dans Playwright
Playwright est une bibliothèque plus moderne de Microsoft, prenant en charge Chromium, Firefox et WebKit. Le proxy est configuré via un objet de configuration, ce qui rend l'API plus claire et typée.
Configuration de base des 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 via proxy :', ip);
await browser.close();
})();
Playwright utilise l'objet proxy au lieu des arguments de ligne de commande. Cela assure une meilleure typage en TypeScript et un code plus propre.
Configuration des proxy au niveau du contexte
const browser = await chromium.launch();
// Contexte 1 - avec proxy
const context1 = await browser.newContext({
proxy: {
server: 'http://proxy1.example.com:8080'
}
});
// Contexte 2 - avec un autre proxy
const context2 = await browser.newContext({
proxy: {
server: 'http://proxy2.example.com:8080'
}
});
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// Chaque page utilise son propre proxy !
Un des avantages clés de Playwright est la possibilité de créer plusieurs contextes de navigateur avec différents proxy dans un seul processus. Cela économise des ressources lors de l'utilisation de plusieurs adresses IP.
Exclusion de domaines du proxy
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
bypass: 'localhost,127.0.0.1,*.internal.com'
}
});
// Les requêtes vers localhost et *.internal.com passeront directement
Authentification par nom d'utilisateur et mot de passe
La plupart des proxy commerciaux nécessitent une authentification. Les deux outils prennent en charge l'autorisation, mais l'implémentent différemment.
Authentification dans Puppeteer
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxy.example.com:8080']
});
const page = await browser.newPage();
// Définir les informations d'identification pour le 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);
La méthode page.authenticate() définit les informations d'identification pour toutes les requêtes suivantes sur cette page. Il est important de l'appeler AVANT le premier page.goto().
Authentification dans 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 permet de spécifier les informations d'identification directement dans l'objet de configuration du proxy — c'est plus pratique et sécurisé, car cela ne nécessite pas d'appels de méthodes supplémentaires.
Méthode alternative : informations d'identification dans l'URL
// Fonctionne dans les deux frameworks
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 }
});
Cette méthode fonctionne, mais n'est pas recommandée pour la production — les informations d'identification peuvent apparaître dans les journaux. Utilisez des variables d'environnement pour stocker des données sensibles.
Conseil : Lors du scraping de sites commerciaux, nous recommandons d'utiliser des proxy résidentiels — ils possèdent de vraies IP d'utilisateurs domestiques et sont moins souvent bloqués par les systèmes anti-bot.
Rotation des proxy et gestion du pool d'IP
Pour le scraping à grande échelle, il est nécessaire de changer régulièrement les adresses IP. Étant donné que le proxy est défini lors du lancement du navigateur, la rotation nécessite le redémarrage de la session du navigateur.
Rotation simple avec un tableau de 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(`Erreur sur ${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);
Ce code parcourt cycliquement les proxy de la liste et lance un nouveau navigateur pour chaque URL. La méthode i % proxyList.length assure une rotation cyclique.
Rotation avec un pool de contextes (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();
// Créer un pool de contextes avec différents proxy
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(`Erreur sur ${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 permet de créer plusieurs contextes dans un seul navigateur, chacun avec son propre proxy. Cela économise de la mémoire et du temps de démarrage par rapport à un redémarrage complet du navigateur.
Rotation intelligente avec suivi des erreurs
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('Tous les proxy sont indisponibles');
}
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} marqué comme indisponible`);
}
resetFailed() {
this.failedProxies.clear();
}
}
// Utilisation
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(`Erreur avec le proxy ${index} :`, error.message);
rotator.markFailed(index);
await browser.close();
if (attempt === maxRetries - 1) {
throw new Error(`Impossible de charger ${url} après ${maxRetries} tentatives`);
}
}
}
}
Cette classe suit les proxy non fonctionnels et les exclut de la rotation. En cas d'erreur, elle passe automatiquement au proxy suivant de la liste.
Gestion des erreurs et vérification de la fonctionnalité
Lors de l'utilisation de proxy, des erreurs spécifiques peuvent survenir : délais d'attente de connexion, refus d'autorisation, blocage de l'IP par le site cible. Une gestion correcte des erreurs est cruciale pour le fonctionnement stable du scraper.
Erreurs typiques et leur gestion
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(`Tentative ${attempt} échouée :`, error.message);
// Analyse du type d'erreur
if (error.message.includes('ERR_PROXY_CONNECTION_FAILED')) {
throw new Error('Proxy indisponible');
}
if (error.message.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
throw new Error('Erreur de tunneling du proxy');
}
if (error.message.includes('407')) {
throw new Error('Erreur d\'autorisation du proxy (407)');
}
if (error.message.includes('Navigation timeout')) {
console.log(`Délai d'attente de chargement, nouvelle tentative dans ${config.retryDelay}ms`);
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
continue;
}
// Si c'est la dernière tentative - relancer l'erreur
if (attempt === config.maxRetries) {
throw error;
}
}
}
}
Vérification de la fonctionnalité des 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;
}
// Test de la liste de proxy
async function validateProxyList(proxyList) {
console.log('Vérification des 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 fonctionnels : ${workingProxies.length}/${proxyList.length}`);
return workingProxies.map(r => r.proxy);
}
Cette fonction vérifie chaque proxy avant utilisation, mesure le temps de réponse et ne renvoie que les proxy fonctionnels. Il est recommandé d'effectuer une validation au démarrage de l'application.
Surveillance et journalisation
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++;
}
}
// Statistiques par 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`
};
}
}
// Utilisation
const monitor = new ProxyMonitor();
async function monitoredScrape(url, proxyIndex) {
const startTime = Date.now();
try {
// ... code de scraping ...
const responseTime = Date.now() - startTime;
monitor.recordRequest(proxyIndex, true, responseTime);
} catch (error) {
monitor.recordRequest(proxyIndex, false, 0, error.message);
throw error;
}
}
Scénarios avancés : géolocalisation et fingerprinting
Les systèmes anti-bot modernes vérifient non seulement l'adresse IP, mais aussi la correspondance de la géolocalisation, du fuseau horaire, de la langue du navigateur et d'autres paramètres. Lors de l'utilisation de proxy d'un autre pays, il est important de configurer tous ces paramètres correctement.
Configuration de la géolocalisation et de la langue pour le 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 };
}
// Exemple : proxy d'Allemagne
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 affichera la version allemande avec des résultats pour Berlin
Ce code configure le navigateur pour qu'il ressemble à un véritable utilisateur d'Allemagne : langue de l'interface en allemand, fuseau horaire de Berlin et coordonnées du centre de Berlin.
Configuration complète du 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 pour Windows 10 + Chrome des États-Unis
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 pour iPhone du Royaume-Uni
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 }
};
Une configuration complète du fingerprint réduit la probabilité de détection de l'automatisation. Il est important que tous les paramètres correspondent les uns aux autres : par exemple, un User-Agent mobile doit être associé à une résolution d'écran mobile.
Important : Lors de l'utilisation de plateformes publicitaires (Google Ads, Facebook Ads) ou de services financiers, nous recommandons d'utiliser des proxy mobiles — ils ont un score de confiance de véritables opérateurs mobiles et sont pratiquement jamais bloqués.
Contournement des fuites WebRTC
// Puppeteer : blocage de 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 : redéfinition de l'API WebRTC
const context = await browser.newContext({ proxy: proxyConfig });
await context.addInitScript(() => {
// Bloquer RTCPeerConnection
window.RTCPeerConnection = undefined;
window.RTCDataChannel = undefined;
window.RTCSessionDescription = undefined;
// Redéfinir getUserMedia
navigator.mediaDevices.getUserMedia = undefined;
navigator.getUserMedia = undefined;
});
WebRTC peut révéler votre véritable adresse IP même en utilisant un proxy. Ce code désactive complètement l'API WebRTC dans le navigateur.
Comparaison de Puppeteer et Playwright pour le travail avec des proxy
| Critère | Puppeteer | Playwright |
|---|---|---|
| Configuration de proxy | Via les arguments de ligne de commande | Via un objet de configuration |
| Authentification | page.authenticate() après le lancement | Dans l'objet proxy lors de la création |
| Plusieurs proxy dans un seul navigateur | Non, un navigateur séparé est nécessaire | Oui, via différents contextes |
| Support des navigateurs | Seulement Chromium/Chrome | Chromium, Firefox, WebKit |
| Performance | Démarrage rapide d'un seul navigateur | Plus efficace avec de nombreux contextes |
| TypeScript | Types via @types/puppeteer | Support intégré de TypeScript |
| Documentation | Bonne, beaucoup d'exemples | Excellente, plus structurée |
| Écosystème | Plus de plugins et d'extensions | Se développe plus rapidement, nouvelles fonctionnalités |
Recommandations de choix
Choisissez Puppeteer si :
- Vous travaillez uniquement avec Chrome/Chromium
- Vous utilisez un seul proxy à la fois
- Vous avez besoin de la compatibilité maximale avec les outils existants
- Vous avez déjà une grande base de code sur Puppeteer
Choisissez Playwright si :
- Vous avez besoin de travailler avec Firefox ou Safari (WebKit)
- Vous devez utiliser plusieurs proxy simultanément
- La performance est importante lors de l'échelle
- Vous écrivez en TypeScript
- Vous commencez un nouveau projet à partir de zéro
Performance lors de la rotation des proxy
Test : scraping de 100 pages avec rotation de 10 proxy sur MacBook Pro M1 :
| Méthode | Temps d'exécution | Consommation RAM |
|---|---|---|
| Puppeteer (redémarrage du navigateur) | 8 minutes 23 secondes | ~1.2 Go au pic |
| Playwright (redémarrage du navigateur) | 7 minutes 54 secondes | ~1.1 Go au pic |
| Playwright (pool de contextes) | 4 minutes 12 secondes | ~800 Mo stable |
Playwright avec un pool de contextes est presque deux fois plus rapide grâce à l'absence de frais généraux liés au démarrage du navigateur. Cela est critique lors du scraping de milliers de pages.
Conclusion
L'intégration de proxy avec Puppeteer et Playwright est une pratique standard pour le web scraping, les tests et l'automatisation. Puppeteer offre simplicité et large écosystème, tandis que Playwright propose une API moderne et de meilleures performances lors du travail avec plusieurs proxy via des contextes de navigateur.
Points clés que nous avons abordés :
- Configuration de base des proxy HTTP, HTTPS et SOCKS5 dans les deux frameworks
- Authentification par nom d'utilisateur et mot de passe
- Rotation des proxy pour le scraping à grande échelle
- Gestion des erreurs et validation des proxy avant utilisation
- Configuration de la géolocalisation et du fingerprint pour contourner les systèmes anti-bot
- Comparaison des performances des différentes approches
Pour des solutions de production, nous recommandons de combiner des proxy de qualité avec une configuration correcte du fingerprint, une gestion des erreurs et une surveillance. Cela garantira un fonctionnement stable du scraper même sur des sites protégés.
Si vous envisagez de scraper des sites commerciaux, des places de marché ou de travailler avec des plateformes publicitaires, nous recommandons d'utiliser des proxy résidentiels — ils offrent une anonymité maximale et un risque minimal de blocages grâce à de vraies adresses IP d'utilisateurs domestiques.