Ротация прокси с Playwright: полное руководство для разработчиков

Узнайте, как настроить ротацию прокси в Playwright — прокси по контекстам, настройки скрытности, геотаргетинг, параллельный скрапинг и продакшн-паттерны с примерами кода.

Ротация прокси с Playwright: полное руководство для разработчиков
В этой статье

Почему Playwright для скрапинга через прокси

Playwright — это современный фреймворк автоматизации браузера от Microsoft, поддерживающий Chromium, Firefox и WebKit. В отличие от HTTP-библиотек, Playwright рендерит полные страницы — выполняет JavaScript, обрабатывает динамический контент и проходит проверки антибот-систем, которые отклоняют обычные HTTP-запросы.

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

Это руководство предполагает, что у вас есть аккаунт ProxyHat. Если вы новичок в прокси, начните с Что такое прокси-сервер? и затем ознакомьтесь с нашим обзором лучших прокси для веб-скрапинга.

Установка и настройка

Node.js (основной)

# Install Playwright
npm init -y
npm install playwright
# Download browser binaries
npx playwright install chromium

Python

# Install Playwright for Python
pip install playwright
python -m playwright install chromium

Настройка прокси на уровне браузера

Самый простой подход — установить прокси при запуске браузера. Каждый контекст и страница автоматически наследуют этот прокси.

Node.js — прокси для всего браузера

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch({
    proxy: {
      server: 'http://gate.proxyhat.com:8080',
      username: 'USERNAME',
      password: 'PASSWORD',
    },
  });
  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');
  console.log(await page.textContent('body'));
  await browser.close();
})();

Python — прокси для всего браузера

from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    browser = p.chromium.launch(
        proxy={
            "server": "http://gate.proxyhat.com:8080",
            "username": "USERNAME",
            "password": "PASSWORD",
        }
    )
    page = browser.new_page()
    page.goto("https://httpbin.org/ip")
    print(page.text_content("body"))
    browser.close()

Ротация прокси на уровне контекста

Настоящая мощь Playwright заключается в контекстах браузера. Каждый контекст — это изолированная сессия с отдельными куками, хранилищем и кешем — и может иметь собственный прокси. Это рекомендуемый паттерн для ротации прокси, так как он избавляет от необходимости запускать новый браузер для каждого IP.

Node.js — ротация по контекстам

const { chromium } = require('playwright');
const crypto = require('crypto');
async function createProxiedContext(browser) {
  const sessionId = crypto.randomBytes(4).toString('hex');
  const context = await browser.newContext({
    proxy: {
      server: 'http://gate.proxyhat.com:8080',
      username: `USERNAME-session-${sessionId}`,
      password: 'PASSWORD',
    },
  });
  return context;
}
(async () => {
  // Launch browser WITHOUT a proxy — set it per context
  const browser = await chromium.launch();
  const urls = [
    'https://example.com/page/1',
    'https://example.com/page/2',
    'https://example.com/page/3',
  ];
  for (const url of urls) {
    const context = await createProxiedContext(browser);
    const page = await context.newPage();
    try {
      await page.goto(url, { timeout: 30000 });
      const content = await page.content();
      console.log(`Fetched ${url} — ${content.length} chars`);
    } catch (err) {
      console.error(`Failed ${url}: ${err.message}`);
    } finally {
      await context.close(); // Releases the session
    }
  }
  await browser.close();
})();

Python — ротация по контекстам

import uuid
from playwright.sync_api import sync_playwright
def create_proxied_context(browser):
    session_id = uuid.uuid4().hex[:8]
    context = browser.new_context(
        proxy={
            "server": "http://gate.proxyhat.com:8080",
            "username": f"USERNAME-session-{session_id}",
            "password": "PASSWORD",
        }
    )
    return context
with sync_playwright() as p:
    browser = p.chromium.launch()
    urls = [
        "https://example.com/page/1",
        "https://example.com/page/2",
        "https://example.com/page/3",
    ]
    for url in urls:
        context = create_proxied_context(browser)
        page = context.new_page()
        try:
            page.goto(url, timeout=30000)
            print(f"Fetched {url} — {len(page.content())} chars")
        except Exception as e:
            print(f"Failed {url}: {e}")
        finally:
            context.close()
    browser.close()

Геотаргетированные контексты

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

const { chromium } = require('playwright');
const GEO_PROFILES = {
  us: { locale: 'en-US', timezone: 'America/New_York',  country: 'us' },
  de: { locale: 'de-DE', timezone: 'Europe/Berlin',     country: 'de' },
  jp: { locale: 'ja-JP', timezone: 'Asia/Tokyo',        country: 'jp' },
};
async function createGeoContext(browser, region) {
  const profile = GEO_PROFILES[region];
  return browser.newContext({
    proxy: {
      server: 'http://gate.proxyhat.com:8080',
      username: `USERNAME-country-${profile.country}`,
      password: 'PASSWORD',
    },
    locale: profile.locale,
    timezoneId: profile.timezone,
    geolocation: null,
  });
}
(async () => {
  const browser = await chromium.launch();
  for (const region of ['us', 'de', 'jp']) {
    const context = await createGeoContext(browser, region);
    const page = await context.newPage();
    await page.goto('https://example.com/pricing');
    console.log(`${region.toUpperCase()}: ${await page.title()}`);
    await context.close();
  }
  await browser.close();
})();

Параллельный скрапинг с пулом воркеров

Контексты Playwright легковесные. Можно запускать несколько контекстов параллельно, каждый с отдельной прокси-сессией, чтобы значительно увеличить пропускную способность.

const { chromium } = require('playwright');
const crypto = require('crypto');
const MAX_CONCURRENCY = 5;
async function scrapeUrl(browser, url) {
  const sessionId = crypto.randomBytes(4).toString('hex');
  const context = await browser.newContext({
    proxy: {
      server: 'http://gate.proxyhat.com:8080',
      username: `USERNAME-session-${sessionId}`,
      password: 'PASSWORD',
    },
  });
  const page = await context.newPage();
  try {
    await page.goto(url, { timeout: 30000, waitUntil: 'domcontentloaded' });
    const title = await page.title();
    return { url, title, success: true };
  } catch (err) {
    return { url, error: err.message, success: false };
  } finally {
    await context.close();
  }
}
async function scrapeAll(urls) {
  const browser = await chromium.launch();
  const results = [];
  // Process in batches of MAX_CONCURRENCY
  for (let i = 0; i < urls.length; i += MAX_CONCURRENCY) {
    const batch = urls.slice(i, i + MAX_CONCURRENCY);
    const batchResults = await Promise.all(
      batch.map(url => scrapeUrl(browser, url))
    );
    results.push(...batchResults);
    console.log(`Completed batch ${Math.floor(i / MAX_CONCURRENCY) + 1}`);
  }
  await browser.close();
  return results;
}
// Usage
const urls = Array.from({ length: 20 }, (_, i) =>
  `https://example.com/product/${i + 1}`
);
scrapeAll(urls).then(results => {
  const success = results.filter(r => r.success).length;
  console.log(`Success: ${success}/${results.length}`);
});

Более продвинутые паттерны конкурентности описаны в нашем руководстве по масштабированию прокси-запросов с контролем конкурентности.

Настройка скрытности

Браузеры Playwright по умолчанию имеют обнаруживаемые маркеры автоматизации. Эти настройки уменьшают ваш отпечаток и помогают обойти антибот-системы.

Основные настройки скрытности

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch({
    args: [
      '--disable-blink-features=AutomationControlled',
      '--disable-features=IsolateOrigins,site-per-process',
    ],
  });
  const context = await browser.newContext({
    proxy: {
      server: 'http://gate.proxyhat.com:8080',
      username: 'USERNAME',
      password: 'PASSWORD',
    },
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
    viewport: { width: 1920, height: 1080 },
    locale: 'en-US',
    timezoneId: 'America/New_York',
    deviceScaleFactor: 1,
    hasTouch: false,
    isMobile: false,
    javaScriptEnabled: true,
  });
  // Remove automation markers
  await context.addInitScript(() => {
    // Override navigator.webdriver
    Object.defineProperty(navigator, 'webdriver', {
      get: () => undefined,
    });
    // Override navigator.plugins to look real
    Object.defineProperty(navigator, 'plugins', {
      get: () => [1, 2, 3, 4, 5],
    });
    // Override navigator.languages
    Object.defineProperty(navigator, 'languages', {
      get: () => ['en-US', 'en'],
    });
    // Override chrome.runtime to avoid detection
    window.chrome = { runtime: {} };
  });
  const page = await context.newPage();
  await page.goto('https://bot.sannysoft.com/');
  await page.screenshot({ path: 'stealth-test.png' });
  await browser.close();
})();

Настройка скрытности на Python

from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    browser = p.chromium.launch(
        args=[
            "--disable-blink-features=AutomationControlled",
        ]
    )
    context = browser.new_context(
        proxy={
            "server": "http://gate.proxyhat.com:8080",
            "username": "USERNAME",
            "password": "PASSWORD",
        },
        user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                   "AppleWebKit/537.36 (KHTML, like Gecko) "
                   "Chrome/131.0.0.0 Safari/537.36",
        viewport={"width": 1920, "height": 1080},
        locale="en-US",
        timezone_id="America/New_York",
    )
    context.add_init_script("""
        Object.defineProperty(navigator, 'webdriver', {
            get: () => undefined,
        });
        window.chrome = { runtime: {} };
    """)
    page = context.new_page()
    page.goto("https://httpbin.org/headers")
    print(page.text_content("body"))
    browser.close()

Логика повторных попыток с ротацией прокси

Комбинирование логики повторных попыток с автоматической ротацией прокси гарантирует, что неудачные запросы будут повторены с новым IP и контекстом.

const { chromium } = require('playwright');
const crypto = require('crypto');
async function fetchWithRetry(browser, url, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const sessionId = crypto.randomBytes(4).toString('hex');
    const context = await browser.newContext({
      proxy: {
        server: 'http://gate.proxyhat.com:8080',
        username: `USERNAME-session-${sessionId}`,
        password: 'PASSWORD',
      },
    });
    const page = await context.newPage();
    try {
      const response = await page.goto(url, {
        timeout: 30000,
        waitUntil: 'domcontentloaded',
      });
      if (response && response.status() >= 400) {
        console.log(`Attempt ${attempt}: HTTP ${response.status()}, retrying...`);
        await context.close();
        continue;
      }
      const html = await page.content();
      await context.close();
      return html;
    } catch (err) {
      console.log(`Attempt ${attempt} failed: ${err.message}`);
      await context.close();
      if (attempt === maxRetries) {
        throw new Error(`All ${maxRetries} attempts failed for ${url}`);
      }
      // Exponential backoff
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
    }
  }
}
(async () => {
  const browser = await chromium.launch();
  try {
    const html = await fetchWithRetry(browser, 'https://example.com/data');
    console.log(`Fetched ${html.length} chars`);
  } catch (err) {
    console.error(err.message);
  }
  await browser.close();
})();

SOCKS5 прокси с Playwright

ProxyHat также поддерживает SOCKS5 на порту 1080. Это полезно, когда нужен протокол-независимый проксинг или требуется избежать накладных расходов HTTP CONNECT.

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch({
    proxy: {
      server: 'socks5://gate.proxyhat.com:1080',
      username: 'USERNAME',
      password: 'PASSWORD',
    },
  });
  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');
  console.log(await page.textContent('body'));
  await browser.close();
})();

Продакшн-паттерн скрапинга

Вот полный продакшн-паттерн скрапера, который объединяет все описанные выше шаблоны — ротацию прокси по контекстам, настройки скрытности, логику повторов, конкурентность и структурированное извлечение данных.

const { chromium } = require('playwright');
const crypto = require('crypto');
const fs = require('fs');
class PlaywrightScraper {
  constructor({ concurrency = 3, maxRetries = 3 }) {
    this.concurrency = concurrency;
    this.maxRetries = maxRetries;
    this.browser = null;
    this.results = [];
    this.stats = { success: 0, failed: 0 };
  }
  async init() {
    this.browser = await chromium.launch({
      args: ['--disable-blink-features=AutomationControlled'],
    });
  }
  _createContext() {
    const sessionId = crypto.randomBytes(4).toString('hex');
    return this.browser.newContext({
      proxy: {
        server: 'http://gate.proxyhat.com:8080',
        username: `USERNAME-session-${sessionId}`,
        password: 'PASSWORD',
      },
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
      viewport: { width: 1920, height: 1080 },
      locale: 'en-US',
    });
  }
  async scrapePage(url) {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      const context = await this._createContext();
      const page = await context.newPage();
      try {
        const response = await page.goto(url, {
          timeout: 30000,
          waitUntil: 'networkidle',
        });
        if (!response || response.status() >= 400) {
          await context.close();
          continue;
        }
        // Extract data — customize this for your target
        const data = await page.evaluate(() => ({
          title: document.title,
          text: document.body.innerText.substring(0, 500),
        }));
        await context.close();
        this.stats.success++;
        return { url, ...data, success: true };
      } catch (err) {
        await context.close();
        if (attempt === this.maxRetries) {
          this.stats.failed++;
          return { url, error: err.message, success: false };
        }
        await new Promise(r => setTimeout(r, 1000 * attempt));
      }
    }
  }
  async scrapeAll(urls) {
    await this.init();
    for (let i = 0; i < urls.length; i += this.concurrency) {
      const batch = urls.slice(i, i + this.concurrency);
      const batchResults = await Promise.all(
        batch.map(url => this.scrapePage(url))
      );
      this.results.push(...batchResults);
    }
    await this.browser.close();
    console.log(`Done: ${this.stats.success} OK, ${this.stats.failed} failed`);
    return this.results;
  }
}
// Usage
const scraper = new PlaywrightScraper({ concurrency: 5, maxRetries: 3 });
const urls = Array.from({ length: 50 }, (_, i) =>
  `https://example.com/item/${i + 1}`
);
scraper.scrapeAll(urls).then(results => {
  fs.writeFileSync('results.json', JSON.stringify(results, null, 2));
});

Для создания переиспользуемого абстрактного слоя прокси читайте Создание промежуточного слоя прокси. Изучите Node SDK и Python SDK для упрощённого управления прокси и ознакомьтесь с тарифами ProxyHat для начала работы.

Часто задаваемые вопросы

Часто задаваемые вопросы

Может ли Playwright использовать разные прокси для каждой страницы?

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

Playwright лучше Puppeteer для скрапинга через прокси?

Playwright предлагает несколько преимуществ: встроенная поддержка аутентификации прокси (без обходных путей через page.authenticate), прокси на уровне контекста без перезапуска браузера, поддержка нескольких браузеров (Chromium, Firefox, WebKit). Puppeteer работает только с Chromium и требует дополнительных плагинов для скрытности.

Сколько параллельных контекстов Playwright можно запустить?

Каждый контекст использует примерно 20-50 МБ памяти. На машине с 8 ГБ RAM можно комфортно запустить 20-50 параллельных контекстов. Практический лимит зависит от времени ответа целевого сайта и доступной пропускной способности.

Работает ли Playwright с SOCKS5 прокси?

Да. Установите сервер прокси как socks5://gate.proxyhat.com:1080 в параметрах запуска браузера или контекста. Playwright нативно обрабатывает аутентификацию и подключение SOCKS5.

Как избежать обнаружения при использовании Playwright с прокси?

Комбинируйте резидентные прокси с настройками скрытности: удалите navigator.webdriver, установите реалистичный viewport и user-agent, совместите локаль и часовой пояс с локацией прокси и добавьте случайные задержки между действиями. Ротация по контекстам обеспечивает свежий отпечаток для каждой сессии.

Готовы начать?

Резидентные, ISP и мобильные прокси в 148+ странах. Создайте бесплатный аккаунт.

Создать бесплатный аккаунт
← Вернуться в Блог