Skip to content
Advanced / 9 min read

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.

Linux HTTP(S) Scraping Web

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, localStorage ou 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 http et/ou socks5. 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 username et password distincts. Chromium ignore les identifiants intégrés dans l’URL du serveur proxy.
  • La valeur server prend un schéma : http://host:port pour les endpoints HTTP de ProxyScrape et socks5://host:port pour 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 :

  1. Exécutez un script ponctuel sans proxy configuré et enregistrez le résultat sous DIRECT_IP.
  2. Exécutez à nouveau le script proxifié.
  3. 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

  1. Installez les dépendances du navigateur dans le pipeline avec npx playwright install --with-deps chromium.
  2. Limitez la concurrence à deux ou trois contextes par vCPU sur un runner CI ; chaque contexte porte une page rendue.
  3. Lancez le navigateur une fois par processus et fermez les contextes dans des blocs finally.
  4. 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.
  5. 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* et ERR_TUNNEL* comme des problèmes d’endpoint, supprimez l’endpoint et poursuivez l’exécution.