TL;DR: Разработка Screenshot API для рендеринга JS-heavy сайтов — это вызов. Использование пула браузеров и кэширования позволяет улучшить производительность и стабильность.
Введение
В современном вебе множество сайтов используют тяжелые JavaScript-фреймворки, такие как React, Angular или Vue. Рендеринг таких страниц для создания скриншотов или экспорта в PDF может быть сложной задачей. Puppeteer и Playwright — популярные инструменты для автоматизации браузеров, но их использование в production может быть сопряжено с рядом проблем: от случайных ошибок загрузки шрифтов до нестабильного рендеринга контента.
Основные проблемы и их решения
1. Шрифты и иконки не загружаются
Одна из самых частых проблем — это случайные ошибки загрузки шрифтов и иконок. Это может быть связано с сетью или временными проблемами на стороне сервера.
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
Решение: Убедитесь, что вы используете waitUntil: 'networkidle2' для ожидания завершения всех сетевых запросов.
2. Холодные старты и производительность
Холодные старты браузера могут значительно увеличивать время отклика. Это особенно заметно при обработке большого количества запросов.
Решение: Используйте пул браузеров для повторного использования уже запущенных экземпляров.
const browserPool = [];
const poolSize = 5;
async function getBrowser() {
if (browserPool.length < poolSize) {
const browser = await puppeteer.launch();
browserPool.push(browser);
}
return browserPool.shift();
}
async function releaseBrowser(browser) {
browserPool.push(browser);
}
3. Память и тяжелые страницы
Некоторые страницы могут потреблять большое количество памяти, что приводит к утечкам и сбоям.
Решение: Ограничьте количество одновременно открытых страниц и регулярно перезапускайте браузер.
let pageCount = 0;
const maxPagesPerBrowser = 10;
async function createPage() {
if (pageCount >= maxPagesPerBrowser) {
await browser.close();
browser = await puppeteer.launch();
pageCount = 0;
}
const page = await browser.newPage();
pageCount++;
return page;
}
4. Рендеринг JS-контента
Иногда страница считается загруженной, но JS-контент еще не отрендерен.
Решение: Используйте кастомные ожидания для проверки наличия ключевых элементов на странице.
await page.waitForSelector('#main-content', { timeout: 5000 });
Практическое применение
Кэширование повторяющихся рендеров
Кэширование результатов рендеринга для одинаковых URL может значительно снизить нагрузку на сервер.
const cache = new Map();
async function renderPage(url) {
if (cache.has(url)) {
return cache.get(url);
}
const browser = await getBrowser();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
const screenshot = await page.screenshot();
cache.set(url, screenshot);
releaseBrowser(browser);
return screenshot;
}
Заключение
Создание стабильного Screenshot API для рендеринга JS-heavy сайтов требует тщательной настройки и оптимизации. Использование пула браузеров, кэширования и кастомных ожиданий позволяет значительно улучшить производительность и стабильность системы. Несмотря на то, что Puppeteer и Playwright предоставляют мощные инструменты для автоматизации, их использование в production требует дополнительных усилий для обработки всех возможных сценариев.
Источник: https://www.reddit.com/r/javascript/comments/1thucyc/askjs_screenshot_api_that_renders_heavy_js/