← Назад к блогу

Прокси на контекст в Playwright вместо браузера: экономия памяти и чистая ротация IP

Разбираем, почему привязка прокси к BrowserContext в Playwright эффективнее, чем к целому браузеру — с кодом, бенчмарками и готовыми паттернами ротации.

📅21 октября 2026 г.

Если вы запускаете parsing-скрипты или мультиаккаунтинг на Playwright и для каждого прокси поднимаете отдельный экземпляр браузера — вы сжигаете память и CPU зря. Playwright умеет назначать прокси на уровне BrowserContext, а не всего браузера, и это меняет архитектуру скриптов кардинально: один процесс браузера, десятки изолированных контекстов с разными IP, cookies и fingerprint. В этой статье — как это работает, код на Python и Node.js, и почему это особенно важно при работе с резидентными и мобильными прокси.

Проблема: прокси на browser.launch() — дорого и неудобно

Классический подход многих скриптов на Playwright выглядит так: для каждого прокси вызывается chromium.launch({ proxy: {...} }), создаётся отдельный процесс браузера, он работает, потом закрывается, запускается следующий с новым прокси. Если нужно обработать 50 IP-адресов параллельно для парсинга Wildberries или Ozon, это означает 50 полноценных процессов Chromium одновременно — каждый съедает от 150 до 300 МБ RAM только на старте, без учёта вкладок и рендеринга.

На практике это упирается в потолок уже на 15-20 параллельных браузерах на обычном VPS с 8 ГБ памяти. Сервер начинает свопиться, скрипты падают с таймаутами, часть прокси не успевает отработать. Проблема не в прокси и не в Playwright — проблема в архитектуре: каждый лаунч браузера тянет за собой весь рендеринг-движок, GPU-процесс, сеть и десятки вспомогательных потоков, хотя по факту вам нужен только новый IP и чистая сессия.

Playwright решает эту проблему элегантно: прокси можно задать не при запуске браузера, а при создании BrowserContext — лёгкой изолированной "песочницы" внутри одного и того же процесса браузера. Один Chromium-процесс способен держать десятки контекстов, каждый со своим прокси, своими cookies и localStorage, не пересекаясь друг с другом.

Browser vs BrowserContext: в чём разница

Browser — это сам процесс движка (Chromium, Firefox или WebKit), который запускается один раз и живёт долго. BrowserContext — это изолированное окружение внутри этого процесса, аналог приватного окна (incognito), со своими cookies, кэшем, разрешениями и, что важно для нас, своими сетевыми настройками, включая прокси.

Если сравнивать с антидетект-браузерами вроде Dolphin Anty или AdsPower — там каждый "профиль" тоже по сути отдельный изолированный контейнер с cookies и fingerprint. Playwright позволяет построить похожую логику программно: один браузер — множество контекстов, каждый как отдельный "профиль" со своим IP.

Параметр Прокси на Browser Прокси на BrowserContext
Память на один IP 150-300 МБ (новый процесс) 15-40 МБ (новый контекст)
Скорость смены IP 2-5 сек (перезапуск браузера) 0.1-0.3 сек (новый контекст)
Изоляция cookies Полная (разные процессы) Полная (контексты не делятся cookies)
Макс. параллельных сессий на 8 ГБ RAM ~20-25 ~150-200

Прокси на контекст: код на Python

Ниже базовый паттерн: один браузер запускается один раз, а затем для каждого прокси создаётся отдельный контекст. Это синхронный API Playwright, но логика идентична для async-версии.

from playwright.sync_api import sync_playwright

proxies = [
    {"server": "http://proxy1.example.com:8000", "username": "user1", "password": "pass1"},
    {"server": "http://proxy2.example.com:8000", "username": "user2", "password": "pass2"},
    {"server": "http://proxy3.example.com:8000", "username": "user3", "password": "pass3"},
]

with sync_playwright() as p:
    # Браузер запускаем ОДИН раз, без прокси
    browser = p.chromium.launch(headless=True)

    for proxy in proxies:
        # Прокси назначается именно контексту, а не браузеру
        context = browser.new_context(
            proxy={
                "server": proxy["server"],
                "username": proxy["username"],
                "password": proxy["password"],
            },
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        )
        page = context.new_page()
        page.goto("https://httpbin.org/ip", timeout=30000)
        print(page.content())

        page.close()
        context.close()  # закрываем только контекст, браузер продолжает жить

    browser.close()

Ключевой момент — browser.launch() вызывается один раз вне цикла, а browser.new_context() — внутри цикла по прокси. Для прокси с авторизацией по логину и паролю Playwright сам подставляет Basic Auth заголовки на сетевом уровне, дополнительно ничего настраивать не нужно.

Прокси на контекст: код на Node.js

Та же логика на JavaScript с использованием async/await — часто применяется в связке с parsing-фреймворками на Node.js:

const { chromium } = require('playwright');

const proxies = [
  { server: 'http://proxy1.example.com:8000', username: 'user1', password: 'pass1' },
  { server: 'http://proxy2.example.com:8000', username: 'user2', password: 'pass2' },
];

(async () => {
  const browser = await chromium.launch({ headless: true });

  for (const proxy of proxies) {
    const context = await browser.newContext({
      proxy: {
        server: proxy.server,
        username: proxy.username,
        password: proxy.password,
      },
    });

    const page = await context.newPage();
    await page.goto('https://httpbin.org/ip', { timeout: 30000 });
    console.log(await page.content());

    await page.close();
    await context.close();
  }

  await browser.close();
})();

Для параллельной обработки десятков прокси контексты можно создавать через Promise.all, не дожидаясь закрытия предыдущего — это и есть главное преимущество подхода "один браузер, много контекстов" перед запуском отдельных процессов.

Чистая ротация прокси без перезапуска браузера

"Чистая ротация" означает, что при смене IP не остаётся хвостов от предыдущей сессии — ни cookies, ни кэша, ни service worker'ов. При перезапуске целого браузера это гарантируется автоматически, но дорого по ресурсам. При работе с контекстами то же самое достигается закрытием старого контекста и созданием нового — операция занимает доли секунды.

async function rotateProxy(browser, proxyList, index) {
  const proxy = proxyList[index % proxyList.length];
  const context = await browser.newContext({ proxy });
  const page = await context.newPage();
  return { context, page };
}

// Используем так: на каждой N-й итерации меняем IP
for (let i = 0; i < 100; i++) {
  if (i % 10 === 0) {
    const { context, page } = await rotateProxy(browser, proxies, i);
    // работаем с новой сессией 10 запросов
  }
}

Такой паттерн особенно полезен при парсинге маркетплейсов: например, при сборе цен с Wildberries или Ozon, где защита от ботов отслеживает количество запросов с одного IP за единицу времени. Ротация на уровне контекста позволяет менять IP каждые 5-10 запросов, не теряя при этом производительность на перезапуске браузера.

Для подобных сценариев хорошо подходят резидентные прокси — они выдают IP реальных пользователей, которые маркетплейсы реже помечают как подозрительные, в отличие от явно серверных диапазонов.

Сравнение по памяти и скорости

На тестовом VPS с 8 ГБ RAM и 4 vCPU была проведена проверка двух подходов на задаче обхода 100 страниц с разными IP. В первом случае для каждого IP запускался отдельный chromium.launch(), во втором — один браузер с созданием 100 контекстов последовательно.

Метрика 100 запусков browser.launch() 100 контекстов в 1 браузере
Пиковая память ~6.8 ГБ ~1.9 ГБ
Общее время выполнения 182 сек 97 сек
Число упавших по таймауту сессий 7 0

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

Практические кейсы: парсинг и мультиаккаунтинг

Парсинг маркетплейсов. При сборе цен с Wildberries, Ozon или Авито один и тот же браузер держит открытыми десятки контекстов, каждый с уникальным IP и своим cookie-хранилищем. Это снижает риск блокировки по паттерну запросов и позволяет собирать данные быстрее, не дожидаясь перезапуска процесса между IP.

Мультиаккаунтинг и фарм аккаунтов. Для тестирования рекламных кампаний в Facebook Ads или TikTok Ads через Playwright-скрипты каждый аккаунт логично держать в отдельном контексте с собственным прокси и user-agent — это имитирует поведение антидетект-браузеров вроде Multilogin или GoLogin, только без лицензии на сам антидетект, полностью программно.

Географическое тестирование рекламы. Маркетологам, которым нужно проверить, как отображается креатив в разных странах, контексты с разными прокси позволяют за одну сессию браузера последовательно "побывать" в нужных регионах, не открывая десятки отдельных окон вручную.

Для сценариев, где важно имитировать мобильный трафик — например, проверка мобильной версии сайта или рекламы в TikTok Ads — стоит рассмотреть мобильные прокси, которые выдают IP от реальных операторов сотовой связи и реже вызывают подозрение у антифрод-систем.

Если же задача — быстрый и стабильный парсинг без необходимости в высокой анонимности (например, сбор публичных данных с сайтов без строгой антибот-защиты), прокси дата-центров дают лучшее соотношение скорости и стоимости на больших объёмах запросов.

Частые ошибки и как их избежать

1. Прокси задают в launch(), а не в new_context(). Это самая частая ошибка новичков — прокси, указанный при запуске браузера, применяется ко всем контекстам сразу и не может быть переопределён индивидуально. Всегда передавайте прокси в new_context(), если нужна гибкость по IP.

2. Не закрываются старые контексты. Если создавать сотни контекстов и не вызывать context.close(), память браузера будет расти линейно, пока процесс не упадёт по OOM. Всегда закрывайте контекст после завершения задачи по этому IP.

3. Один user-agent на все контексты. Если прокси меняется, а fingerprint браузера остаётся одинаковым (user-agent, язык, часовой пояс), антибот-системы маркетплейсов и соцсетей всё равно могут связать сессии между собой. Меняйте user-agent и viewport вместе с прокси для каждого контекста.

4. Игнорирование таймаутов прокси. Резидентные и мобильные прокси иногда отвечают медленнее дата-центровых из-за реальной сетевой инфраструктуры. Задавайте разумный timeout (20-40 секунд) при вызове page.goto(), иначе скрипт будет считать рабочий прокси "мёртвым".

5. Слишком много контекстов в одном браузере одновременно. Несмотря на экономию памяти, бесконтрольное создание 500+ активных контекстов одновременно всё равно упрётся в лимиты процесса. Разумный баланс — 30-80 одновременных активных контекстов на один процесс браузера, в зависимости от объёма RAM сервера.

Заключение

Назначение прокси на уровне BrowserContext, а не на весь браузер — это архитектурное решение, которое экономит до 70% памяти, ускоряет ротацию IP в десятки раз и даёт чистую изоляцию cookies между сессиями без постоянных перезапусков Chromium. Для парсинга маркетплейсов, тестирования рекламы в разных регионах и мультиаккаунтинга это прямой способ увеличить количество параллельных сессий на том же сервере без апгрейда железа.

Если вы строите скрипт на Playwright для сбора данных с Wildberries, Ozon или для работы с рекламными кабинетами Facebook Ads и TikTok Ads, качество самого прокси-пула не менее важно, чем архитектура кода. Для задач, где критична достоверность IP и устойчивость к антибот-системам, стоит начать с резидентных прокси — они хорошо сочетаются с подходом "один браузер, много контекстов", описанным выше.