Сценарий · обработка документов

Serverless-обработка PDF: генерация, извлечение и трансформация

Генерируйте PDF из HTML-шаблонов или данных, извлекайте текст и структурированные данные из загруженных документов и трансформируйте PDF в шагах фонового пайплайна — без HTTP-таймаута, с поддержкой бинарных ответов и встроенными ретраями.

Обновлено: 2026-06-28

Суть ответа

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

Когда подходит

  • Генерация счетов и отчётов, которая занимает больше секунды
  • Извлечение данных из документов и OCR в фоновых пайплайнах
  • Пакетная генерация PDF (N счетов, N отчётов) с параллельным fan-out

На что обратить внимание

  • PDF-библиотеки (pdf-lib, pdfkit, puppeteer для HTML-в-PDF) требовательны к памяти. Генерация 10 PDF параллельно в одном вызове обработчика может исчерпать память на ограниченных serverless-рантаймах.
  • Без ретрая при сбое временное падение библиотеки или OOM во время генерации теряет задачу целиком. Пользователь получает ошибку и вынужден повторять вручную.

Почему обработка PDF не помещается в синхронный HTTP

  • Генерация PDF из сложных шаблонов занимает 2–30 секунд — упирается в таймауты шлюза
  • Извлечение текста из загруженных PDF с OCR может занимать минуты на файлах с большим числом изображений
  • Бинарные ответы (base64-кодирование больших PDF) добавляют давление на память синхронных обработчиков

Разброс задержек у PDF-операций широкий: простой одностраничный счёт может сгенерироваться за 500 мс, но 50-страничный отчёт с графиками, 100-страничный контракт с OCR или пачка из 200 счетов не укладываются в окно синхронного обработчика.

Почему генерация PDF внутри HTTP-обработчика хрупка

PDF-библиотеки (pdf-lib, pdfkit, puppeteer для HTML-в-PDF) требовательны к памяти. Генерация 10 PDF параллельно в одном вызове обработчика может исчерпать память на ограниченных serverless-рантаймах.

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

PDF-операции как шаги async-пайплайна

HTTP-обработчик принимает запрос на генерацию или извлечение, возвращает 202 с ID задачи и запускает шаг пайплайна. Шаг выполняет тяжёлую PDF-операцию с собственным бюджетом памяти, ретраится при сбое и сохраняет результат в объектное хранилище.

Бинарные PDF сохраняются в объектное хранилище (S3-совместимое), а клиент получает pre-signed URL — вместо большого base64-тела через шлюз.

Паттерны serverless-обработки PDF

Генерация PDF из шаблона

Отрендерите HTML-шаблон с данными, сконвертируйте в PDF через puppeteer или PDF-библиотеку, сохраните в объектное хранилище, верните pre-signed URL.

Извлечение текста из PDF

Извлекайте текст и структурированные данные из загруженных PDF — счетов, контрактов, форм. Возвращайте структурированный JSON для дальнейшей обработки.

Пакетная генерация PDF

Генерируйте N PDF в параллельных шагах пайплайна (fan-out), затем объединяйте или упаковывайте в zip в финальном шаге (fan-in). 200 счетов — за минуты, а не часы.

OCR для PDF с изображениями

Шаг пайплайна вызывает OCR-сервис или локальную библиотеку (tesseract в Python) для извлечения текста из сканов — долгая операция с ретраями при сбое.

Поток async-генерации PDF

1

HTTP-обработчик возвращает 202 + ID задачи

Проверьте запрос, сохраните запись задачи со status=pending, запустите пайплайн с данными и ID задачи.

2

Шаг пайплайна генерирует PDF

Отрендерите шаблон с данными, сгенерируйте PDF, загрузите в объектное хранилище, запишите URL файла в запись задачи.

3

Уведомить клиента

Верните pre-signed URL для скачивания через вебхук, письмо или опрос эндпоинта статуса.

Пайплайн генерации PDF-счёта

HTTP-обработчик ставит работу через global.durable.startNew, который запускает рендерер как durable-инстанс; эта функция рендерит HTML в PDF, загружает его в объектное хранилище и обновляет запись задачи. Инстанс переживает рестарты и адресуется по id для опроса статуса.

api/generate-invoice.mjs (HTTP-обработчик)
export async function handler(event) {
  const { invoiceId, customerId } = JSON.parse(event.body || '{}');
  if (!invoiceId) return { statusCode: 400, body: JSON.stringify({ error: 'invoiceId required' }) };
  const existing = await db.invoicePdfs.find(invoiceId);
  if (existing?.url) return { statusCode: 200, body: JSON.stringify({ url: existing.url }) };
  await global.durable.startNew('render-invoice-pdf', undefined, { invoiceId, customerId });
  return { statusCode: 202, body: JSON.stringify({ invoiceId, status: 'generating' }) };
}
jobs/render-invoice-pdf.mjs (durable-функция)
import puppeteer from 'puppeteer-core';

export async function handler(event) {
  const { invoiceId, customerId } = event; // the input passed to startNew()
  const invoice = await db.invoices.findById(invoiceId);
  const html = renderInvoiceTemplate(invoice);
  const browser = await puppeteer.launch({ executablePath: '/usr/bin/chromium' });
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });
  const pdfBytes = await page.pdf({ format: 'A4', printBackground: true });
  await browser.close();
  const url = await storage.upload(pdfBytes, `invoices/${invoiceId}.pdf`);
  await db.invoicePdfs.upsert({ invoiceId, url, generatedAt: new Date() });
  return { invoiceId, url };
}

Используйте serverless-обработку PDF для

Когда это уместно

  • Генерация счетов и отчётов, которая занимает больше секунды
  • Извлечение данных из документов и OCR в фоновых пайплайнах
  • Пакетная генерация PDF (N счетов, N отчётов) с параллельным fan-out

Когда лучше выбрать другое

  • Простые одностраничные PDF, которые стабильно генерируются быстрее 1 секунды, — оставьте их синхронными, так проще

Частые вопросы

Как отдавать большие бинарные PDF?

Сохраняйте PDF в объектное хранилище (S3-совместимое) и возвращайте pre-signed URL для скачивания. Не передавайте большие base64-PDF в теле HTTP-ответа — ссылки на хранилище берегут и лимиты шлюза, и память клиента.

Можно ли использовать Python-библиотеки вроде reportlab или pdfplumber?

Да — Python 3.12 поддерживает reportlab, pdfplumber, PyMuPDF и другие PDF-библиотеки. Задеплойте Python-функцию для извлечения и трансформации рядом с Node.js-функциями для HTTP-обработчиков в том же воркспейсе.