Ich sage euch: man muß noch Chaos in sich haben, um einen tanzenden Stern gebären zu können (“Also sprach Zarathustra”, Friedrich Nietzsche)
Работа с PDF-файлами в Python (часть I): чтение и разбор
В эпоху бурной цифровизации Portable Document Format (PDF) — межплатформенный открытый формат электронных документов, изначально разработанный компанией Adobe Systemsએ с использованием ряда возможностей языка PostScriptએ, стал особенно популярным. В первую очередь, он предназначен для представления в электронном виде полиграфической продукции. C 1 июля 2008 года PDF стал открытым стандартом ISO 32000. Последняя версия ISO 32000‑2: 2017 впитала в себя весь опыт использования стандарта и отражает его эволюцию. Сегодня PDF, де‑факто, стал стандартном обмена информации и для многих приложений стал бесценным источником контента. Но работать с таким контентом сложно.
Обработка PDF документов
В Linux для работы с файлами PDF всё проще, есть мощные инструменты командной строки, такие как pdftk и pdfgrep. Но, поскольку, PDF — межплатформенный открытый формат электронных документов, хотелось бы с таким же удобством работать и в Windows, и в macOS. При этого нужна фантазия и усилия разработчика. Вот и совсем недавно встретилась задачка переноса информации из PDF‑файла в базу данных. Естественно, задача автоматизации здесь имеет свою уникальную специфику и без разработки здесь ну совсем никак.
Это руководство — начало небольшой серии, где будут рассмотрены полезные для разработчика библиотеки, позволяющие создавать собственные скрипты Python для решения рутинных задач автоматизации. В первой части внимание сконцентрировано на манипулировании существующими PDF‑файлами. Вы узнаете, как читать и извлекать содержимое (текст и изображения) и разбивать документы на отдельные страницы. Вторая часть будет посвящена наложению водяных знаков в документ. Третья часть посвящена исключительно написанию/созданию PDF‑файлов, а также удалению и повторному объединению отдельных страниц в новый документ.
Инструменты и библиотеки
Спектр доступных решений для связанных с Python инструментов, модулей и библиотек PDF немного сбивает с толку. Требуется время, чтобы понять, что к чему и какие проекты постоянно поддерживаются. Наше исследование позволило отобрать тех кандидатов, которые соответствуют современным требованиям:
PyPDF2 — библиотека для извлечения информации и содержимого документов, постраничного разделения документов, объединения документов, обрезки страниц и добавления водяных знаков. PyPDF2 поддерживает как незашифрованные, так и зашифрованные документы.
PDFMiner — полностью написан на Python и хорошо работает для Python 2.4. Для Python 3 используйте клонированный пакет PDFMiner.six. Оба пакета позволяют анализировать и преобразовывать PDF‑документы. Это включает в себя поддержку PDF 1.7, а также языков CJKએ (китайский, японский и корейский) и различные типы шрифтов (Type1, TrueTypeએ, Type3 и CID).
PDFQuery — позиционируется как «быстрая и удобная библиотека чистого PDF» и реализована как оболочка для PDFMiner, lxml и pyquery. Основная идея заключается в том, чтобы «надежно извлекать данные из наборов PDF‑файлов, используя как можно меньше кода».
tabula-py — простая оболочка Python для tabula-java, которая может читать таблицы из PDF‑файлов и преобразовывать их во фреймы (DataFrames) Pandas. Позволяет конвертировать файл PDF в файлы форматов CSVએ/TSVએ/JSONએ.
pdflib — расширение библиотеки Poppler, которое позволяет анализировать и конвертировать PDF‑документы. Не следует его путать с коммерческим клоном с таким же именем.
PyFPDF — библиотека для создания документов PDF под Python. Портировано из библиотеки FPDF (PHPએ), известной замены PDFlib-расширения со множеством примеров, сценариев и производных.
PDFTables — коммерческий сервис, предлагающий извлечение данных из таблиц документов PDF. Предлагает API, позволяющий использовать PDFTables в качестве SAASએ.
PyX — графический пакет Python для создания файлов PostScript, PDF и SVGએ. Он сочетает в себе абстракцию модели чертежа PostScript с интерфейсом TeXએ/LaTeXએ. Сложные задачи, такие как создание 2D и 3D графиков в готовом для публикации качестве, построены из этих примитивов.
ReportLab — амбициозная промышленная библиотека, в основном ориентированная на оздание высококачественных PDF‑документов. Доступны как свободная версия с открытым исходным кодом, так и коммерческая, улучшенная, версия ReportLab PLUS.
PyMuPDF (он же «fitz») — привязка Python для MuPDF, который является облегченным средством просмотра PDF и XPS. Библиотека может получать доступ к файлам в форматах PDF, XPSએ, OpenXPS, epubએ в комиксах и художественных книгах, а также известна своей высокой производительностью и высоким качеством рендеринга.
pdfrw — чистый анализатор PDF на основе Python для чтения и записи PDF. Он точно воспроизводит векторные форматы без растеризации. Вместе с ReportLab он помогает повторно использовать части существующих PDF‑файлов в новых PDF‑файлах, созданных с помощью ReportLab.
В своём исследовании мы учитывали мнения Github-сообщества, а именно:
Звёзды Github: общее количество звезд проекта, выставленных пользователям.
Релизы Github: количество релизов каждого проекта, что отражает активность работы над проектом и его зрелость.
Fork-и Github: количество, сделанных копий каждого проекта, что показывает популярность использования проекта в собственных работах.
Читать это руководство, не прорабатывая приведённые в нём примеры, бессмысленно. Поэтому, вооружимся IDLE Python и воспользуемся менеджером пакетов pip или pip3 для установки PyPDF2 и PyMuPDF. Наберём в командной строке (Windows):
pip3 install pypdf2
pip3 install pymupdf
Для того, что бы не запутаться создадим папочку для своего проекта. Как видите местом для неё выбрана папка «Документы» стандартной установки Windows.
Папки images и dist будем использовать для записи результатов работы своих программ, а в папке source храним исходные PDF‑файлы, сами скрипты будем хранить в корне. Кстати, все примеры этой серии статей о работе с PDF‑файлами есть на Github, откуда их можно забрать и использовать в качестве «кирпича» для своих упражнений
Извлечение текста с помощью PyPDF2
Начнём с PyPDF2. Ниже приведен скрипт, который позволяет извлечь из PDF‑файла текст и вывести него в консоль.
Сначала импортируем PdfFileReader, помня о том, что пакет уже установлен. Задаём имя файла из папки source (можете загрузить туда свой файл и поменять Computer-Vision-Resources.pdf в скрипте на имя загруженного файла), открывает документ и получаем информацию о документе, используя метод getDocumentInfo() и общее количество страниц getNumPages(). Далее в цикле for читаем каждую страницу, получаем содержимое page.extractText() и печатаем в stdout.
Обратите внимание, что PyPDF2 начинает считать страницы с 0, и поэтому вызов pdf.getPage(i) при i = 0 извлекает первую страницу документа.
from PyPDF2 import PdfFileReader
pdf_document = "source/Computer-Vision-Resources.pdf"
with open(pdf_document, "rb") as filehandle:
pdf = PdfFileReader(filehandle)
info = pdf.getDocumentInfo()
pages = pdf.getNumPages()
print("Количество страниц в документе: %i\n\n" % pages)
print("Мета-описание: ", info)
for i in range(pages):
page = pdf.getPage(i)
print("Стр.", i, " мета: ", page, "\n\nСодержание;\n")
print(page.extractText())
Как видите, извлеченный текст печатается сплошным потоком. Здесь нет ни абзацев, ни разделений предложений. Как указано в документации по PyPDF2, все текстовые данные возвращаются в том порядке, в котором они представлены на странице. В основном, это зависит от внутренней структуры документа PDF и от того, как поток инструкций, создан во время его записи, поэтому их использование может привести к неожиданностям, надо дополнительно «парсить», не очень удобно.
Извлечение текста с помощью PyMuPDF
Перейдём к PyMuPDF.
Отображение информации о документе, печать количества страниц и извлечение текста из документа PDF выполняется аналогично PyPDF2 (см. скрипт ниже). Импортируемый модуль имеет имя fitz, что соответствует имени PyMuPDF в ранних версиях.
Приятной особенностью PyMuPDF является то, что он сохраняет исходную структуру документа без изменений — целые абзацы с разрывами строк сохраняются такими же, как в PDF‑документе.
Извлечение изображений из PDF с помощью PyMuPDF
Переходим к изображениям. PyMuPDF упрощает извлечение изображений из документов PDF с использованием метода getPageImageList(). Скрипт, приведённый ниже, основан на примере из вики-страницы PyMuPDF и извлекает и постранично сохраняет все изображения из PDF в формате PNG. Если изображение имеет цветовое пространство CMYK, оно будет сначала преобразовано в RGB.
import fitz
pdf_document = "source/Computer-Vision-Resources.pdf"
doc = fitz.open(pdf_document)
print("Исходный документ", doc)
print("\nКоличество страниц: %i\n\n------------------\n\n" % doc.pageCount)
print(doc.metadata)
page_count = 0
for i in range(len(doc)):
for img in doc.getPageImageList(i):
xref = img[0]
pix = fitz.Pixmap(doc, xref)
pix1 = fitz.Pixmap(fitz.csRGB, pix)
page_count += 1
pix1.writePNG("images/picture_number_%s_from_page_%s.png" % (page_count, i+1))
print("Image number ", page_count, " writed...")
pix1 = None
Этот скрипт Python извлёк 773 изображения на 400-страничном PDF, размером полгигабайта менее чем за 3 минуты, что удивительно. Отдельные изображения хранятся в формате PNG. Чтобы сохранить исходный формат и размер изображения вместо преобразования в PNG, взгляните на расширенные версии сценариев в вики PyMuPDF.
Разделение PDF‑файлов на страницы с помощью PyPDF2
Для этого примера, в первую очередь необходимо импортировать классы PdfFileReader и PdfFileWriter. Затем мы открываем файл PDF, создаем объект для чтения и перебираем все страницы, используя метод объекта для чтения getNumPages.
Внутри цикла for мы создаем новый экземпляр PdfFileWriter, который еще не содержит страниц. Затем мы добавляем текущую страницу к нашему объекту записи, используя метод pdfWriter.addPage(). Этот метод принимает объект страницы, который мы получаем, используя метод PdfFileReader.getPage().
Следующим шагом является создание уникального имени файла, что мы делаем, используя исходное имя файла плюс слово «page» плюс номер страницы. Мы добавляем 1 к текущему номеру страницы, потому что PyPDF2 считает номера страниц, начиная с нуля.
Наконец, мы открываем новое имя файла в режиме (режиме wb) записи двоичного файла и используем метод write() класса pdfWriter для сохранения извлеченной страницы на диск.
Листинг 4: Разделение PDF на отдельные страницы.
from PyPDF2 import PdfFileReader, PdfFileWriter
pdf_document = "source/Computer-Vision-Resources.pdf"
pdf = PdfFileReader(pdf_document)
for page in range(pdf.getNumPages()):
pdf_writer = PdfFileWriter()
current_page = pdf.getPage(page)
pdf_writer.addPage(current_page)
outputFilename = "dist/Computer-Vision-Resources-page-{}.pdf".format(page + 1)
with open(outputFilename, "wb") as out:
pdf_writer.write(out)
print("created", outputFilename)
Найти все страницы, где есть заданный текст
Этот скрипт довольно практичен и работает аналогично pdfgrep. Используя PyMuPDF, скрипт возвращает все номера страниц, которые содержат заданную строку поиска. Страницы загружаются одна за другой и с помощью метода searchFor() обнаруживаются все вхождения строки поиска. В случае совпадения соответствующее сообщение печатается на stdout:
import fitz
filename = "source/Computer-Vision-Resources.pdf"
search_term = "COMPUTER VISION"
pdf_document = fitz.open(filename)
for current_page in range(len(pdf_document)):
page = pdf_document.loadPage(current_page)
if page.searchFor(search_term):
print("%s найдено на странице %i" % (search_term, current_page+1))
Методы, показанные здесь, довольно мощные. Сравнительно небольшое количество строк кода позволяет легко получить результат. Другие варианты применения рассматриваются во второй части, посвященной добавлению водяного знака и картинок в PDF.