Параллельное выполнение задач

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

Разделение контекстов и страниц

В Puppeteer существует несколько уровней изоляции:

  • Браузер (Browser) — глобальная сессия, один процесс Chromium. Запускать несколько браузеров параллельно ресурсоёмко.
  • Контекст браузера (BrowserContext) — позволяет создавать изолированные «песочницы» внутри одного браузера. Использование нескольких контекстов эффективнее, чем запуск отдельных браузеров.
  • Страницы (Page) — отдельные вкладки в контексте. Каждая страница может выполнять задачи независимо, но ресурсы делятся с другими страницами того же контекста.

Ключевой принцип: для параллельной работы следует создавать отдельные страницы внутри контекста, а для полной изоляции использовать отдельные контексты.

Асинхронное управление задачами

Puppeteer полностью асинхронный, что позволяет управлять параллельными задачами через промисы и async/await. Для запуска множества операций одновременно часто используют Promise.all():

const puppeteer = require(&

(async () => {
    const browser = await puppeteer.launch();
    const urls = ['https://site1.com', 'https://site2.com', 'https://site3.com'];

    const pages = await Promise.all(urls.map(async url => {
        const page = await browser.newPage();
        await page.goto(url, { waitUntil: 'networkidle2' });
        const title = await page.title();
        await page.close();
        return title;
    }));

    console.log(pages);
    await browser.close();
})();

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

Ограничение числа параллельных задач

Запуск слишком большого числа параллельных страниц может привести к падению производительности или ошибкам из-за ограничений памяти. Для контроля часто используют пулы задач. Например, с использованием p-limit:

const pLimit = require('p-limit');
const limit = pLimit(5); // максимум 5 страниц одновременно

const tasks = urls.map(url => limit(async () => {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2' });
    const content = await page.content();
    await page.close();
    return content;
}));

const results = await Promise.all(tasks);

p-limit гарантирует, что одновременно работает не более пяти задач, что снижает нагрузку на память и CPU.

Обработка ошибок в параллельных потоках

При параллельном выполнении важно корректно обрабатывать ошибки, чтобы одна неудачная страница не блокировала остальные задачи. Наиболее надежный подход — оборачивать каждую задачу в конструкцию try/catch:

const tasks = urls.map(url => limit(async () => {
    try {
        const page = await browser.newPage();
        await page.goto(url, { waitUntil: 'domcontentloaded' });
        const title = await page.title();
        await page.close();
        return { url, title };
    } catch (error) {
        return { url, error: error.message };
    }
}));

Такой метод позволяет собрать результаты всех задач и отдельно обработать ошибки без прерывания всей операции.

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

Для высокой изоляции и независимости тестов можно запускать несколько браузеров параллельно. Это повышает стабильность, особенно если одна из страниц или расширений Chromium может вызвать сбой:

const browsers = await Promise.all(
    [1, 2, 3].map(() => puppeteer.launch())
);

const results = await Promise.all(
    browsers.map(async browser => {
        const page = await browser.newPage();
        await page.goto('https://example.com');
        const title = await page.title();
        await browser.close();
        return title;
    })
);

Такой подход требует больше ресурсов, но обеспечивает полную изоляцию каждой задачи.

Оптимизация памяти и производительности

  • Закрытие страниц: освобождает память после завершения задачи.
  • Использование BrowserContext вместо новых браузеров: снижает нагрузку на систему.
  • Контроль числа параллельных задач через лимиты: предотвращает перегрузку CPU.
  • Минимизация загружаемых ресурсов: отключение изображений, стилей и скриптов, если они не нужны:
await page.setRequestInterception(true);
page.on('request', request => {
    if (['image', 'stylesheet', 'font'].includes(request.resourceType())) {
        request.abort();
    } else {
        request.continue();
    }
});

Логирование и мониторинг

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

console.log(`Start: ${url}`);
const start = Date.now();
await page.goto(url);
console.log(`Done: ${url}, time: ${Date.now() - start}ms`);

Это помогает быстро выявлять узкие места и потенциальные ошибки при масштабировании задач.

Комбинирование последовательного и параллельного подходов

Некоторые задачи требуют последовательного выполнения (например, логин, получение токена, затем обработка данных). В таких случаях комбинируется последовательное выполнение для критических шагов и параллельное — для независимых задач:

const tokenPage = await browser.newPage();
await tokenPage.goto('https://example.com/login');
const token = await tokenPage.$eval('#token', el => el.value);
await tokenPage.close();

const tasks = urls.map(url => limit(async () => {
    const page = await browser.newPage();
    await page.goto(`${url}?token=${token}`);
    const data = await page.content();
    await page.close();
    return data;
}));

Такой гибридный подход обеспечивает правильный порядок операций без потери скорости при массовой обработке.

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