Рубрики
Без рубрики

Преобразование HTML в PDF с помощью Python, AWS LAMBDA и WHTMLTOPDF

Создание функции AWS Lambda, которая использует Python и WHHTMLTOPDF для преобразования файла HTML в PDF … Теги с Python, AWS.

Создание функции AWS Lambda, которая использует Python и WHTMLTOPDF для преобразования файла HTML в файл PDF.

Цель

Чтобы настроить легко вызвать HTML в PDF Converter в качестве функции AWS Lambda.

Обзор процесса

  1. Загрузка wkhtmltopdf. двоичный
  2. Создание AWS Lambda Layer (ы) и настройка нашей функции
  3. Написание функции aws lambda
    • Мы будем использовать Python’s подпрокат модуль позвонить в wkhtmltopdf. команда
    • Для более глубоких питонов сфокусированы, также проверьте pdfkit.

Предварительные условия

Эта статья предполагает доступ к учетной записи AWS (допустимо допустимое) и базовые знания AWS Lambda/S3 и Python.

Функциональные требования

  1. Разрешить передачу ключа файла S3 или HTML String.
  2. Верните ключ файла для сгенерированного PDF
  3. Примите небольшой набор вариантов для wkhtmltopdf команда

    • Страница полноценного человека можно найти здесь
    • Большинство тех, которые мы хотели бы в любом случае, по умолчанию (то есть --Images , - Встраиваемые-внешние ссылки , и т.д.)

Функциональность для следующих вариантов

  • --orientation <Ориентация>
  • --title
  • - маргин-дно <унижет>
  • - Маргин-левый
  • - Маргин - правильно
  • - Маргин-топ

Допущения

  1. Строка HTML или файл будут действительны и будут включать в себя необходимые теги ( , , , ). Очень важно, чтобы вы проверили достоверность этого HTML перед вызовом этой функции, если вы когда-либо используете что-то подобное в производстве. Возможно, он может быть наилучшим образом принять только ключевые ключи файла S3 вместо строки HTML, но это просто для отображения наших возможностей функций или использование в качестве внутреннего инструмента.
  2. Полезная нагрузка событий будет содержать все допустимые значения (имя S3 ведра, ключ файла, WHTMLTOPDF Варианты и т. Д.)

Примечания

Эта статья будет использовать US-East-2 Для области AWS изменение этого не должно влиять на функциональность, просто ссылки в статье.

Лучший способ сделать это через AWS Serverless Application Model (SAM) , но это более адаптировано для тех, кто ищет базовую настройку через консоль управления AWS.

Общая задача, которую я узнал, что проводится недавно, программно преобразование файла/строки HTML в встроенный и стилизованный файл PDF.

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

После некоторых исследований на сторонних библиотеках, которые могут упростить нашу цель, я решил использовать wkhtmltopdf Отказ

wkhtmltopdf Является ли инструмент командной строки с открытым исходным кодом, который позволяет легко преобразовать файл HTML в файл PDF. Это именно то, что мы ищем. Мы позвоним wkhtmltopdf Команда, используя подпрокат Библиотека Python. Для более глубокого использования Python вы можете проверить pdfkit Отказ

Давайте погрузимся в это.

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

Лямбда имеет тонну использования случаев и это то, что я лично использовал много раз, и я большой поклонник.

Для нашей цели AWS LAMBDA является мощным инструментом по следующим причинам

  • Это позволяет нам разгрузить обработку от сервера
    • Это скорее общее преимущество, мы на самом деле не будем вызывать эту функцию с запущенного сервера
    • Эти звонки также будут масштабироваться автоматически
  • Наши зависимости, специально wkhtmltopdf Двоичный, может быть хорошо обрабатывается через AWS LAMBDA SELERS.

    • Это помогает избежать борьбы с различными дистрибутивами Linux или нескольких установочных мест

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

Вопросы с загрузкой двоичного

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

Предупреждение! Версия в Debian/Ubuntu REPOS понижена функциональность (потому что она составлена без патчей WHTMLTOPDF QT), таких как добавление контуров, заголовки, нижние колонтитулы, TOC и т. Д. Чтобы использовать этот параметры, вы должны установить статический двоичный файл с сайта WKHTMLTOPDF »

Когда я впервые установил wkhtmltopdf Я не принудил предупреждение и просто провел следующее:

sudo apt-get install wkhtmltopdf

На первоначальном осмотре я не испытывал упомянутых проблем ( по крайней мере в моей местной среде ). Вопросы пришли, когда я фактически подтолкнул код, используя эту библиотеку в обстановку, и я заметил, что PDF больше не генерируют.

Я смог исправить это, установив альтернативный способ:

sudo apt-get remove --purge wkhtmltopdf
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.bionic_amd64.deb
sudo dpkg -i wkhtmltox_0.12.6-1.bionic_amd64.deb
rm wkhtmltox_0.12.6-1.bionic_amd64.deb

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

Установка этого двоичного двоина в AWS Lambda слой может помочь решить это, имея одну точку установки и управления.

wkhtmltopdf Сайт на самом деле списки с использованием этой библиотеки с AWS Lambda в качестве FAQ и дает следующий ответ на этот вопрос:

” Все файлы, необходимые для LAMBDA-слоя, упаковываются в один ZIP-архив (Amazon Linux 2/LAMBDA ZIP) »

Вы можете скачать двоичные на странице выпуска под Стабильные релизы Отказ Вы увидите запись под Amazon Linux с лямбда zip. как архитектура.

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

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

Aws лямбда слои Позвольте нам добавлять «слои» зависимостей для наших функций. Альтернатива этому на это загружает функцию лямбда в качестве пакета развертывания или использование AWS SAM (модель приложения без сервеса), но выходит из объема этого поста.

wkhtmltopdf.

Теперь, когда у нас есть загруженный ZIP-файл, давайте добавим файл как слой в Консоль управления AWS Отказ

Перейти к Слои раздел На странице AWS Lambda и нажмите Создать слой Отказ

Затем добавьте следующую конфигурацию слоя.

Обратите внимание, что мы не добавляем время выполнения, это намеренно, поскольку наш слой является двоичным.

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

Теперь мы настроены для создания нашей функции!

Перейдите к Функции страницы В пределах службы AWS Lambda и нажмите Создать функцию Отказ

Выберите Автор с нуля и добавьте следующую конфигурацию.

Вы можете игнорировать Расширенные настройки для нашего использования.

Как только функция создана, у нас есть лишь несколько дополнений на конфигурацию.

Добавление слоя в нашу функцию лямбда

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

В верхней части панели обзора функций нажмите « » Слои Кнопка прямо под именем вашей функции. Это приведет вас к разделу слоев. Теперь нажмите Добавить слой.

Нажмите на Укажите ARN и скопируйте вашу версию слоя ARN с ранее.

Причина, по которой нам нужно указать наш слой by arn, потому что мы не определяли время выполнения выше.

Важный! Если ваша функция генерирует PDF с кучей черных квадратов, это скорее всего, потому что в Lambda нет конфигурации шрифта в Lambda. Чтобы решить это, вы можете пойти в эта ссылка Что я упомянул ранее, и скопируйте один из AWS Linux Fonts ARNS для вашего региона (или построить с нуля), добавьте переменную среды в README и повторите эти шаги, чтобы добавить слой шрифта.

Добавить разрешение на доступ к ведрю S3

Одна окончательная конфигурация функций, которую нам нужно добавить, является разрешение на нашу функцию для доступа Amazon S3. Для этого перейдите на вкладку «Конфигурация» под «Обзором функций».

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

Отсюда нажмите Прикреплять политики и добавьте Amazons3fullaccess политика вроде так

Теперь, когда наша функция настроена, мы можем погрузиться в фактические требования и код!

Требования

  1. Разрешить передачу ключа файла S3 или HTML String.
  2. Верните ключ файла для сгенерированного PDF
  3. Примите небольшой набор вариантов для wkhtmltopdf команда

    • Страница полноценного человека можно найти здесь
    • Большинство тех, которые мы хотели бы в любом случае, по умолчанию (то есть --Images , - Встраиваемые-внешние ссылки , и т.д.)

Давайте позвольте пользователю пройти следующие параметры

  • --orientation <Ориентация> – Общая ориентация страницы для файла PDF.

    • Допустимые значения – Ландшафт или Портрет
  • --title – Название сгенерированного файла.
  • Поля файла
    • - маргин-дно <унижет>
    • - Маргин-левый (по умолчанию 10 мм)
    • - ГАРГИН-ПРАВА (по умолчанию 10 мм)
    • - Маргин-топ

Допущения

  1. Строка HTML или файл будут действительны и будут включать в себя необходимые теги ( , , , Несомненно
  2. Полезная нагрузка событий будет содержать все допустимые значения (имя S3 ведра, ключ файла, WHTMLTOPDF Варианты и т. Д.)

Код функции

По умолчанию вы увидите следующий обработчик.

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

Это код, который будет выполнен, когда вызывается ваша функция. Мы вернемся к этому немного.

Импорт

Во-первых, давайте пойдем вперед и импортируем все библиотеки Python, которые нам понадобятся и настроили некоторые основные инструменты, такие как S3 клиент и наше Логин Отказ

from datetime import datetime
import json
import logging
import os
import subprocess
from typing import Optional

import boto3
from botocore.exceptions import ClientError


# Set up logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Get the s3 client
s3 = boto3.client('s3')

Теперь, основываясь на наших требованиях, нам понадобится функции Helper для

  1. Загрузите HTML-файл из S3
  2. Загрузить файл на S3

Давайте начнем с тех, а затем вернемся к нашему острубеку лямбда.

Загрузка/загрузка файла

BOTO3 делает его очень легко взаимодействовать с S3. Использование BOTO3. Мы можем добавить следующие функции помощника.

def download_s3_file(bucket: str, file_key: str) -> str:
    """Downloads a file from s3 to `/tmp/[File Key]`.

    Args:
        bucket (str): Name of the bucket where the file lives.
        file_key (str): The file key of the file in the bucket.

    Returns:
        The local file name as a string.
    """
    local_filename = f'/tmp/{file_key}'
    s3.download_file(Bucket=bucket, Key=file_key, Filename=local_filename)
    logger.info('Downloaded HTML file to %s' % local_filename)

    return local_filename


def upload_file_to_s3(bucket: str, filename: str) -> Optional[str]:
    """Uploads the generated PDF to s3.

    Args:
        bucket (str): Name of the s3 bucket to upload the PDF to.
        filename (str): Location of the file to upload to s3.

    Returns:
        The file key of the file in s3 if the upload was successful.
        If the upload failed, then `None` will be returned.
    """
    file_key = None
    try:
        file_key = filename.replace('/tmp/', '')
        s3.upload_file(Filename=filename, Bucket=bucket, Key=file_key)
        logger.info('Successfully uploaded the PDF to %s as %s'
                    % (bucket, file_key))
    except ClientError as e:
        logger.error('Failed to upload file to s3.')
        logger.error(e)
        file_key = None

    return file_key

Расставание события

Одна вещь, о которой мы еще не говорили, это данные, которые нам нужно будет пройти нашу функцию.

Давайте определим нашу схему событий JSON как следующее.

{
    "bucket": " [Required]",
    "file_key": " [Required if `html_string` is not defined]",
    "html_string": " [Required if `file_key` is not defined]",
    "wkhtmltopdf_options": {
        "orientation": "<`landscape` or `portrait`> [Optional: Default is `portrait`]",
        "title": " [Optional]",
        "margin": "<margin (same="" [<top="" as="" css="" format="" of="" pdf="" the=""> <right> <bottom> <left>] (all must be included)).> [Optional]"
    }
}
</left></bottom></right></margin>

wkhtmltopdf_options является дополнительным объектом. Если включенные варианты недействительны, они не будут включены.

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

Теперь давайте вернемся к lambda_handler Функция и добавить какой-нибудь код, чтобы вытащить данные с нашего события и собрать оставшиеся кусочки на самом деле вызов wkhtmltopdf исполняемый для завершения нашей функции лямбда.

def lambda_handler(event, context):
    logger.info(event)

    # bucket is always required
    try:
        bucket = event['bucket']
    except KeyError:
        error_message = 'Missing required "bucket" parameter from request payload.'
        logger.error(error_message)
        return {
            'status': 400,
            'body': json.dumps(error_message),
        }

    # html_string and file_key are conditionally required, so let's try to get both
    try:
        file_key = event['file_key']
    except KeyError:
        file_key = None

    try:
        html_string = event['html_string']
    except KeyError:
        html_string = None

    if file_key is None and html_string is None:
        error_message = (
            'Missing both a "file_key" and "html_string" '
            'from request payload. One of these must be '
            'included.'
        )
        logger.error(error_message)
        return {
            'status': 400,
            'body': json.dumps(error_message),
        }

    # Now we can check for the option wkhtmltopdf_options and map them to values
    # Again, part of our assumptions are that these are valid
    wkhtmltopdf_options = {}
    if 'wkhtmltopdf_options' in event:
        # Margin is    
        if 'margin' in event['wkhtmltopdf_options']:
            margins = event['wkhtmltopdf_options']['margin'].split(' ')
            if len(margins) == 4:
                wkhtmltopdf_options['margin-top'] = margins[0]
                wkhtmltopdf_options['margin-right'] = margins[1]
                wkhtmltopdf_options['margin-bottom'] = margins[2]
                wkhtmltopdf_options['margin-left'] = margins[3]

        if 'orientation' in event['wkhtmltopdf_options']:
            wkhtmltopdf_options['orientation'] = 'portrait' \
                if event['wkhtmltopdf_options']['orientation'].lower() not in ['portrait', 'landscape'] \
                else event['wkhtmltopdf_options']['orientation'].lower()

        if 'title' in event['wkhtmltopdf_options']:
            wkhtmltopdf_options['title'] = event['wkhtmltopdf_options']['title']

    # If we got a file_key in the request, let's download our file
    # If not, we'll write the HTML string to a file
    if file_key is not None:
        local_filename = download_s3_file(bucket, file_key)
    else:
        timestamp = str(datetime.now()).replace('.', '').replace(' ', '_')
        local_filename = f'/tmp/{timestamp}-html-string.html'

        # Delete any existing files with that name
        try:
            os.unlink(local_filename)
        except FileNotFoundError:
            pass

        with open(local_filename, 'w') as f:
            f.write(html_string)

    # Now we can create our command string to execute and upload the result to s3
    command = 'wkhtmltopdf  --load-error-handling ignore'  # ignore unecessary errors
    for key, value in wkhtmltopdf_options.items():
        if key == 'title':
            value = f'"{value}"'
        command += ' --{0} {1}'.format(key, value)
    command += ' {0} {1}'.format(local_filename, local_filename.replace('.html', '.pdf'))

    # Important! Remember, we said that we are assuming we're accepting valid HTML
    # this should always be checked to avoid allowing any string to be executed
    # from this command. The reason we use shell=True here is because our title
    # can be multiple words.
    subprocess.run(command, shell=True)
    logger.info('Successfully generated the PDF.')
    file_key = upload_file_to_s3(bucket, local_filename.replace('.html', '.pdf'))

    if file_key is None:
        error_message = (
            'Failed to generate PDF from the given HTML file.'
            ' Please check to make sure the file is valid HTML.'
        )
        logger.error(error_message)
        return {
            'status': 400,
            'body': json.dumps(error_message),
        }

    return {
        'status': 200,
        'file_key': file_key,
    }

Теперь вы можете перейти на вкладку «Тест» и создать следующее тестовое событие (измените имя вашего ведра)

{
    "bucket": "bucket-for-articles",
    "html_string": "
This is an example of a simple HTML page.",
    "wkhtmltopdf_options": {
        "orientation": "portrait",
        "title": "Test PDF Generation",
        "margin": "10mm 10mm 10mm 10mm"
    }
}

Вы должны получить обратное событие с помощью Статус 200 и а file_key Из вашего преобразованного файла, таким образом, достижение нашей цели! 🎉

Оригинал: “https://dev.to/bschoeneweis/converting-html-to-a-pdf-using-python-aws-lambda-and-wkhtmltopdf-3mdh”