[Python, HTML, PDF] HTML ⟹ PDF @ Python
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Наверняка не очень редко возникает задача печати HTML-документов с какого-то сервера в точности как задумано автором этого сервера. Делать это лучше всего не в надежде на браузер клиента, а на стороне сервера. А если на сервере крутится нечто на питоне (Django/Flask/тысячи их), то хорошо бы оценить во что это обойдется.Для тестов выбирались такие библиотеки, чтобы как минимум были в виде пакетов в официальных репо RH-based дистрибутивов или же - в крайнем случае - можно было таковые собрать. И чтобы без долгих танцев с бубном.
В macOS всё ставилось с помощью homebrew и pip3, в Fedora - из стандартного репо (искл. xhtml2pdf - этого в репах нет, но при должной усидчивости за пару часов можно собрать вполне себе цивильный rpm).Дано: После тщательного отбора кандидатов накопилось аж 3:
- python-pdfkit - адаптер к вызову бинарника wkhtmltopdf.
- weasyprint - прокладка между html5lib и reportlab.
- xhtml2pdf - примерно то же самое, что и weasyprint, но со своими тараканами особенностями. В таблице указано как "Pisa" (основной модуль).
Платформ для тестирования набралось под руками тоже 3 (все x64):
- MacBook - Apple MacBookPro9.2 (13" mid 2012, i5-3210M (2.5GHz)), HDD, macOS 10.15 "Catalina", Python 3.9 (brew)
- LinBook (так это назовем) - тот же самый макбук, но с Fedora 33, Python 3.9
- DeskTop - Intel G3450 (3.4GHz), HDD, Fedora 33, Python 3.9
Документов для тестов - 3 (все - на одну страничку каждый):
- ПД4 - квитанция на оплату налогов и сборов в Сбер (форма ПД-4сб). HTML ручной работы, максимально соответствующий стандартам. Требования к точности передачи задумки автора в печати довольно высокие.
- Инструкция - чей-то документ с заголовком, комментариями, табличками и местом для подписи. Получен из .doc экспортом из Word 2007. HTML не так, чтобы очень тяжелый, но на тяп-ляп. То есть как оно и будет в жизни. Требования к точности - никакие.
- Р21001 - последний листик (стр.5Б) формы Р21001 - с якорями для сканера, буквами в квадратиках и всем остальным, что мы так любим в документах для налоговой. Экспорт из Excel 2007, IE6-совместимо. Получилось 2 МБ формально правильного HTML, но совершенно фееричной разметки, то есть достаточно тяжелого для парсера-генератора. Требования к точности очень высокие.
Решение:Код на коленке
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Benchmark of html-to-pdf converters.
(c) @justhabrauser, GPLv3.
"""
# 1. system
import os
import sys
from time import time
# 2. 3rd
from pdfkit import from_string # https://github.com/JazzCore/python-pdfkit
from weasyprint import HTML # https://github.com/Kozea/WeasyPrint
from xhtml2pdf.pisa import CreatePDF # https://github.com/xhtml2pdf/xhtml2pdf
def __pdfkit(html: str) -> bytes:
return from_string(html, False, options={'quiet': ''})
def __weasy(html: str) -> bytes:
return HTML(string=html).write_pdf()
def __pisa(html: str) -> bytes:
pdf = CreatePDF(html)
if not pdf.err:
pdf.dest.seek(0)
return pdf.dest.read()
def main(indir: str, outdir: str, count: int) -> None:
# 1. Load all htmls
modules = (__pdfkit, __weasy, __pisa)
html_list = list() # (filename, content)[]
dir_list = os.listdir(indir)
dir_list.sort()
for i, fn in enumerate(dir_list):
fpath = os.path.join(indir, fn)
if os.path.isfile(fpath) and fpath.endswith(".html"):
print("Load '{}'".format(fn), file=sys.stderr)
with open(fpath, "rt") as i_f:
html = i_f.read()
html_list.append(html)
# 2. write results (and warm up)
for j, m in enumerate(modules):
with open(os.path.join(outdir, "%d_%d.pdf" % (i, j)), "wb") as o_f:
o_f.write(m(html))
# 2. for C times x I pages x J engines:
print("Count\tPage\tEngine\tTime\n=====\t====\t======\t====")
for c in range(count): # count
for i, h in enumerate(html_list): # html page
for j, m in enumerate(modules): # engine
t0 = time()
m(h)
t1 = time()
print("{}\t{}\t{}\t{}".format(c, i, j, t1-t0))
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage: {} <dir_with_htmls> <output_dir_for_pdfs>".format(sys.argv[0]), file=sys.stderr)
elif not os.path.isdir(sys.argv[1]):
print("Input '{}' is not dir or not exists.".format(sys.argv[1]), file=sys.stderr)
elif not os.path.isdir(sys.argv[2]):
print("Output '{}' is not dir or not exists.".format(sys.argv[2]), file=sys.stderr)
else:
main(sys.argv[1], sys.argv[2], 5)
Среднее время обработки каждого документа (в разрезе документов, библиотек и платформ (D=DeskTop, L=LinBook, M=MacBook)), сек.:LibПД4СметаР21001DLMDLMDLMPdfkit0,360,441,490,360,441,231,31,96,9Weasy0,360,470,600,270,360,6526,134,854,7Pisa0,120,170,280,290,410,6820,427,342,2Выводы: таракан без ног не слышит ©Общий вывод - счастья нет. То есть я не смог ни одного кандидата однозначно выгнать на мороз или наградить золотой медалью. В среднем по больнице видно, что pdfkit дольше запрягает, но потом быстрее едет, но это и без тестов логично (хотя разница все-равно впечатляет). Ну а так каждый может оценить цифры, протестировать самостоятельно и сделать свои выводы. Я могу только привести свои личные впечатления:
- pdfkit. Все-таки это не чистокровный питон и даже не обертка C-либы, что нарушает внутреннюю гармонию и бесит перфекционизм. Радует высокое качество полученного PDF, максимально точная передача задумки (реально WYSIWYG), максимальная скорость на тяжелых документах. Не радует неторопливость на мелких задачах и почти полная неуправляемость.
- weasyprint. Бедненько - но чистенько. Всеядное, приемлемая (а иногда и неплохая) скорость, достаточно предсказуемый результат. Но без наворотов и без рекордов.
- xhtml2pdf. Вредное. HTML должен не просто идеально соответствовать стандартам, он должен еще понравиться этой либе, иначе "инжалид дежице". Отдельно идут упражнения с кириллицей (кстати, я тестировал без этих упражнений (лениво), то есть не совсем корректно) и фееричность получаемого результата. За это там куча наворотов и в среднем хорошая скорость работы (как для питона).
Отдельно стоят вопросы управления разрывами страниц, нумерация страниц, хорошо бы еще попробовать iText7 (но это вязать python с java, что из категории секаса переводит вопрос в категорию прона), wkhtmltopdf-static и иные окружения. Но я хотел просто быстро оценить порядок скорости на целевой лично для меня платформе (RHEL8+).
===========
Источник:
habr.com
===========
Похожие новости:
- [Системное администрирование, Python] Как ленивый работящему помог. Ещё один скрипт для релиза подкаста
- [Мессенджеры, Python, Социальные сети и сообщества] Читаем telegram-каналы в виде новостной ленты (+ бонусом rss)
- [Python, Программирование, Управление персоналом] Шаги построения рекомендательной системы в обучении персонала
- [Python, Программирование, Анализ и проектирование систем, Интерфейсы, Go] Порт GUI фреймворка с Python на Go. Анализ граблей и плюшек
- [Ненормальное программирование, Программирование, Машинное обучение] Бонус работы аналитиком данных: Как я нашел свой новый дом в Дублине (перевод)
- [Python, Программирование] Сохранение сюжетов matplotlib в pdf файл
- [Python, Программирование, Машинное обучение] Расширяющийся нейронный газ
- [Python, PostgreSQL, SQL] Поговорим о RFM-анализе
- [Python, Читальный зал, Финансы в IT] Семейный бюджет, Google sheets и Python
- [Python, API, Программирование микроконтроллеров, Разработка для интернета вещей] Опыт написания IDL для embedded
Теги для поиска: #_python, #_html, #_pdf, #_html, #_pdf, #_python, #_python, #_html, #_pdf
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:10
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Наверняка не очень редко возникает задача печати HTML-документов с какого-то сервера в точности как задумано автором этого сервера. Делать это лучше всего не в надежде на браузер клиента, а на стороне сервера. А если на сервере крутится нечто на питоне (Django/Flask/тысячи их), то хорошо бы оценить во что это обойдется.Для тестов выбирались такие библиотеки, чтобы как минимум были в виде пакетов в официальных репо RH-based дистрибутивов или же - в крайнем случае - можно было таковые собрать. И чтобы без долгих танцев с бубном. В macOS всё ставилось с помощью homebrew и pip3, в Fedora - из стандартного репо (искл. xhtml2pdf - этого в репах нет, но при должной усидчивости за пару часов можно собрать вполне себе цивильный rpm).Дано: После тщательного отбора кандидатов накопилось аж 3:
#!/usr/bin/env python3
# -*- coding: utf-8 -*- """ Benchmark of html-to-pdf converters. (c) @justhabrauser, GPLv3. """ # 1. system import os import sys from time import time # 2. 3rd from pdfkit import from_string # https://github.com/JazzCore/python-pdfkit from weasyprint import HTML # https://github.com/Kozea/WeasyPrint from xhtml2pdf.pisa import CreatePDF # https://github.com/xhtml2pdf/xhtml2pdf def __pdfkit(html: str) -> bytes: return from_string(html, False, options={'quiet': ''}) def __weasy(html: str) -> bytes: return HTML(string=html).write_pdf() def __pisa(html: str) -> bytes: pdf = CreatePDF(html) if not pdf.err: pdf.dest.seek(0) return pdf.dest.read() def main(indir: str, outdir: str, count: int) -> None: # 1. Load all htmls modules = (__pdfkit, __weasy, __pisa) html_list = list() # (filename, content)[] dir_list = os.listdir(indir) dir_list.sort() for i, fn in enumerate(dir_list): fpath = os.path.join(indir, fn) if os.path.isfile(fpath) and fpath.endswith(".html"): print("Load '{}'".format(fn), file=sys.stderr) with open(fpath, "rt") as i_f: html = i_f.read() html_list.append(html) # 2. write results (and warm up) for j, m in enumerate(modules): with open(os.path.join(outdir, "%d_%d.pdf" % (i, j)), "wb") as o_f: o_f.write(m(html)) # 2. for C times x I pages x J engines: print("Count\tPage\tEngine\tTime\n=====\t====\t======\t====") for c in range(count): # count for i, h in enumerate(html_list): # html page for j, m in enumerate(modules): # engine t0 = time() m(h) t1 = time() print("{}\t{}\t{}\t{}".format(c, i, j, t1-t0)) if __name__ == '__main__': if len(sys.argv) != 3: print("Usage: {} <dir_with_htmls> <output_dir_for_pdfs>".format(sys.argv[0]), file=sys.stderr) elif not os.path.isdir(sys.argv[1]): print("Input '{}' is not dir or not exists.".format(sys.argv[1]), file=sys.stderr) elif not os.path.isdir(sys.argv[2]): print("Output '{}' is not dir or not exists.".format(sys.argv[2]), file=sys.stderr) else: main(sys.argv[1], sys.argv[2], 5)
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:10
Часовой пояс: UTC + 5