Параллельное выполнение задач в Puppeteer позволяет значительно ускорить процесс тестирования и сбора данных с веб-страниц, особенно при работе с большим количеством страниц или сложной логикой взаимодействия. Puppeteer построен на основе Chromium, и каждая новая сессия или контекст браузера может потреблять значительные ресурсы, поэтому важно грамотно организовать параллельность, чтобы сохранить производительность и стабильность.
В Puppeteer существует несколько уровней изоляции:
Ключевой принцип: для параллельной работы следует создавать отдельные страницы внутри контекста, а для полной изоляции использовать отдельные контексты.
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 вместо новых
браузеров: снижает нагрузку на систему.
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 является ключевым инструментом для масштабируемого тестирования и автоматизации. Правильное использование контекстов, страниц, пулов задач и ограничений позволяет добиться высокой скорости и стабильности работы.