Используйте Puppeteer и Nodejs для создания скриншотов и PDF-файлов - как сервис
Вы когда-нибудь задумывались о простом решении для создания скриншотов программным способом?
Как разработчик, я хотел создать простой сервис, который позволяет создавать динамические веб-сайты и легко делиться ими с помощью скриншотов и 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 минут...