CoderCastrov logo
CoderCastrov
API

Используйте Puppeteer и Nodejs для создания скриншотов и PDF-файлов - как сервис

Используйте Puppeteer и Nodejs для создания скриншотов и PDF-файлов - как сервис
просмотров
4 мин чтение
#API

Вы когда-нибудь задумывались о простом решении для создания скриншотов программным способом?

Как разработчик, я хотел создать простой сервис, который позволяет создавать динамические веб-сайты и легко делиться ими с помощью скриншотов и PDF-файлов. Я мог бы создать простое приложение NodeJS (как описано в этой статье), чтобы решить проблему в определенный момент, но я подумал: "Почему бы не сделать это простым сервисом?".

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

Что такое немного больше?

  • Простой и понятный API
  • Проверка параметров
  • Документация
  • Тестирование
  • Учет безопасности
  • Docker-образы
  • Простое развертывание, например, на Heroku

API

Я решил создать простой сервис NodeJS с использованием ExpressJS. Он очень простой и состоит из двух конечных точек: /api/shot и /api/pdf.

Я использовал apidoc, чтобы создать документацию встроенно в мой код, после создания документации она доступна по адресу /docs (превью).

//...
/**
 * @api {get} /pdf получить pdf страницы
 * @apiName TakeScreenshot
 * @apiGroup PDF
 * @apiVersion 1.0.0
 *
 * @apiParam {String} url ссылка на страницу
 * @apiParam {Number} [w] ширина области просмотра
 * @apiParam {Number} [h] высота области просмотра
 * @apiParam {String} [d] устройство для использования области просмотра - перезаписывает другие параметры v/h
 *
 * @apiSuccess {File} pdf сгенерированный pdf
 * @apiError {Object} Errors возвращаемые ошибки
 */
//...

✓ Документация

Помимо документации, я решил использовать ajv для проверки параметров, так как он быстрый и хорошо поддерживается. Схемы находятся в файле schema.js и очень просты.

const { devices } = require('./util');

const screenshotSchema = {
  title: 'Скриншот страницы',
  description: 'Сделать скриншот страницы',
  type: 'object',
  properties: {
    url: {
      type: 'string',
      format: 'uri',
      pattern: '^(https?|http?)://',
      minLength: 1,
      maxLength: 255,
    },
    selector: {
      type: 'string',
      minLength: 1,
      maxLength: 255,
    },
  },
  required: ['url'],
};

const pdfSchema = {
  title: 'Печать страницы в формате PDF',
  description: 'Сделать pdf страницы',
  type: 'object',
  properties: {
    url: {
      type: 'string',
      format: 'uri',
      pattern: '^(https?|http?)://',
      minLength: 1,
      maxLength: 255,
    },
    w: {
      type: 'integer',
      minimum: 1,
      maximum: 12288,
    },
    h: {
      type: 'integer',
      minimum: 1,
      maximum: 12288,
    },
    d: {
      type: 'string',
      enum: Object.keys(devices),
    },
  },
  required: ['url'],
};

module.exports = { screenshotSchema, pdfSchema };

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

//...
router.get('/shot', async (req, res) => {
  const validate = ajv.compile(screenshotSchema);
  const result = await validate(req.query);
  if (!result) {
    const errors = await parseAJVErrors(validate.errors);
    // вернуть ошибки валидации
    return res.status(400).json({ errors });
  }
  const { url, selector } = req.query;
  // использовать параметры сейчас

✓ Проверка параметров

И, наконец, мы хотим иметь простой API - всего две конечные точки, отвечающие на GET-запросы. Одна для скриншотов, доступная по адресу /api/shot, а вторая для PDF, доступная по адресу /api/pdf. Так как мы работаем с GET-запросами, мы поддерживаем только параметры запроса.

✓ Простой API

Тестирование

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

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

const test = require('ava');
const { shot: screenshot, pdf } = require('./capture');
const fs = require('fs');

test('create screenshots', async (t) => {
  const screenshotBuffer = await screenshot({ url: 'https://www.google.com' });
  t.assert(
    screenshotBuffer.toString('binary').length > 1,
    'Должен быть создан скриншот',
  );
});
test('create screenshot by selector', async (t) => {
  const screenshotSelectorBuffer = await screenshot({
    url: 'https://card-joy.web.app/v?img=c1&t=Merry%20Christmas!&p=topLeft',
    selector: '#root > div > div > div > img',
  });
  t.assert(
    screenshotSelectorBuffer.toString('binary').length > 1,
    'Должен быть создан скриншот',
  );
});
test('create pdf', async (t) => {
  const generated = await pdf({ url: 'https://www.google.com' });
  t.assert(generated.length > 0, 'Должен быть создан PDF');
});

И, наконец, я установил команду для тестирования в package.json: “test”: “ava — verbose” — используя verbose для получения дополнительной информации о каждом тестовом случае.

✓ Тестирование

Безопасность

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

Не в последнюю очередь, я добавил ограничение скорости, которое по умолчанию составляет 20 запросов в течение 15 минут на IP-адрес и хранится в памяти.

✓ Обработаны распространенные уязвимости безопасности

Развертывание

Сервис легко запустить с помощью Docker. Образ упаковывает все зависимости, включая браузер Chrome.

$ docker run -it --rm -p 3000:3000 chrkaatz/the-snap

Или, если у вас уже есть учетная запись Heroku, вы можете использовать кнопку Deploy to Heroku в проекте.

Для настройки я добавил app.json со следующим содержимым, чтобы использовать Nodejs и Buildpack, позволяющий использовать puppeteer в Heroku.

{
    "name": "The Snap",
    "description": "Простой сервис для получения скриншотов и PDF с веб-страниц",
    "repository": "https://github.com/chrkaatz/the-snap",
    "logo": "https://github.com/chrkaatz/the-snap/raw/main/logo.png",
    "keywords": ["node", "puppeteer", "screenshot", "api", "pdf"],
    "buildpacks": [
        {
            "url": "heroku/nodejs"
        },
        {
          "url": "https://buildpack-registry.s3.amazonaws.com/buildpacks/jontewks/puppeteer.tgz"
        }
    ],
    "env": {
        "HUSKY": {
            "description": "Отключение git-хуков, управляемых husky",
            "value": "0"
        }
    }
  }

✓ Развертывание

Код

Исходный код можно найти по адресу https://github.com/chrkaatz/the-snap, а рабочую версию можно проверить по адресу https://the-snap.herokuapp.com/.

Финальное замечание: Домашняя страница создана с использованием MVP.css и очень проста в использовании. Мне понадобилось всего 5 минут...