Работа с 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 немного сбивает с толку. Требуется время, чтобы понять, что к чему и какие проекты постоянно поддерживаются. Наше исследование позволило отобрать тех кандидатов, которые соответствуют современным требованиям:

  1. PyPDF2 — библиотека для извлечения информации и содержимого документов, постраничного разделения документов, объединения документов, обрезки страниц и добавления водяных знаков. PyPDF2 поддерживает как незашифрованные, так и зашифрованные документы.
  2. PDFMiner — полностью написан на Python и хорошо работает для Python 2.4. Для Python 3 используйте клонированный пакет PDFMiner.six. Оба пакета позволяют анализировать и преобразовывать PDF‑документы. Это включает в себя поддержку PDF 1.7, а также языков CJK (китайский, японский и корейский) и различные типы шрифтов (Type1, TrueType, Type3 и CID).
  3. PDFQuery — позиционируется как «быстрая и удобная библиотека чистого PDF» и реализована как оболочка для PDFMiner, lxml и pyquery. Основная идея заключается в том, чтобы «надежно извлекать данные из наборов PDF‑файлов, используя как можно меньше кода».
  4. tabula-py — простая оболочка Python для tabula-java, которая может читать таблицы из PDF‑файлов и преобразовывать их во фреймы (DataFrames) Pandas. Позволяет конвертировать файл PDF в файлы форматов CSV/TSV/JSON.
  5. pdflib — расширение библиотеки Poppler, которое позволяет анализировать и конвертировать PDF‑документы. Не следует его путать с коммерческим клоном с таким же именем.
  6. PyFPDF — библиотека для создания документов PDF под Python. Портировано из библиотеки FPDF (PHP), известной замены PDFlib-расширения со множеством примеров, сценариев и производных.
  7. PDFTables — коммерческий сервис, предлагающий извлечение данных из таблиц документов PDF. Предлагает API, позволяющий использовать PDFTables в качестве SAAS.
  8. PyX — графический пакет Python для создания файлов PostScript, PDF и SVG. Он сочетает в себе абстракцию модели чертежа PostScript с интерфейсом TeX/LaTeX. Сложные задачи, такие как создание 2D и 3D графиков в готовом для публикации качестве, построены из этих примитивов.
  9. ReportLab — амбициозная промышленная библиотека, в основном ориентированная на оздание высококачественных PDF‑документов. Доступны как свободная версия с открытым исходным кодом, так и коммерческая, улучшенная, версия ReportLab PLUS.
  10. PyMuPDF (он же «fitz») — привязка Python для MuPDF, который является облегченным средством просмотра PDF и XPS. Библиотека может получать доступ к файлам в форматах PDF, XPS, OpenXPS, epub в комиксах и художественных книгах, а также известна своей высокой производительностью и высоким качеством рендеринга.
  11. pdfrw — чистый анализатор PDF на основе Python для чтения и записи PDF. Он точно воспроизводит векторные форматы без растеризации. Вместе с ReportLab он помогает повторно использовать части существующих PDF‑файлов в новых PDF‑файлах, созданных с помощью ReportLab.

В своём исследовании мы учитывали мнения Github-сообщества, а именно:

  • Звёзды Github: общее количество звезд проекта, выставленных пользователям.
  • Релизы Github: количество релизов каждого проекта, что отражает активность работы над проектом и его зрелость.
  • Fork-и Github: количество, сделанных копий каждого проекта, что показывает популярность использования проекта в собственных работах.
Библиотека Использование  Github Releases
Github
 Github
PyPDF2 Чтение 2 972 10 751
PyMuPDF Чтение 474 59 111
pdflib Чтение 20 4 0
PDFTables Чтение 85 0 69
tabula-py Чтение 971 23 200
PDFMiner.six Чтение 1 599 11 1 400
PDFQuery Чтение 477 1 70
pdfrw Чтение, Запись/Создание 1 145 4 187
Reportlab Запись/Создание 31 48 22
PyX Запись/Создание 23 26 7
PyFPDF Запись/Создание 457 7 174

Читать это руководство, не прорабатывая приведённые в нём примеры, бессмысленно. Поэтому, вооружимся IDLE Python и воспользуемся менеджером пакетов pip или pip3 для установки PyPDF2 и PyMuPDF. Наберём в командной строке (Windows):

pip3 install pypdf2
pip3 install pymupdf

Для того, что бы не запутаться создадим папочку для своего проекта. Как видите местом для неё выбрана папка «Документы» стандартной установки Windows.

Проект в Windows
Вот так это выглядит в 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
Извлечение текста с помощью PyPDF2

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

Извлечение текста с помощью PyMuPDF

Перейдём к PyMuPDF.

Отображение информации о документе, печать количества страниц и извлечение текста из документа PDF выполняется аналогично PyPDF2 (см. скрипт ниже). Импортируемый модуль имеет имя fitz, что соответствует имени PyMuPDF в ранних версиях.

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)

for current_page in range(len(doc)):
    page = doc.loadPage(current_page)
    page_text = page.getText("text")
    print("Стр. ", current_page+1, "\n\nСодержание;\n")
    print(page_text)

Извлечение текста с помощью PyMuPDF
Извлечение текста с помощью 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))
Результаты поиска COMPUTER VISION
Результаты поиска COMPUTER VISION

Методы, показанные здесь, довольно мощные. Сравнительно небольшое количество строк кода позволяет легко получить результат. Другие варианты применения рассматриваются во второй части, посвященной добавлению водяного знака и картинок в PDF.

Продолжение цикла статей-конспектов на сайте «Python — курс молодого бойцв»

Источники вдохновения:

CC BY-NC 4.0 Работа с PDF-файлами в Python (часть I): чтение и разбор, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

Добавить комментарий