Skip to content
Advanced / 4 min read

Веб-скрапинг ProxyScrape с Playwright: маршрутизация Headless Chromium через HTTP- и SOCKS5-прокси

Настройте Playwright так, чтобы headless Chromium выходил через ProxyScrape, проверяйте exit IP перед скрапингом, ротируйте HTTP- или SOCKS5-эндпоинты для каждого контекста браузера и правильно классифицируйте ошибки прокси.

Linux HTTP(S) Веб-скрейпинг

Обзор

Headless-браузеры помещают конфигурацию прокси внутрь браузера, а не внутрь вашей HTTP-библиотеки. Это меняет настройку: вы конфигурируете прокси при запуске браузера или создании контекста браузера, вам приходится обрабатывать аутентификацию прокси, а конфигурация для каждого контекста — это то, что делает возможной ротацию без перезапуска Chromium для каждого запроса.

В этом руководстве используется Playwright с Chromium и эндпоинтами ProxyScrape (HTTP или SOCKS5). Оно охватывает запуск с одним эндпоинтом, проверку exit IP, ротацию для каждого контекста по нескольким эндпоинтам, обработку ошибок прокси и запуск в Linux CI без исчерпания памяти.

Когда headless-браузер — подходящий инструмент

  • Целевой сайт рендерит контент на стороне клиента, поэтому обычный HTTP-запрос возвращает пустую оболочку.
  • Вам нужно взаимодействовать: заполнять формы, кликать, прокручивать или ждать завершения сетевой активности.
  • Вам нужно поведение на уровне браузера, такое как cookies, localStorage или service workers.
  • Вы проверяете, как сайт ведёт себя для конкретного региона.

Оставайтесь с обычным HTTP-клиентом, когда страница рендерится на сервере; это гораздо дешевле и быстрее.

Предварительные требования

  • Node.js 18 или новее и npm.
  • Установленный Playwright с Chromium.
  • Учётные данные ProxyScrape, а также хост и порт для http и/или socks5. Резидентные, датацентровые и мобильные прокси здесь все работают; выберите тип, который соответствует защите целевого сайта.
  • Linux, macOS или Windows. Примечания по CI предполагают раннеры Debian или Ubuntu.

Шаги

Шаг 1 — Установите Playwright и Chromium

mkdir proxyscrape-playwright
cd proxyscrape-playwright
npm init -y
npm install playwright
npx playwright install --with-deps chromium

--with-deps устанавливает библиотеки ОС, необходимые Chromium в Linux-образах и на CI-раннерах.

Шаг 2 — Решите, применяется ли прокси к браузеру или к контексту

Область Где задаётся Для чего использовать Оговорки
Браузер chromium.launch({ proxy }) Один эндпоинт на весь запуск Каждый контекст использует один и тот же exit IP
Контекст browser.newContext({ proxy }) Ротация: один эндпоинт на контекст, параллельно Поддержка зависит от движка; Chromium — безопасный вариант по умолчанию — проверьте поддержку прокси для каждого контекста в вашей версии Playwright, прежде чем полагаться на неё в Firefox или WebKit

Два практических правила:

  • Передавайте учётные данные как отдельные поля username и password. Chromium игнорирует учётные данные, встроенные в URL прокси-сервера.
  • Значение server принимает схему: http://host:port для HTTP-эндпоинтов ProxyScrape и socks5://host:port для SOCKS5.

Шаг 3 — Запустите через один эндпоинт и проверьте exit IP

// launch-proxy.mjs
import { chromium } from 'playwright';

const proxy = {
  server: process.env.PROXYSCRAPE_SERVER, // http://HOST:PORT или 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

Если это выводит IP, прокси-путь работает от начала до конца. Если он зависает или выбрасывает ошибку, перейдите к таблице устранения неполадок, прежде чем писать больше кода.

Шаг 4 — Быстро обнаруживайте, когда трафик не проксируется

Тихая ошибка конфигурации — например, опечатка в строке server — может оставить вас скрапить со своего собственного IP. Зафиксируйте свой прямой IP один раз, а затем проверяйте его.

// 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`);
  }
}

Пошаговая процедура:

  1. Запустите одноразовый скрипт без настроенного прокси и запишите результат как DIRECT_IP.
  2. Снова запустите скрипт с прокси.
  3. Вызовите assertProxied(exitIp, process.env.DIRECT_IP) перед любым реальным скрапингом и завершайте запуск с ошибкой, когда проверка срабатывает.

Шаг 5 — Ротируйте эндпоинты между контекстами браузера

Один браузер, много контекстов, по одному эндпоинту на каждый.

// 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

Почему эта форма работает:

  • Браузер запускается один раз; контексты дешёвые, и каждый несёт свой собственный эндпоинт.
  • Пул воркеров ограничивает количество одновременно существующих контекстов, что является главным рычагом управления памятью в Playwright.
  • Каждый результат записывает эндпоинт, который его обслужил, поэтому сбои можно атрибутировать без догадок.

Шаг 6 — Рассматривайте сбои прокси как сбои эндпоинта, а не сбои страницы

  • net::ERR_PROXY_CONNECTION_FAILED — хост или порт прокси указан неверно или недоступен.
  • net::ERR_TUNNEL_CONNECTION_FAILED — туннель CONNECT к HTTPS-цели был отклонён; убедитесь, что настроенный протокол соответствует эндпоинту.
  • net::ERR_PROXY_AUTH_REQUESTED — учётные данные были отклонены; перепроверьте имя пользователя и пароль и передавайте их как поля, а не внутри URL сервера.
  • TimeoutError — эндпоинт медленный или цель тяжёлая; увеличьте тайм-аут и снизьте параллелизм.

Когда появляется одна из этих ошибок, удалите этот эндпоинт из очереди до конца запуска и продолжайте с остальными. Повторные попытки с тем же сломанным эндпоинтом только тратят время.

Шаг 7 — Запустите это в Linux CI без исчерпания ресурсов

  1. Установите зависимости браузера в пайплайне с помощью npx playwright install --with-deps chromium.
  2. Поддерживайте параллелизм на уровне двух-трёх контекстов на vCPU на CI-раннере; каждый контекст несёт отрендеренную страницу.
  3. Запускайте браузер один раз на процесс и закрывайте контексты в блоках finally.
  4. Предпочитайте датацентровые прокси для высокообъёмных целей с низкой защитой в запланированных задачах, а резидентные или мобильные эндпоинты оставляйте для более сложных целей.
  5. Следите за юнит-экономикой: оплата по мере использования упрощает запуск нерегулярных CI-запусков, а фиксированный месячный план обычно дешевле для регулярной ночной задачи.

Устранение неполадок

Симптом Вероятная причина Решение
Страницы загружаются, но с вашего собственного IP Прокси игнорируется (опечатка в server или опция не была передана в контекст) Логируйте exit IP с помощью помощника из шага 4 и выбрасывайте ошибку, когда он совпадает с прямым IP
ERR_PROXY_CONNECTION_FAILED Неверный хост или порт, или исходящий трафик CI заблокирован Проверьте тот же эндпоинт с помощью curl, затем проверьте сетевую политику
ERR_PROXY_AUTH_REQUESTED Неверные учётные данные или они встроены в URL Передавайте поля username и password, никогда user:pass@host
ERR_TUNNEL_CONNECTION_FAILED на HTTPS Несоответствие протокола между эндпоинтом и настроенной схемой Попробуйте HTTP-эндпоинт, затем SOCKS5-эндпоинт и оставьте тот, который работает
Каждый контекст сообщает один и тот же exit IP Используется прокси уровня браузера вместо уровня контекста, или движок не поддерживает прокси для каждого контекста Используйте chromium.launch() и задавайте proxy в каждом newContext()
Запуск убивает CI-раннер Слишком много одновременных контекстов Снизьте CONCURRENCY до 2 и заново измерьте память

Итог

  • В Playwright прокси принадлежит вызову запуска или контексту браузера, а не отдельным запросам.
  • Проверяйте exit IP перед скрапингом; неправильно настроенный прокси может незаметно оставить вас на собственном IP.
  • Ротируйте, давая каждому контексту собственный HTTP- или SOCKS5-эндпоинт ProxyScrape и ограничивая количество одновременно работающих контекстов.
  • Классифицируйте сбои ERR_PROXY* и ERR_TUNNEL* как проблемы эндпоинта, отбрасывайте эндпоинт и продолжайте запуск.