ProxyScrape Web Scraping avec Playwright : faire passer Chromium headless par des proxys HTTP et SOCKS5
Configurez Playwright pour que Chromium headless sorte via ProxyScrape, vérifiez l’IP de sortie avant le scraping, faites tourner les endpoints HTTP ou SOCKS5 par contexte de navigateur, et classez correctement les erreurs de proxy.
Aperçu
Les navigateurs headless placent la configuration du proxy à l’intérieur du navigateur, et non dans votre bibliothèque HTTP. Cela change la configuration : vous configurez le proxy lorsque vous lancez un navigateur ou créez un contexte de navigateur, vous devez gérer l’authentification proxy, et c’est la configuration par contexte qui rend la rotation possible sans redémarrer Chromium à chaque requête.
Ce tutoriel utilise Playwright avec Chromium et des endpoints ProxyScrape (HTTP ou SOCKS5). Il couvre le lancement avec un seul endpoint, une vérification de l’IP de sortie, la rotation par contexte sur plusieurs endpoints, la gestion des erreurs de proxy, et son exécution sur une CI Linux sans épuiser la mémoire.
Quand un navigateur headless est le bon outil
- La cible rend le contenu côté client, donc une simple requête HTTP renvoie une coquille vide.
- Vous devez interagir : remplir des formulaires, cliquer, faire défiler ou attendre que le réseau soit inactif.
- Vous avez besoin d’un comportement au niveau du navigateur, comme les cookies,
localStorageou les service workers. - Vous validez le comportement d’un site pour une région spécifique.
Restez avec un client HTTP simple lorsque la page est rendue côté serveur ; c’est bien moins coûteux et plus rapide.
Prérequis
- Node.js 18 ou plus récent et npm.
- Playwright installé avec Chromium.
- Les identifiants ProxyScrape ainsi que l’hôte et le port pour
httpet/ousocks5. Les proxys résidentiels, datacenter et mobiles fonctionnent tous ici ; choisissez le type qui correspond aux défenses de la cible. - Linux, macOS ou Windows. Les notes CI supposent des runners Debian ou Ubuntu.
Étapes
Étape 1 — Installer Playwright et Chromium
mkdir proxyscrape-playwright
cd proxyscrape-playwright
npm init -y
npm install playwright
npx playwright install --with-deps chromium
--with-deps installe les bibliothèques système dont Chromium a besoin sur les images Linux et les runners CI.
Étape 2 — Décider si le proxy s’applique au navigateur ou au contexte
| Portée | Où il est défini | Utilisation | Mises en garde |
|---|---|---|---|
| Navigateur | chromium.launch({ proxy }) |
Un seul endpoint pour toute l’exécution | Chaque contexte partage une seule IP de sortie |
| Contexte | browser.newContext({ proxy }) |
Rotation : un endpoint par contexte, en parallèle | La prise en charge varie selon le moteur ; Chromium est la valeur sûre par défaut — vérifiez la prise en charge des proxys par contexte pour votre version de Playwright avant de vous y fier dans Firefox ou WebKit |
Deux règles pratiques :
- Passez les identifiants via des champs
usernameetpassworddistincts. Chromium ignore les identifiants intégrés dans l’URL du serveur proxy. - La valeur
serverprend un schéma :http://host:portpour les endpoints HTTP de ProxyScrape etsocks5://host:portpour SOCKS5.
Étape 3 — Lancer via un seul endpoint et vérifier l’IP de sortie
// launch-proxy.mjs
import { chromium } from 'playwright';
const proxy = {
server: process.env.PROXYSCRAPE_SERVER, // http://HOST:PORT or socks5://HOST:PORT
username: process.env.PROXYSCRAPE_USERNAME,
password: process.env.PROXYSCRAPE_PASSWORD,
};
const browser = await chromium.launch({ proxy, headless: true });
try {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://api.ipify.org?format=json', { waitUntil: 'domcontentloaded' });
console.log('exit ip:', await page.textContent('body'));
} finally {
await browser.close();
}
export PROXYSCRAPE_SERVER="http://host-from-your-plan:port-from-your-plan"
export PROXYSCRAPE_USERNAME="your-username"
export PROXYSCRAPE_PASSWORD="your-password"
node launch-proxy.mjs
Si cela affiche une IP, le chemin proxy fonctionne de bout en bout. Si cela reste bloqué ou lève une erreur, consultez le tableau de dépannage avant d’écrire plus de code.
Étape 4 — Échouer rapidement lorsque le trafic n’est pas proxifié
Une mauvaise configuration silencieuse — par exemple, une faute de frappe dans la chaîne du serveur — peut vous faire scraper depuis votre propre IP. Capturez votre IP directe une fois, puis vérifiez-la par assertion.
// exit-ip.mjs
export async function getExitIp(page) {
await page.goto('https://api.ipify.org?format=json', { waitUntil: 'domcontentloaded' });
const body = await page.textContent('body');
return JSON.parse(body).ip;
}
export function assertProxied(exitIp, directIp) {
if (directIp && exitIp === directIp) {
throw new Error(`Traffic was not proxied: exit IP ${exitIp} equals the direct IP`);
}
}
Procédure ordonnée :
- Exécutez un script ponctuel sans proxy configuré et enregistrez le résultat sous
DIRECT_IP. - Exécutez à nouveau le script proxifié.
- Appelez
assertProxied(exitIp, process.env.DIRECT_IP)avant tout scraping réel, et faites échouer l’exécution lorsque l’assertion se déclenche.
Étape 5 — Faire tourner les endpoints entre les contextes de navigateur
Un navigateur, plusieurs contextes, un endpoint chacun.
// rotate-contexts.mjs
import { chromium } from 'playwright';
const ENDPOINTS = (process.env.PROXYSCRAPE_ENDPOINTS ?? '')
.split(',')
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => {
const [scheme, host, port, username, password] = entry.split('|');
return { server: `${scheme}://${host}:${port}`, username, password };
});
if (ENDPOINTS.length === 0) {
throw new Error('Set PROXYSCRAPE_ENDPOINTS to http|HOST|PORT|USER|PASS,socks5|HOST|PORT|USER|PASS');
}
const TARGETS = (process.env.TARGETS ?? 'https://example.com,https://example.org').split(',');
const CONCURRENCY = Number(process.env.CONCURRENCY ?? 3);
const USER_AGENT =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36';
function isProxyError(error) {
const message = String(error && error.message ? error.message : '');
return /ERR_PROXY|ERR_TUNNEL|ERR_PROXY_AUTH/i.test(message);
}
async function runBatch(browser, proxy, urls) {
const context = await browser.newContext({ proxy, userAgent: USER_AGENT });
context.setDefaultTimeout(30000);
const results = [];
try {
const page = await context.newPage();
for (const url of urls) {
try {
const response = await page.goto(url, { waitUntil: 'domcontentloaded' });
results.push({ url, endpoint: proxy.server, status: response ? response.status() : null });
} catch (error) {
results.push({
url,
endpoint: proxy.server,
error: isProxyError(error) ? `proxy: ${error.message}` : error.message,
});
}
}
} finally {
await context.close();
}
return results;
}
const browser = await chromium.launch({ headless: true });
try {
const queue = ENDPOINTS.map((proxy, index) => ({
proxy,
urls: TARGETS.filter((_, targetIndex) => targetIndex % ENDPOINTS.length === index),
}));
const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
const collected = [];
while (queue.length > 0) {
const job = queue.shift();
collected.push(...(await runBatch(browser, job.proxy, job.urls)));
}
return collected;
});
const results = (await Promise.all(workers)).flat();
for (const result of results) {
console.log(JSON.stringify(result));
}
} finally {
await browser.close();
}
export PROXYSCRAPE_ENDPOINTS="http|HOST|PORT|USER|PASS,socks5|HOST|PORT|USER|PASS"
export TARGETS="https://example.com,https://example.org,https://example.net"
export CONCURRENCY=3
node rotate-contexts.mjs
Pourquoi cette forme fonctionne :
- Le navigateur démarre une fois ; les contextes sont peu coûteux et chacun porte son propre endpoint.
- Le pool de workers plafonne le nombre de contextes existant en même temps, ce qui est le principal levier de mémoire dans Playwright.
- Chaque résultat enregistre l’endpoint qui l’a servi, de sorte que les échecs peuvent être attribués sans deviner.
Étape 6 — Traiter les échecs proxy comme des échecs d’endpoint, et non comme des échecs de page
net::ERR_PROXY_CONNECTION_FAILED— l’hôte ou le port du proxy est incorrect ou injoignable.net::ERR_TUNNEL_CONNECTION_FAILED— le tunnel CONNECT vers une cible HTTPS a été refusé ; confirmez que le protocole configuré correspond à l’endpoint.net::ERR_PROXY_AUTH_REQUESTED— les identifiants ont été rejetés ; revérifiez le nom d’utilisateur et le mot de passe, et passez-les comme champs plutôt qu’à l’intérieur de l’URL du serveur.TimeoutError— l’endpoint est lent ou la cible est lourde ; augmentez le délai d’attente et réduisez la concurrence.
Lorsque l’un de ces cas apparaît, retirez cet endpoint de la file d’attente pour le reste de l’exécution et continuez avec les autres. Réessayer sans cesse le même endpoint défectueux ne fait que perdre du temps.
Étape 7 — L’exécuter sur une CI Linux sans épuiser les ressources
- Installez les dépendances du navigateur dans le pipeline avec
npx playwright install --with-deps chromium. - Limitez la concurrence à deux ou trois contextes par vCPU sur un runner CI ; chaque contexte porte une page rendue.
- Lancez le navigateur une fois par processus et fermez les contextes dans des blocs
finally. - Préférez les proxys datacenter pour les cibles à fort volume et faible défense sur les tâches planifiées, et réservez les endpoints résidentiels ou mobiles aux cibles plus difficiles.
- Surveillez l’économie unitaire : la facturation à l’usage facilite le démarrage de runs CI en rafale, tandis qu’un forfait mensuel fixe est généralement moins cher pour une tâche nocturne régulière.
Dépannage
| Symptôme | Cause probable | Correctif |
|---|---|---|
| Les pages se chargent, mais depuis votre propre IP | Proxy ignoré (faute de frappe dans server, ou option non transmise au contexte) |
Journalisez l’IP de sortie avec l’assistant de l’étape 4 et levez une erreur lorsqu’elle correspond à l’IP directe |
ERR_PROXY_CONNECTION_FAILED |
Hôte ou port incorrect, ou sortie CI bloquée | Testez le même endpoint avec curl, puis vérifiez la politique réseau |
ERR_PROXY_AUTH_REQUESTED |
Identifiants incorrects ou intégrés dans l’URL | Passez les champs username et password, jamais user:pass@host |
ERR_TUNNEL_CONNECTION_FAILED sur HTTPS |
Incompatibilité de protocole entre l’endpoint et le schéma configuré | Essayez l’endpoint HTTP, puis l’endpoint SOCKS5, et gardez celui qui fonctionne |
| Chaque contexte rapporte la même IP de sortie | Proxy au niveau navigateur utilisé au lieu du niveau contexte, ou le moteur ne prend pas en charge les proxys par contexte | Utilisez chromium.launch() et définissez proxy sur chaque newContext() |
| L’exécution tue le runner CI | Trop de contextes simultanés | Réduisez CONCURRENCY à 2 et remesurez la mémoire |
Résumé
- Dans Playwright, le proxy appartient à l’appel de lancement ou au contexte de navigateur, pas aux requêtes individuelles.
- Vérifiez l’IP de sortie avant le scraping ; un proxy mal configuré peut silencieusement vous laisser sur votre propre IP.
- Faites la rotation en donnant à chaque contexte son propre endpoint ProxyScrape HTTP ou SOCKS5 et en limitant le nombre de contextes exécutés simultanément.
- Classez les échecs
ERR_PROXY*etERR_TUNNEL*comme des problèmes d’endpoint, supprimez l’endpoint et poursuivez l’exécution.