Как генерировать PDF-документы постранично в фоновых задачах в App Engine

Мне нужно создать более 100 страниц PDF-документов. Этот процесс требует обработки большого количества данных, а генерация «все сразу» требует больше времени и памяти, чем я могу выделить.

Однако я пробовал несколько разных способов взломать свой путь:

  • xhtml2pdf с созданием и преобразованием HTML
  • rportlab для создания некоторых страниц и
  • pyPdf для объединения

С разным результатом я заработал, но он медленный и занимает больше памяти, чем должен (иногда достигая предела мягкой памяти экземпляра). В настоящее время я создаю несколько разделов в разных задачах, сохраняя каждый в blobstore, и объединяю их с pyPdf, но он задыхается от больших документов.

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

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

Я думал о постраничном создании с помощью api файлов blobstore, где каждая задача будет генерировать одну страницу, а последняя задача будет завершать файл blobstore, делая его доступным для чтения. Но я не могу найти, как приостановить создание, сохранить частичный PDF-файл в поток и возобновить создание с этим потоком для создания следующей страницы в другой задаче.

Итак, мой вопрос:

Как в GAE создать PDF-документ объемом более нескольких страниц, разделив генерацию между запросами задач, а затем сохранить полученный документ в хранилище больших двоичных объектов?

Если разделение генерации невозможно с помощью reportlab, то как минимизировать объем объединения различных PDF-документов, чтобы он соответствовал ограничениям, установленным запросом задачи GAE?

ОБНОВЛЕНИЕ: альтернативы Conversion API приветствуются.

Второе ОБНОВЛЕНИЕ Conversion API выводится из эксплуатации, поэтому сейчас это не вариант.

3-е ОБНОВЛЕНИЕ Могут ли здесь помочь API Pileline или MapReduce?


person Janusz Skonieczny    schedule 22.04.2012    source источник
comment
Есть ли у вас способ разбить исходные данные на фрагменты размером со страницу без фактического преобразования? Если это так, вы можете написать функцию, которая использует replortlab для создания отдельных страниц и использовать multiprocessing.Pool.map для параллельного запуска списка фрагментов всех ваших входных данных. В качестве последнего шага используйте pyPdf, чтобы объединить страницы в один документ.   -  person Roland Smith    schedule 22.04.2012
comment
Я уже делаю что-то подобное без особого успеха, слияние занимает много времени во время задачи (должно быть менее 10 минут) для некоторых документов. Может быть, я делаю это неправильно, в любом случае мне нужен пример кода, как это сделать правильно.   -  person Janusz Skonieczny    schedule 22.04.2012
comment
Быстрый набор больших документов звучит как работа для TeX (возможно, с пакетами макросов LaTeX или conTeXt). Однако вам придется запускать это на отдельном сервере.   -  person Roland Smith    schedule 22.04.2012
comment
Спасибо, но нет. Если об использовании фоновых задач не может быть и речи, то я мог бы использовать серверный экземпляр GAE, и с небольшими усилиями или вообще без усилий мой код на основе reportlab будет работать. Но эти документы не такие уж большие, а PDF на самом деле представляет собой поток команд, IMO должна быть возможность приостанавливать-возобновлять работу на основе задач и файлового потока blobstore в качестве хранилища.   -  person Janusz Skonieczny    schedule 23.04.2012
comment
Используете ли вы Platypus с ReportLab? Platypus использует много памяти по необходимости, потому что он строит документ из множества объектов. ReportLab использует экспоненциально меньше памяти, если вы не используете Platypus (но ценой простоты).   -  person G Gordon Worley III    schedule 23.04.2012
comment
Делаю но только на отдельных страницах и там где проблем с их генерацией нет, а вот со слиянием по pyPdf у меня проблемы. Вот почему я думал об оптимизации генерации постранично, о том, как каждая задача будет генерировать 1 страницу, приостанавливать, сохранять неполный PDF-файл в хранилище BLOB-объектов, а затем ставить новую задачу в очередь для следующей страницы, где генерация будет возобновлена.   -  person Janusz Skonieczny    schedule 23.04.2012


Ответы (2)


Взгляните на новый Conversion API: https://developers.google.com/appengine/docs/python/conversion/overview

person Peter Knego    schedule 22.04.2012
comment
Существует ограничение в 60 с. Насколько большие документы вы сгенерировали с помощью этого? Я думаю, что человек до меня пробовал это и не получил хороших результатов. Этот API довольно простой: как, например, вставить разрывы страниц? - person Janusz Skonieczny; 23.04.2012
comment
Если вы запускаете его в очереди задач, то ограничение составляет 10 минут: developers.google.com/appengine/articles. /отложено - person Peter Knego; 23.04.2012
comment
Вы говорите, что максимальный срок 60 секунд, установленный для запроса на конверсию, отменяется, если из задачи используется API конверсии? - person Janusz Skonieczny; 23.04.2012
comment
@WooYek Почему вы думаете, что ваш запрос на преобразование займет более 60 секунд? - person Nick Johnson; 23.04.2012
comment
Сноска Остерегайтесь ограничения по времени 60-х ;) Но, тем не менее, я попробую. Я поддерживаю чужой код и надеялся, что капитальный ремонт — и удаление всего кода, основанного на reportlab — не понадобится :/ - person Janusz Skonieczny; 23.04.2012
comment
Conversion API выводится из эксплуатации. - person Janusz Skonieczny; 21.08.2012

Я предлагаю установить wkhtmltopdf на движке приложения. Wkhtmltopdf — это инструмент командной строки для преобразования html в pdf.

Создайте файлы html, а затем преобразуйте их в pdf один за другим с помощью wkhtmltopdf.

В Windows вы можете использовать (в системах на базе Linux это что-то похожее):

def create_pdf(in_html_file=None, out_pdf_file=None, quality=None):
    pathtowk = 'C:/wkhtmltopdf/bin/wkhtmltopdf.exe {0} {1} {2}'    

    if quality == 1: # super quality no compression
        args_str = '--encoding utf-8 --disable-smart-shrinking --no-pdf-compression --page-size A4 --zoom 1 -q -T 15.24mm -L 25.4mm -B 20.32mm  -R 33.02mm'
    elif quality == 2: # moderate quality some compression
        args_str = '--encoding utf-8 --disable-smart-shrinking --page-size A4 --zoom 1 -q -T 15.24mm -L 25.4mm -B 20.32mm  -R 33.02mm'
    else: # poor quality max compression
        args_str = '--encoding utf-8 --page-size A4 --zoom 1 -q -T 15.24mm -L 25.4mm -B 20.32mm  -R 33.02mm'

    os.system(pathtowk.format(args_str, in_html_file, out_pdf_file))

В качестве альтернативы вы можете использовать subprocess.call(pathtowk.format(args_str, in_html_file, out_pdf_file)) для выполнения wkhtmltopdf (на мой взгляд, это лучше).

Когда процесс преобразования будет завершен, используйте PyPdf2 для объединения сгенерированных PDF-файлов в один файл.

person Community    schedule 08.05.2015