Puppeteer PDF

Puppeteer PDF превращает HTML-страницы, шаблоны и веб-интерфейсы в PDF через управляемый браузер: можно задавать формат листа, поля, ориентацию, колонтитулы, диапазоны страниц, печать фона и правила CSS, а перед сохранением дождаться шрифтов, изображений и данных, чтобы счёт, отчёт или каталог получился воспроизводимым.

Работа строится вокруг объекта страницы: скрипт открывает адрес или помещает готовую HTML-разметку через setContent(), выполняет нужный JavaScript, ожидает заданное состояние и вызывает page.pdf(). Результат можно записать по указанному пути либо получить как массив байтов для отправки в HTTP-ответ, помещение в объектное хранилище или дальнейшую обработку. Такой порядок особенно удобен там, где документ формируется из тех же компонентов и стилей, что и веб-кабинет.

Главные параметры собраны в объекте PDFOptions: format выбирает стандартный лист, width и height задают собственный размер, landscape меняет ориентацию, margin резервирует поля, displayHeaderFooter включает колонтитулы, а printBackground сохраняет фоновые цвета и изображения. Макет при этом остаётся обычной веб-страницей, поэтому переносы, скрытие служебных кнопок, размеры таблиц и печатные варианты блоков настраиваются CSS-правилами.

Скачать Puppeteer PDF

Оценка 9.7 Рекомендуем
  • Редактирование PDF
  • Русский интерфейс
  • Просто новичкам
Скачать бесплатно на Windows
Лучшая альтернатива
Puppeteer PDF
Оценка 8.5
  • Требует Node.js
  • Нет визуального редактора
  • Нужен Chromium
Скачать Puppeteer PDF
Загрузка начнётся после нажатия

Как устроен рабочий процесс Puppeteer PDF

Минимальный сценарий состоит из четырёх операций: запустить браузер, создать страницу, подготовить содержимое и напечатать PDF. Важно закрывать браузер в блоке finally, иначе авария шаблона или ошибка сети оставит процесс Chromium в памяти. Для одноразовой утилиты можно запускать браузер на каждый документ, но серверу выгоднее держать один экземпляр и создавать отдельную страницу либо изолированный контекст для каждого задания.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' }
  });
} finally {
  await browser.close();
}

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

Код преобразования HTML в PDF с Puppeteer

Выбор между переходом по адресу и setContent

page.goto() нужен, когда документ уже существует как маршрут приложения. Браузер получает обычную страницу, выполняет клиентские скрипты, использует куки, загружает стили и обращается к API так же, как при ручном открытии. Этот вариант сокращает дублирование шаблонов, но связывает генерацию с доступностью фронтенда, сетевыми задержками, политикой авторизации и изменениями в интерфейсе.

page.setContent() помещает в страницу строку HTML. Так удобнее печатать письмо, счёт или сертификат, собранный серверным шаблонизатором. Разметка должна быть полноценной: укажите кодировку, базовые стили и понятные размеры. Относительные пути к картинкам и шрифтам без базового адреса часто становятся причиной пустых мест. Надёжнее передавать абсолютные внутренние адреса, внедрять небольшие ресурсы как данные либо перехватывать запросы и отдавать файлы из контролируемого каталога.

Выбор следует делать по месту, где уже сосредоточена бизнес-логика. Если итоговая таблица вычисляется на сервере и не требует интерактивности, готовый HTML уменьшает количество точек отказа. Если же содержимое строится React, Vue или другим клиентским кодом и зависит от состояния интерфейса, переход по маршруту точнее воспроизводит экран. Не стоит смешивать оба подхода без необходимости: повторная подстановка HTML после навигации сбрасывает документ и может уничтожить подготовленные данные.

Базовый вызов page.pdf в коде Puppeteer

Ожидание данных, изображений и шрифтов

Вызов page.pdf() по умолчанию ждёт загрузки шрифтов, но это не означает, что все остальные компоненты страницы уже готовы. Картинка может лениво появляться только после прокрутки, график — рисоваться после запроса, а таблица — заполняться через несколько этапов. Поэтому генератору нужен явный критерий завершения. Подходящий селектор должен обозначать не просто наличие контейнера, а завершённое состояние: например, элемент с атрибутом data-rendered="true" или отсутствие индикатора загрузки.

await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-rendered="true"]', { visible: true });
await page.evaluate(async () => {
  await document.fonts.ready;
  const images = [...document.images];
  await Promise.all(images.map(image => image.complete
    ? Promise.resolve()
    : new Promise(resolve => {
        image.addEventListener('load', resolve, { once: true });
        image.addEventListener('error', resolve, { once: true });
      })));
});

Обработчик ошибки изображения в таком ожидании не маскирует проблему; он лишь не даёт скрипту зависнуть навсегда. После ожидания следует отдельно проверить, какие ресурсы не загрузились, и решить, допустим ли документ без них. Для финансового отчёта отсутствие логотипа может быть терпимо, а отсутствие штрихкода — нет. Практичный генератор собирает список неуспешных запросов, пишет его в журнал и отклоняет задание при пропаже обязательного ресурса.

Ленивая загрузка требует дополнительного внимания. Атрибут loading="lazy", наблюдатель пересечения и виртуализированный список могут не создавать элементы за пределами видимой области. Перед печатью можно отключить ленивый режим в печатном маршруте, последовательно прокрутить страницу либо предоставить серверный режим, который сразу выводит все строки. Последний вариант предсказуемее: автоматическая прокрутка плохо работает с бесконечной лентой и может привести к неконтролируемому объёму PDF.

Формат листа, размеры и ориентация

Параметр format выбирает предопределённый бумажный формат. Для большинства русскоязычных документов подходит A4, тогда как значение по умолчанию в API соответствует Letter. Если формат указан вместе с width и height, приоритет получает format; поэтому собственные размеры применяйте без него. Строковые размеры принимают единицы измерения, в том числе миллиметры, сантиметры и дюймы, что облегчает перенос требований из типографии.

landscape: true разворачивает лист. Это не масштабирование содержимого и не команда уместить таблицу: широкая таблица всё равно выйдет за границы, если сумма фиксированных колонок больше доступной ширины. Сначала задайте печатную сетку, разрешите перенос текста и сократите декоративные отступы, затем выбирайте альбомный лист. Для плотных отчётов полезно оставить первую колонку гибкой, числовым колонкам дать минимальную ширину и запретить разрыв коротких значений.

Собственный размер подходит этикеткам, билетам и карточкам. Его следует проверять не только визуально, но и в свойствах готового файла: некоторые последующие системы печати добавляют поля или подгоняют документ под лист. Если важен физический размер, не полагайтесь на скриншот. Измерьте MediaBox страницы, отключите масштабирование в драйвере печати и убедитесь, что браузерный макет использует те же единицы.

PDF собственного размера, подготовленный браузерной печатью

Поля и доступная область страницы

margin принимает отдельные значения для верхнего, правого, нижнего и левого края. Поля уменьшают область, в которой размещается основной документ, и одновременно создают пространство для колонтитулов. Если верхний шаблон включён, но верхнее поле равно нулю, текст может оказаться обрезанным или наложиться на содержимое. Подбирайте поля вместе с высотой шаблона, а не после завершения дизайна.

Удобно хранить размеры в одном объекте и использовать их как контракт шаблона. Например, верхние 20 мм зарезервированы под номер заказа, нижние 16 мм — под пагинацию, а боковые 12 мм — под безопасную область. CSS документа не должен заново компенсировать эти отступы через большой padding, иначе фактическое поле удвоится. Для модульной системы стоит задать переменные печатного дизайна и синхронизировать их с параметрами вызова.

Когда блок не помещается по высоте, браузер переносит его согласно алгоритму разбиения. Большой margin-bottom у последнего элемента может создать почти пустую страницу, а отрицательные поля способны вытянуть контент в зону колонтитула. Такие приёмы, приемлемые на экране, в печати хрупки. Для интервалов между разделами используйте обычный отступ и тестируйте граничные данные: длинный адрес, многострочное название товара, максимальное число строк.

CSS для печати и правило @page

Метод печатает страницу с типом среды print. Поэтому правила @media print определяют, какие элементы попадут в документ. В них обычно скрывают навигацию, кнопки, всплывающие панели и интерактивные фильтры, переводят фон в печатную палитру, снимают фиксированную высоту контейнеров и раскрывают обрезанный текст. Экранный макет лучше не переписывать целиком: чем меньше расхождение двух вариантов, тем проще поддерживать их одновременно.

@media print {
  .toolbar, .sidebar, .no-print { display: none !important; }
  .report { width: auto; min-height: 0; overflow: visible; }
  a { color: inherit; text-decoration: none; }
  .card { break-inside: avoid; }
}

@page {
  size: A4;
  margin: 18mm 14mm;
}

Если размер задан в @page, параметр preferCSSPageSize: true просит браузер предпочесть CSS-размер параметрам вызова. Без этого флага содержимое может масштабироваться под выбранный формат. Используйте один центр управления: либо размер находится в коде генератора, либо шаблон объявляет его сам. Одновременные несогласованные настройки затрудняют поиск причины, почему документ стал меньше или получил неожиданные поля.

Не все экранные свойства хорошо переходят на страницы. Фиксированные панели могут повторяться или перекрывать текст, контейнер с overflow: hidden обрежет продолжение, а высота в единицах окна браузера привяжет блок к виртуальному viewport, а не к бумаге. В печатных правилах снимайте ограничения высоты, проверяйте поведение flex- и grid-контейнеров на границе страницы и не используйте абсолютное позиционирование для основного потока.

Результат применения печатных CSS-правил

Разрывы страниц и целостность блоков

Современные свойства break-before, break-after и break-inside управляют пагинацией. Для заголовка раздела можно запретить разрыв сразу после него, карточку товара удерживать целиком, а каждую главу начинать с нового листа. Устаревшие свойства с префиксом page-break- всё ещё встречаются в шаблонах, но новый синтаксис понятнее и охватывает больше типов разбиения.

break-inside: avoid является пожеланием, а не гарантией разместить любой блок целиком. Если карточка выше печатной области, браузеру всё равно придётся разрезать её или вытолкнуть содержимое. Поэтому большие таблицы, длинные условия договора и вложенные списки нужно проектировать так, чтобы они могли продолжаться. Запрет разрыва разумен для подписи с расшифровкой, короткой диаграммы или пары название — значение, но опасен для контейнера неизвестной высоты.

Строки таблицы обычно переносятся аккуратнее, если заголовок находится в thead, а данные — в tbody. Браузер может повторять шапку на следующих страницах, но сложные вложенные блоки и объединённые ячейки требуют проверки. Не превращайте таблицу в набор div только ради экрана: семантическая разметка лучше сообщает движку, где находятся строки и заголовки. Для очень широкой таблицы иногда правильнее сформировать отдельное приложение в альбомной ориентации.

PDF с управляемыми разрывами страниц

Колонтитулы, номера страниц и дата

Колонтитулы включаются параметром displayHeaderFooter. HTML верхнего и нижнего шаблона передаётся через headerTemplate и footerTemplate. Специальные классы date, title, url, pageNumber и totalPages заменяются печатными значениями. Для номера вида 3 / 12 достаточно двух элементов с соответствующими классами и разделителя между ними.

const footerTemplate = `
  <div style="font-size:9px;width:100%;text-align:center">
    <span class="pageNumber"></span>
    <span> / </span>
    <span class="totalPages"></span>
  </div>`;

await page.pdf({
  format: 'A4',
  displayHeaderFooter: true,
  footerTemplate,
  headerTemplate: '<div></div>',
  margin: { top: '16mm', bottom: '18mm' }
});

Шаблоны колонтитулов живут отдельно от DOM основной страницы. На стили сайта рассчитывать нельзя: задавайте шрифт, размер, цвет, ширину и отступы непосредственно в шаблоне. Скрипты в колонтитуле не предназначены для вычислений. Если там требуется номер договора, название клиента или период отчёта, вставьте безопасно экранированное значение при сборке строки.

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

PDF с верхним и нижним колонтитулами

Фоновые изображения, прозрачность и точность цвета

printBackground: true сохраняет фоновые цвета и картинки, которые печатный режим иначе может опустить. Параметр необходим для фирменных плашек, цветных диаграмм, водяного узора и дизайна сертификата. Он не исправляет отсутствующий ресурс: если изображение не загрузилось или CSS-селектор не сработал, фон останется пустым. Перед печатью проверяйте вычисленный стиль ключевого элемента и ответы на запросы.

Браузер корректирует цвета для печати, стремясь повысить читаемость и снизить расход краски. Чтобы получить цвета ближе к экранным, используют -webkit-print-color-adjust: exact. Это свойство следует применять осмысленно: очень светлый текст на цветной плашке может выглядеть хорошо на мониторе, но плохо на офисном принтере. Для документов, предназначенных и для просмотра, и для физической печати, лучше подготовить проверенную палитру, а не просто принудить весь документ к точному экранному отображению.

omitBackground: true скрывает стандартный белый фон и позволяет получить прозрачность там, где формат и дальнейший процесс её поддерживают. Это узкая задача, например наложение страницы на другой фон. Для обычного счёта прозрачность нежелательна: просмотрщики могут показывать её разным цветом, а при растрировании итог станет непредсказуемым. Белый фон остаётся самым надёжным для архивного и печатного документа.

PDF с печатью цветного фона

Экранный и печатный вариант страницы

По умолчанию PDF создаётся с CSS-средой print. Если требуется точное экранное оформление, перед печатью вызывают page.emulateMediaType('screen'). Это полезно для страницы без отдельной печатной темы, но решение имеет цену: навигация, липкие панели, тени и элементы управления могут попасть в документ. Часто правильнее сохранить печатную среду и перенести в неё только необходимые экранные цвета и сетку.

Переключение среды выполняйте до проверки финальной геометрии. После emulateMediaType() могут измениться размеры блоков, видимость секций и загруженные ресурсы. Затем дождитесь повторной стабилизации макета. Если приложение реагирует на matchMedia и перестраивает графики, одного ожидания сети недостаточно; нужен сигнал от компонента, что он завершил повторный рендер.

Для диагностики удобно сделать PNG-снимок непосредственно перед page.pdf(). Снимок и PDF используют разные механизмы разбиения, поэтому не совпадут по страницам, но помогут понять, успели ли появиться данные и не перекрывает ли их модальное окно. Если снимок корректен, а печать нет, ищите проблему в @media print, @page, полях или разрывах. Если ошибка видна уже на снимке, причина находится раньше — в данных, навигации или ресурсах.

Диапазоны страниц и выборочная печать

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

Для надёжного отделения частей лучше сначала формировать самостоятельные документы, а затем объединять их PDF-инструментом. Так каждый раздел получает собственные размеры и колонтитулы, а выбор не зависит от случайного переноса строки. pageRanges уместен, когда макет стабилен либо пользователь буквально выбирает страницы после предварительного расчёта.

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

Получение файла без записи на диск

Если path не задан, page.pdf() возвращает Uint8Array. Это подходящий режим для веб-обработчика: массив можно передать в ответ с MIME-типом PDF, записать в облачное хранилище или отправить в очередь. Он также удобен в средах с временной файловой системой, где запись на диск не гарантируется между запросами.

const bytes = await page.pdf({ format: 'A4', printBackground: true });

response.setHeader('Content-Type', 'application/pdf');
response.setHeader('Content-Disposition', 'attachment; filename="report.pdf"');
response.end(Buffer.from(bytes));

Не храните множество больших массивов одновременно. После отправки убирайте ссылки на буфер, закрывайте страницу и ограничивайте число параллельных заданий. При потоковой архитектуре можно использовать метод создания PDF-потока, если он подходит выбранной версии API и окружению, но даже поток не отменяет затраты браузера на полный расчёт макета. Сам документ должен быть сформирован до устойчивой передачи результата.

При записи через path относительный путь вычисляется от текущего рабочего каталога процесса. На сервере он часто отличается от каталога исходного файла. Используйте заранее созданный абсолютный каталог, безопасное имя и атомарное перемещение. Никогда не соединяйте путь с непроверенным названием от пользователя: последовательности перехода к родительскому каталогу могут вывести запись за разрешённую область.

Печать счетов и коммерческих документов

Счёт обычно состоит из реквизитов, таблицы позиций, итогов, условий оплаты и подписи. Для него важнее всего устойчивость к разной длине данных. Название организации может занять три строки, адрес — пять, а одна позиция — содержать длинное описание. Шаблон должен расти по высоте без абсолютного позиционирования, числовые столбцы — выравниваться по правому краю, а итоговый блок — по возможности не отделяться от последних строк.

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

Штрихкоды и QR-коды должны появиться до критерия готовности. Если они строятся canvas-скриптом, проверьте размер холста и фактическое наличие непустых пикселей. SVG обычно масштабируется лучше и остаётся резким в PDF. Важный код не следует подгружать с внешнего генератора в момент печати: задержка или блокировка домена сделает документ неполным. Создавайте его внутри доверенного процесса.

Пример счёта, сформированного в PDF

Отчёты с таблицами и большим числом строк

Большой отчёт испытывает браузер сразу по трём направлениям: объём DOM, вычисление стилей и пагинация. Тысячи простых строк могут работать быстрее, чем сотни строк со сложными тенями, вложенными flex-контейнерами и изображениями. Для печатного маршрута уберите интерактивные элементы, анимацию, липкие колонки и виртуализацию. Последняя экономит ресурсы на экране, но в PDF приведёт к отсутствию строк, которые не были смонтированы.

Если таблица шире листа, сначала определите информационный приоритет. Второстепенные поля можно вынести в приложение, объединить или перенести под основное значение. Уменьшение шрифта до нечитаемого размера — плохой способ уместить всё. Альбомный лист расширяет область, но не бесконечно. Для действительно широких данных лучше создать несколько согласованных таблиц или использовать формат большего размера.

Повторяющаяся шапка помогает читать многостраничный отчёт. Разметка thead должна содержать только заголовки, а не фильтры и интерактивные кнопки. Если шапка перекрывает первую строку следующего листа, проверьте фиксированное позиционирование и границы таблицы. Сложные многоуровневые заголовки тестируйте на каждом месте разрыва, особенно когда используются rowspan и colspan.

Для миллионов записей Puppeteer PDF не должен становиться заменой агрегированию. Сначала сократите данные на уровне запроса, сформируйте итоговые показатели и ограничьте отчёт разумным объёмом. Печать бесконечной выгрузки создаёт огромный файл, задерживает очередь и может исчерпать память. Полный набор лучше отдавать как CSV или табличный формат, а PDF оставить для читаемого резюме.

Альбомный PDF с широкой таблицей

Диаграммы, canvas и SVG

Графики часто отрисовываются позже текста, поэтому селектор контейнера не гарантирует их готовность. Компонент должен выставлять собственный признак после завершения анимации и загрузки данных. Перед печатью отключайте переходы и анимацию, иначе PDF может зафиксировать промежуточный кадр. У библиотек визуализации нередко есть режим без анимации, который лучше включить именно для печатного маршрута.

Canvas растеризуется с текущим внутренним разрешением. Если CSS делает холст большим, а его пиксельная матрица остаётся маленькой, диаграмма будет размытой. Учитывайте коэффициент плотности и создавайте холст достаточного размера до рисования. SVG сохраняет векторные линии и текст, поэтому предпочтителен для схем и диаграмм, если библиотека умеет его выдавать.

Интерактивные подсказки, выпадающие легенды и элементы, появляющиеся при наведении, в PDF бесполезны. Выведите все необходимые подписи прямо на график или добавьте рядом таблицу значений. Цвет не должен быть единственным способом различить серии: при чёрно-белой печати помогут разные штрихи, маркеры и подписи. Такой макет остаётся понятным и при просмотре на экране, и после печати.

Шрифты и многоязычный текст

Браузер может напечатать только те символы, для которых доступен глиф. Если нужный шрифт не загрузился, сработает замена; если и в заменяющем нет символа, в документе появится пустой квадрат. Для кириллицы, латиницы и базовых знаков достаточно многих распространённых гарнитур, но CJK, арабская письменность, математические знаки и редкие валюты требуют осознанного набора шрифтов.

Веб-шрифт следует подключать из стабильного места и дождаться document.fonts.ready. Разрешённые форматы и политика доступа должны соответствовать адресу страницы. Ошибка CORS, неверный MIME-тип или запрет сети могут оставить только системную замену. Для закрытого контура удобнее включить шрифт в образ приложения либо отдавать его тем же сервером, что и шаблон.

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

Эмодзи особенно зависят от окружения. Цветной глиф может отсутствовать, замениться монохромным или выглядеть иначе. В деловом документе надёжнее использовать SVG-значок с понятной подписью. Если эмодзи несёт смысл статуса, обязательно дублируйте его текстом, иначе печать на другом сервере изменит значение строки.

Многоязычный текст и встроенные шрифты в PDF

Авторизация, куки и защищённые страницы

Для печати страницы за входом можно создать отдельный контекст браузера, установить куки или пройти форму. Прямое внесение куки быстрее и стабильнее, если сервер уже выдал проверенный сеанс. Автоматический ввод логина повторяет пользовательский путь, но зависит от разметки формы, двухфакторной проверки и защиты от автоматизации. Для внутреннего сервиса лучше предусмотреть специальный одноразовый токен с минимальными правами.

Секреты не должны попадать в колонтитул, адрес документа, имя файла или журнал ошибок. Если маршрут принимает токен в строке запроса, класс url в стандартном колонтитуле способен напечатать его на каждой странице. Уберите вывод адреса и очищайте диагностические сообщения. Заголовок авторизации устанавливайте только для разрешённого домена, иначе перенаправление может отправить его не тому получателю.

Один контекст на задание предотвращает утечку куки между клиентами. После завершения контекст закрывают независимо от успеха печати. Повторное использование одной страницы для разных пользователей опасно: в памяти могут остаться localStorage, IndexedDB, service worker и кэш. Даже если следующий маршрут визуально открыт заново, остаточное состояние способно повлиять на документ.

Печатный маршрут должен проверять права на данные самостоятельно. Нельзя считать сам факт запуска внутреннего генератора достаточным разрешением. Передайте идентификатор пользователя или сервисную роль, ограничьте доступ конкретным заказом и сохраняйте аудит. Так ссылка на чужой отчёт не превратится в способ выгрузить его через PDF.

Подстановка данных и защита HTML-шаблона

Когда значения вставляются в HTML-строку, их необходимо экранировать. Имя клиента с символом < не должно ломать разметку, а пользовательский комментарий не должен выполнять скрипт. Безопаснее использовать шаблонизатор с автоматическим экранированием или передавать данные в страницу через page.evaluate() и записывать их в textContent. Разрешайте HTML только там, где он действительно нужен и проходит строгую очистку.

Не доверяйте ссылкам на изображения из пользовательских данных. Браузер попробует обратиться к указанному адресу, включая внутреннюю сеть. Это создаёт риск серверных запросов от имени генератора. Разрешайте только известные домены, проксируйте изображения через контролируемое хранилище либо внедряйте проверенные файлы. Запросы к локальным адресам, служебным метаданным и файловой системе должны блокироваться.

Request interception позволяет контролировать каждый ресурс. Политика может разрешить документ, стили, шрифты и изображения из заданного набора, а всё остальное отменить. Не забывайте, что чрезмерно строгий фильтр способен заблокировать data- и blob-ресурсы, нужные графику. Сначала соберите список фактических запросов на тестовом макете, затем сформулируйте белый список и добавьте тест на отказ.

Скрипты страницы выполняются с возможностями браузера. Если печатается недоверенная разметка, изоляция контейнера и браузерная песочница становятся обязательной частью защиты, а не необязательной оптимизацией. Не запускайте такой процесс с широкими правами к файловой системе и сети. Ограничьте время, память, число процессов и доступные каталоги.

Пакетная генерация и повторное использование браузера

Запуск Chromium заметно дороже открытия новой страницы, поэтому очередь документов обычно использует один браузер на несколько задач. Это не означает бесконечную жизнь процесса. После заданного числа документов или при росте памяти браузер можно аккуратно заменить. Менеджер должен перестать выдавать ему новые задания, дождаться текущих и запустить свежий экземпляр.

Параллелизм ограничивайте семафором. Десятки страниц, одновременно рассчитывающих большие таблицы и шрифты, способны замедлить весь сервер сильнее, чем последовательная очередь. Начинайте с небольшого числа рабочих страниц, измеряйте время и пиковую память, затем увеличивайте нагрузку. Отдельно учитывайте скачивание ресурсов: десять одинаковых отчётов могут создать десятикратный всплеск на API.

Каждое задание должно иметь собственный таймаут и возможность отмены. После превышения лимита страницу закрывают, но браузер сохраняют, если он остаётся здоровым. Если закрытие зависло или процесс перестал отвечать, завершают весь экземпляр. Простое отклонение Promise без очистки оставляет ресурсы занятыми и постепенно разрушает сервис.

Повторные попытки допустимы только для временных ошибок. Сбой сети можно повторить с задержкой, а неверный CSS или отсутствующий обязательный шрифт будет ломаться каждый раз. Классифицируйте ошибки: инфраструктурная, входные данные, шаблон, доступ, лимит. Это уменьшает очередь бессмысленных повторов и быстрее показывает разработчику настоящую причину.

Кэш браузера и воспроизводимость

Кэш ускоряет повторную загрузку общих шрифтов и стилей, но способен скрыть проблему развертывания. Один сервер продолжит печатать со старым файлом, другой возьмёт новый, и документы разойдутся. Для критичных шаблонов используйте версии в именах ресурсов и неизменяемые адреса. Тогда новый выпуск явно получает новый набор, а старые задания остаются воспроизводимыми.

Отключение кэша полезно в тестах, где нужно проверить полный путь загрузки. В рабочей очереди оно повышает задержку и трафик. Компромисс — долгий кэш для контентно адресованных ресурсов и запрет для данных отчёта. Service worker может неожиданно вернуть устаревшую страницу; для печатного маршрута его часто обходят, если офлайн-логика не является частью документа.

Воспроизводимость требует фиксировать не только HTML, но и часовой пояс, локаль, шрифты, размер окна, данные и версию шаблона. Текущая дата в колонтитуле делает байты разными при каждом запуске. Если нужен детерминированный файл, передавайте дату явно и не используйте динамический класс date. Аналогично случайные идентификаторы и порядок несортированных данных следует стабилизировать.

Запуск в контейнере

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

Процесс браузера создаёт дочерние процессы. В контейнере полезен корректный init-процесс, который собирает завершившихся потомков. Без него после большого числа заданий могут накапливаться процессы-зомби. Завершение контейнера должно сначала остановить приём задач, закрыть страницы и браузер, а затем выйти в пределах срока оркестратора.

Разделяемая память в некоторых окружениях мала, что приводит к падениям на больших страницах. Флаг, переводящий использование памяти в другой каталог, иногда помогает, но может снизить производительность. Лучше сначала измерить потребление и увеличить доступный объём там, где это возможно. Не добавляйте набор флагов из случайных примеров без понимания: часть из них отключает механизмы защиты.

Шрифты должны входить в образ или подключаться из надёжного внутреннего хранилища. После добавления пакета шрифтов очистите и перестройте кэш шрифтов, затем проверьте контрольный PDF. Различие между рабочим ноутбуком и контейнером чаще всего проявляется именно в шрифтах, локали и отсутствующих системных библиотеках.

Песочница Chromium и права процесса

Ошибка запуска с упоминанием sandbox часто провоцирует добавить --no-sandbox. Такой флаг может запустить процесс, но снижает изоляцию браузера и не должен быть автоматическим решением. Правильный подход зависит от операционной системы, пользователя контейнера и доступных механизмов ядра. Настройте среду так, чтобы поддерживаемая песочница работала, особенно если документ содержит внешние или пользовательские данные.

Браузер не должен работать от привилегированного пользователя и иметь доступ ко всему диску. Создайте отдельного пользователя, разрешите запись только во временный каталог и папку результатов, ограничьте сеть. Временные файлы удаляйте после задания. Даже доверенный шаблон может включить скомпрометированную зависимость, поэтому принцип минимальных прав остаётся актуальным.

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

Ошибка Could not find Chrome и установка браузера

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

В CI кэш браузера должен переживать шаги сборки в ожидаемом каталоге. Если зависимости установлены на одном этапе, а рабочий образ копирует только node_modules, бинарный файл может остаться в домашнем кэше предыдущего слоя. Перенесите кэш явно или запускайте установку в финальном образе. Запись точного пути в конфигурации делает сборку понятнее.

puppeteer-core не загружает браузер автоматически. При его использовании нужно передать путь к поддерживаемому исполняемому файлу или подключиться к уже запущенному браузеру. Это удобно в корпоративном образе, где Chrome управляется отдельно, но требует согласования версий. Нельзя считать любой найденный Chromium совместимым только потому, что он запускается.

Не подменяйте проблему случайным системным браузером без проверки. Отличие протокола или возможностей печати проявится нестабильно: простая страница создастся, а сложная функция упадёт. Зафиксируйте используемую комбинацию, прогоните набор PDF-тестов и обновляйте библиотеку вместе с браузером контролируемо.

Отсутствующие библиотеки Linux

Когда исполняемый файл найден, но не запускается, сообщение может указывать на отсутствующую системную библиотеку. Определите её через диагностический вывод, а не устанавливайте огромный случайный набор. Минимальные образы требуют библиотек графики, шрифтов, сертификатов и других компонентов, которыми браузер пользуется даже без видимого окна.

Проверяйте запуск на том же базовом образе и под тем же пользователем, что и рабочий процесс. Команда, успешная в интерактивном контейнере от root, может падать у сервисного пользователя. Добавьте health-check, который не только открывает браузер, но и создаёт маленький PDF с текстом и шрифтом. Так проверяется именно цепочка, нужная приложению.

После обновления базовой системы повторно запускайте контрольные документы. Библиотеки и шрифтовый стек могут измениться без правок JavaScript. Сравнение только по коду проекта не обнаружит такую регрессию. Хэш образа, список пакетов и результаты PDF-теста должны быть частью журнала развертывания.

Таймауты навигации и вечная сеть

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

Разделяйте таймауты этапов. Загрузка маршрута, ожидание данных, ожидание шрифтов и печать имеют разные причины сбоя. Один общий лимит сообщает лишь не успело, тогда как отдельные метки показывают, где затрачено время. Записывайте длительность каждого этапа и размер DOM; рост одного показателя часто предупреждает о будущих сбоях.

При таймауте полезно сохранить диагностический снимок и короткий фрагмент HTML, но не весь документ с персональными данными. Снимок должен попадать в защищённое хранилище с ограниченным сроком. Журнал консоли и ошибки запросов часто объясняют пустую таблицу лучше, чем стектрейс генератора.

Не оставляйте страницу ждать после отмены пользовательского запроса. Сигнал отмены должен дойти до очереди и закрыть страницу, если результат больше не нужен. Иначе множество закрытых клиентских соединений продолжат потреблять ресурсы до таймаута.

Пустые страницы и пропавшие элементы

Пустой PDF обычно связан не с самим вызовом печати, а с состоянием страницы. Возможные причины: содержимое скрыто в @media print, основной контейнер имеет нулевую высоту, приложение ещё не смонтировалось, авторизация перенаправила на пустой маршрут или ресурс был заблокирован. Сначала сохраните HTML и снимок непосредственно перед печатью, затем проверьте вычисленный стиль корневого блока.

Если пропал только элемент, выясните, существует ли он в DOM и имеет ли ненулевой прямоугольник. display: none, visibility: hidden, прозрачность, отрицательное смещение и родительский overflow дают разные симптомы. Для canvas проверьте внутренний размер, для изображения — naturalWidth, для SVG — viewBox.

Белая дополнительная страница часто появляется из-за элемента высотой ровно в лист плюс поля или из-за нижнего отступа. Уберите min-height: 100vh в печатной теме, проверьте рамки и округление единиц. Небольшое отличие в долю пикселя способно вытолкнуть последнюю линию на новый лист. Не маскируйте проблему диапазоном страниц, пока не понятна причина: при других данных лишняя страница может оказаться содержательной.

Обрезанный текст и горизонтальный выход

Обрезание справа возникает, когда фиксированная ширина, минимальная ширина или длинная непрерывная строка превышают печатную область. В печатном CSS задайте max-width: 100%, разрешите перенос длинных идентификаторов и снимите экранные ограничения. Для URL-подобных значений подходит overflow-wrap: anywhere, но номера и артикулы лучше переносить по осмысленным разделителям.

Вертикальное обрезание связано с фиксированной высотой и overflow. Карточка, рассчитанная на две экранные строки, может получить пять строк в другой гарнитуре. В печати используйте естественную высоту. Если блок обязан иметь фиксированный размер, заранее ограничьте текст и покажите явное многоточие либо продолжение в приложении.

Масштаб scale может уменьшить всю страницу, но это крайний инструмент. Он влияет на читаемость и физический размер элементов. Сначала исправьте сетку, поля и ширину. Масштаб разумен для заранее спроектированного листа, который нужно немного подогнать, а не для спасения непредсказуемого интерфейса.

Почему не печатается фон или меняется цвет

Первым проверьте printBackground. Затем убедитесь, что фон задан элементу, который существует в печатной теме, и что картинка загрузилась. Относительный путь в HTML, переданном через setContent(), может разрешаться не туда. Вычисленный стиль должен содержать ожидаемый цвет или изображение, а список неуспешных запросов — быть пустым.

Изменение оттенков является ожидаемым поведением печатного режима. Примените точную цветопередачу только к фирменным и информационным блокам, которым она нужна. Если весь документ слишком тёмный, создайте отдельные печатные переменные цветов. PDF не гарантирует соответствие конкретному принтеру: профиль устройства, бумага и настройки драйвера всё равно влияют на результат.

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

Проблемы колонтитулов

Если номер страницы не появился, проверьте включение displayHeaderFooter и точное имя специального класса. Элемент должен находиться в строке шаблона. Если весь колонтитул отсутствует, увеличьте соответствующее поле и задайте видимый размер шрифта. Стили основной страницы к нему не применяются.

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

Значение url может быть нежелательным и даже содержать секретный параметр. Выводите только специально подготовленное название документа. Класс title берёт заголовок страницы, поэтому установите его перед печатью или подставьте собственный текст. Динамическую дату лучше передавать явно, если важна единая временная зона.

Ошибки изображений и политика доступа

Картинка может открываться в обычном браузере пользователя и не открываться в генераторе из-за другой сети, отсутствующей куки, заголовка referer или политики CORS. Для элемента img CORS не всегда мешает показу, но становится важным, если изображение затем читается canvas. Диагностика должна фиксировать код ответа, конечный адрес после перенаправления и тип содержимого.

Сервер иногда возвращает HTML-страницу ошибки с кодом 200 вместо изображения. Браузер покажет сломанный значок, а сетевой запрос формально будет успешным. Проверяйте Content-Type и naturalWidth. Для обязательных знаков храните ожидаемый хэш или хотя бы минимальные размеры, чтобы заглушка не прошла проверку.

Встроенные данные исключают сетевую задержку, но увеличивают HTML и потребление памяти. Небольшой логотип удобно внедрить, а фотографии каталога лучше отдавать отдельными оптимизированными файлами. Устанавливайте реальные размеры изображения и не вкладывайте многомегапиксельную фотографию в блок шириной несколько сантиметров.

Размер PDF и оптимизация

Основной вклад в размер обычно дают фотографии, встроенные шрифты и повторяющиеся ресурсы. Сжимайте изображения до разрешения, соответствующего печатному размеру, выбирайте подходящий формат и удаляйте невидимые дубликаты. CSS-уменьшение не уменьшает исходные байты. Для схем и логотипов SVG часто компактнее и резче растрового файла.

Каждое начертание шрифта может добавить данные. Не подключайте весь семейный набор, если документ использует обычный и полужирный. Проверьте лицензию на встраивание и набор символов. Подмножество может формироваться браузером, но рассчитывать на это как на единственную оптимизацию не стоит; сначала исключите неиспользуемые файлы.

Большой DOM увеличивает время, но не обязательно пропорционально размер файла. Скрытые через экранные стили элементы могут всё ещё загружать ресурсы, а полностью удалённые — нет. Печатный маршрут должен не только скрывать меню, но и не запрашивать тяжёлые виджеты. Отдельный параметр страницы режим печати позволяет серверу не отдавать ненужные данные.

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

Метаданные, заголовок и имя файла

Заголовок страницы может попасть в служебные данные и используется специальным классом колонтитула. Установите осмысленный document.title перед печатью. Он не заменяет имя скачиваемого файла, которое задаёт HTTP-заголовок или путь записи. Оба значения следует очищать от управляющих символов и ограничивать по длине.

Имя файла лучше строить из безопасного идентификатора и даты, а пользовательское название оставлять внутри документа. Символы, допустимые в одной файловой системе, могут быть запрещены в другой или неправильно обработаны браузером. Для ответа используйте корректное кодирование имени и запасной ASCII-вариант.

Puppeteer PDF не является полноценным редактором метаданных PDF. Если нужны автор, ключевые слова, профиль архивного стандарта, вложения или сложная структура, после печати потребуется PDF-библиотека. Разделяйте задачи: браузер формирует внешний вид из HTML, специализированный инструмент проверяет или дополняет контейнер PDF.

Тестирование шаблонов

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

Сравнение PDF побайтно подходит только для полностью детерминированного окружения. Дата, идентификаторы объектов, порядок метаданных и версия браузера могут менять байты без визуального отличия. Практичнее проверять число страниц, размеры листа, наличие ключевого текста, отсутствие пустых страниц и допустимый диапазон размера файла. Для визуального контроля страницы растрируют и сравнивают с эталоном с небольшим допуском.

Текстовое извлечение обнаруживает ситуацию, когда текст случайно превратился в изображение или исчез. Оно не гарантирует правильную геометрию, поэтому дополняется снимком. Для таблицы можно проверить порядок заголовков и итоговую сумму. Для колонтитула — наличие номеров на каждой странице. Тест должен проверять смысл документа, а не только факт создания файла.

Эталон обновляют осознанно. Если изменение CSS сдвинуло весь отчёт, новая картинка не должна приниматься одной кнопкой без просмотра. Храните причину изменения рядом с эталоном и отдельно просматривайте страницы с максимальным отличием.

Проверка готового PDF

После page.pdf() убедитесь, что массив не пуст и начинается с сигнатуры PDF. Запись на диск должна завершиться до отправки ссылки пользователю. Затем можно открыть файл библиотекой проверки, прочитать число страниц и размеры. Нулевая страница, неожиданно огромный лист или документ в несколько байтов — повод отклонить результат.

Для критичных документов полезно извлечь текст и найти идентификатор заказа, имя получателя и итог. Такая проверка не заменяет визуальную, но не позволяет выдать красивую пустую форму. Чувствительные значения не следует писать в общий журнал; достаточно результата проверки или хэша ожидаемого идентификатора.

Хэш файла помогает доказать неизменность при хранении и передаче. Он не говорит, что документ правильный, поэтому вычисляется после содержательных проверок. Если файл подписывается цифровой подписью, подпись выполняют после всех модификаций и оптимизации; любая последующая правка нарушит её.

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

В параметрах печати есть экспериментальные возможности создания структуры доступности и outline. На них нельзя опираться без тестов в целевых просмотрщиках. HTML-заголовки, списки, таблицы и подписи формируют полезную семантическую основу, но наличие корректной разметки страницы ещё не гарантирует соответствие требованиям доступного PDF.

Если документ должен пройти формальную проверку PDF/UA или архивного профиля, используйте специализированный валидатор и при необходимости последующую обработку. Проверьте порядок чтения, язык, альтернативный текст, таблицы и навигацию клавиатурного просмотра. Браузерная печать решает задачу отображения, а нормативное соответствие является отдельным проектом.

Outline может строить навигационную структуру из заголовков, но экспериментальный статус означает возможность изменения поведения. Перед включением проверьте длинный документ с вложенными уровнями и русскими названиями. Если закладки обязательны по договору, надёжнее контролировать их специализированной PDF-библиотекой.

Viewport и бумажная страница — разные размеры

Размер окна, заданный через viewport, влияет на адаптивную верстку до печати: от него зависят медиазапросы ширины, выбор мобильного меню, плотность графика и количество колонок. Размер бумаги определяется отдельно параметрами PDF или правилом @page. Большой viewport не превращает A4 в широкий лист, а маленький viewport может включить мобильный интерфейс, который затем будет напечатан на обычной бумаге.

Для отчётов закрепите контрольную ширину viewport и проверяйте, что печатная тема не зависит от случайного размера окна. Если компонент рассчитывает геометрию при загрузке, задайте viewport до навигации. Изменение после отрисовки вызывает повторный layout, но не каждая библиотека графиков автоматически перестраивает canvas; после смены размера ей может потребоваться явный вызов обновления.

Параметр deviceScaleFactor влияет прежде всего на растровую отрисовку и снимки. Он не является способом сделать физический текст в PDF крупнее. Размер шрифта задаётся CSS, а физическая область — форматом листа и масштабом. Не смешивайте эти уровни: иначе документ будет выглядеть нормально на одном тестовом изображении, но получит неожиданную геометрию при печати.

Проверка DOM перед печатью

Перед созданием файла полезно выполнить короткую прикладную проверку в контексте страницы. Она может подтвердить, что заголовок не пуст, таблица содержит ожидаемое число строк, итог совпадает с переданными данными, а индикатор загрузки отсутствует. Проверка должна возвращать небольшой объект, а не весь DOM: так журнал остаётся компактным и не раскрывает содержание документа.

const state = await page.evaluate(() => ({
  title: document.querySelector('h1')?.textContent?.trim(),
  rows: document.querySelectorAll('tbody tr').length,
  loading: Boolean(document.querySelector('[aria-busy="true"]')),
  width: document.documentElement.scrollWidth
}));

if (!state.title || state.loading || state.rows === 0) {
  throw new Error('Страница не готова к печати');
}

Ширина документа помогает обнаружить горизонтальный выход до создания PDF. Сравните scrollWidth с шириной печатного контейнера, а для ключевых блоков прочитайте getBoundingClientRect(). Такие проверки не заменяют визуальный тест, но быстро ловят нулевой размер, невидимый контейнер и таблицу, которая неожиданно стала шире макета.

Код внутри evaluate() выполняется в странице и не видит переменные Node.js, пока они не переданы аргументом. Возвращаемое значение должно сериализоваться. Не пытайтесь вынести наружу DOM-элемент или функцию; извлекайте только строки, числа и логические признаки. Это ограничение полезно: диагностический интерфейс получается явным и легко тестируется.

Работа Puppeteer с содержимым DOM

Отладка в видимом режиме

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

Поставьте паузу непосредственно перед page.pdf(), откройте инструменты разработчика и переключите эмуляцию печатной среды. В панели стилей видно, какое правило скрывает элемент, а в сети — какой шрифт или рисунок не загрузился. Проверяйте именно тот маршрут, набор данных и параметры viewport, при которых возник сбой. Упрощённая демонстрационная страница может не воспроизвести проблему.

События console, pageerror, requestfailed и ответы с ошибочными кодами следует собирать и в headless. Они объясняют, почему компонент не установил признак готовности. Не превращайте журнал в полный сетевой дамп: скрывайте заголовки авторизации, куки и параметры с персональными данными, а тело ответа сохраняйте только в защищённой диагностике.

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

Автоматизация действий на странице через Puppeteer

Подключение к уже запущенному браузеру

Вместо запуска нового Chromium процесс может подключаться к управляемому браузеру. Такая архитектура применяется, когда браузер вынесен в отдельный контейнер или сервис, а приложение отправляет ему задания. Она отделяет тяжёлый процесс от веб-сервера и упрощает масштабирование, но добавляет сеть, авторизацию соединения и необходимость очищать контексты после каждого клиента.

Для удалённого соединения особенно важны ограничения доступа. Управляющий endpoint даёт широкие возможности браузера и не должен быть открыт общей сети. Используйте короткоживущие учётные данные, шифрование канала и сетевой белый список. Не передавайте секрет подключения в URL, который может попасть в журнал или колонтитул.

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

Такой режим не превращает Puppeteer PDF в готовый публичный сервис. HTTP-методы, очередь, хранение, контроль доступа и пользовательский интерфейс проектирует сама команда либо предоставляет отдельный поставщик. При выборе размещения учитывайте, где находятся персональные данные и ресурсы страницы.

Обёртка в HTTP-метод

Частый сценарий — endpoint, принимающий идентификатор отчёта и возвращающий PDF. Он не должен принимать произвольный HTML и произвольный адрес без строгой политики. Безопаснее выбрать шаблон из известного набора, загрузить данные по проверенным правам и передать генератору внутреннюю структуру. Это ограничивает SSRF, выполнение недоверенного кода и утечку секретов.

Синхронный ответ подходит небольшому документу, который стабильно создаётся за несколько секунд. Длинные отчёты лучше помещать в очередь: клиент получает идентификатор задания, а файл появляется после проверки. Очередь позволяет ограничить параллелизм, повторить временный сбой и не держать HTTP-соединение открытым. Статус должен различать ожидание, генерацию, проверку, готовность и окончательную ошибку.

Устанавливайте лимиты входных данных до открытия браузера: число строк, суммарный размер изображений, допустимый период и максимальную длину текста. Иначе один запрос создаст документ на тысячи страниц и вытеснит нормальные задания. Ограничение должно сопровождаться понятным сообщением и альтернативой, например выгрузкой данных в табличном формате.

Ответ с файлом задаёт Content-Type, безопасное имя и политику кэширования. Документ с персональными данными не следует кэшировать общим прокси. При хранении используйте короткоживущую подписанную выдачу или проверку прав при каждом скачивании. Сам факт знания имени файла не должен открывать доступ.

Обновление браузера без неожиданных сдвигов

Браузерный движок определяет разбиение страниц, шрифтовые метрики и поддержку CSS. После обновления макет может немного измениться даже без правки шаблона. Поэтому обновление зависимостей сопровождают печатью контрольного набора и сравнением страниц. Особенно внимательно проверяют таблицы на границе листа, колонтитулы, SVG, прозрачность и многоязычный текст.

Закрепляйте зависимости в lock-файле и собирайте воспроизводимый образ. Плавающий диапазон версий превращает одинаковый коммит в разные PDF в разные дни. Обновление выполняйте отдельным изменением, чтобы различия можно было связать с конкретной причиной. После проверки разворачивайте постепенно и наблюдайте длительность, память и долю ошибок.

Не храните старый браузер бесконечно ради совпадения пикселей. Исправления безопасности важны для процесса, который обрабатывает HTML и сеть. Цель тестов — быстро обнаружить регрессию и адаптировать шаблон, а не блокировать обновления. Если юридически значимый документ должен воспроизводиться, храните его готовые байты и данные аудита, а не рассчитывайте, что спустя годы повторная печать даст идентичный файл.

Сравнение Puppeteer PDF с аналогами

ПрограммаЛучше подходит дляГлавное ограничение
Puppeteer PDFПечати динамического HTML, CSS и JavaScript через ChromiumНужны Node.js и браузерный процесс
PDF CommanderРучного редактирования, сборки и оформления готовых PDFНе заменяет серверную автоматизацию HTML
PlaywrightЕдиного набора браузерной автоматизации и тестов с печатьюБолее широкий стек избыточен для одной функции PDF
wkhtmltopdfПростого командного преобразования стабильного HTMLСтарый движок ограничивает современный CSS
WeasyPrintПечатных HTML/CSS-документов без выполнения интерфейсного JavaScriptНе воспроизводит полноценное браузерное приложение
PDFKitПрограммного рисования страниц и точного размещения объектовНе рендерит готовую веб-страницу как браузер

Puppeteer PDF разумно выбирать, когда документ уже существует как веб-шаблон, зависит от JavaScript или должен совпадать с Chromium. PDF Commander удобнее для человека, которому нужно открыть готовый файл и исправить текст, страницы или оформление без написания кода. Playwright полезен команде, уже использующей его для браузерных тестов. WeasyPrint подходит печатным шаблонам с сильным CSS и без клиентского приложения, wkhtmltopdf — простому наследуемому процессу с проверенной разметкой, а PDFKit — документам, где страница строится командами рисования, а не HTML.

Когда выбирать Puppeteer PDF

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

Для ручной правки существующего PDF решение неудобно: оно не предоставляет визуальные инструменты редактирования текста, перестановки страниц или аннотаций. Исходником остаётся HTML и код. Если оператору нужно каждый раз менять абзац мышью, лучше использовать PDF-редактор. Если же документ создаётся автоматически из базы и должен повторяться сотни раз, кодовый процесс даёт контроль и масштабируемость.

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

Практическая схема надёжного генератора

  1. Получить проверенные входные данные и идентификатор шаблона.
  2. Взять разрешение на печать конкретного документа.
  3. Создать изолированный контекст и страницу.
  4. Установить локаль, часовой пояс, размер окна и параметры доступа.
  5. Открыть маршрут либо поместить безопасно собранный HTML.
  6. Дождаться прикладного признака готовности, шрифтов и обязательных изображений.
  7. Переключить нужную CSS-среду и отключить анимации.
  8. Проверить ошибки консоли и неуспешные запросы.
  9. Создать PDF с явно заданными форматом, полями и фоном.
  10. Проверить сигнатуру, число страниц, размеры и ключевой текст.
  11. Сохранить результат, вычислить хэш и закрыть страницу.
  12. Записать длительность этапов без раскрытия секретов и персональных данных.

Такая последовательность отделяет подготовку страницы от проверки результата. Если документ оказался неверным, журнал показывает, на каком этапе возникло отклонение. Схема также позволяет заменять хранение, очередь или шаблонизатор, не меняя требования к печати.

Контрольный список макета

  • Формат листа и ориентация заданы явно, а не оставлены значением по умолчанию.
  • Поля учитывают фактическую высоту колонтитулов.
  • Печатная тема скрывает кнопки, меню, модальные окна и липкие панели.
  • Таблицы допускают перенос строк и повторяют понятную шапку.
  • Большие блоки не защищены от разрыва без проверки их максимальной высоты.
  • Фон включён только там, где он нужен, а важные цвета протестированы на печати.
  • Шрифты содержат все языки и начертания контрольного документа.
  • Изображения имеют подходящее разрешение и не зависят от случайного внешнего узла.
  • Часовой пояс, локаль и дата задаются воспроизводимо.
  • Пустой набор данных показывает понятное состояние, а не ломает сетку.

Проверять список следует после каждого заметного изменения общего CSS, компонентов таблицы и контейнерного образа. Печатный маршрут зависит от всех трёх уровней. Изменение, которое не затронуло код генерации, всё равно способно изменить PDF.

Итоговая организация работы

Puppeteer PDF даёт предсказуемый результат, когда команда относится к печати как к отдельному сценарию браузерного интерфейса. Шаблон получает собственные правила @media print, данные сообщают о готовности, ресурсы проверяются, а параметры листа не оставляются неявными. Вызов page.pdf() становится последним коротким шагом после подготовки, а не попыткой исправить страницу одним набором опций.

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

Начинать лучше с одного реального документа и набора сложных примеров: длинные строки, несколько языков, много страниц, отсутствующая картинка и медленный ответ API. После устранения найденных проблем правила можно перенести на остальные шаблоны. Такой порядок быстрее обнаруживает ограничения макета и даёт понятные критерии, по которым новый PDF разрешено выдавать пользователю.