PyPDF

PyPDF помогает автоматизировать обработку документов: читать текст и метаданные, объединять и разделять страницы, менять их поворот и область отображения, добавлять закладки, аннотации, водяные знаки, вложения и пароли. Основная работа строится вокруг PdfReader для разбора исходного файла, PdfWriter для сборки результата и PageObject для операций с отдельными страницами, поэтому один сценарий можно применить к целой папке без повторения ручных действий.

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

Вместо меню и панелей инструментов используются вызовы Python, что особенно полезно для повторяемых задач: сборки пакета из сотен файлов, удаления пустых листов, извлечения реквизитов, заполнения однотипных форм или защиты результатов разными паролями. При этом качество операции зависит от внутреннего устройства PDF: текстовый слой извлекается напрямую, а скан без распознанного текста требует отдельного OCR; обрезка меняет видимую область страницы, но сама по себе не удаляет скрытые объекты.

Скачать PyPDF

Оценка 9.7 Рекомендуем
  • Редактирование PDF
  • Русский интерфейс
  • Просто новичкам
Скачать бесплатно на Windows
Лучшая альтернатива
PyPDF
Оценка 8.5
  • Нет графического интерфейса
  • Нет встроенного OCR
  • Не подписывает PDF
Скачать PyPDF
Загрузка начнётся после нажатия

Как устроена работа с документом

Три базовых класса задают почти весь практический маршрут. PdfReader разбирает таблицу перекрёстных ссылок, каталог документа, дерево страниц и связанные объекты. Коллекция reader.pages ведёт себя как последовательность: страницу можно получить по индексу, перебрать циклом или выбрать срезом на стороне Python. PageObject представляет одну страницу вместе с её медиабоксом, кропбоксом, ресурсами и потоком команд рисования. PdfWriter принимает страницы и объекты, формирует новое дерево и записывает итог в файл либо в любой двоичный поток с методами write и seek.

Для простой копии не требуется вручную переносить каждый словарь PDF. Метод append добавляет весь документ, а merge вставляет выбранный материал в заданную позицию. При добавлении отдельной страницы применяется add_page, при вставке внутрь уже собранной последовательности — insert_page. Номера в Python начинаются с нуля, поэтому пятая видимая страница имеет индекс 4. Эта деталь особенно важна в сценариях удаления, перестановки и назначения закладок.

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

from pathlib import Path
from pypdf import PdfReader, PdfWriter

input_path = Path("input.pdf")
output_path = Path("result.pdf")

reader = PdfReader(input_path)
writer = PdfWriter()

for page in reader.pages:
    writer.add_page(page)

with output_path.open("wb") as stream:
    writer.write(stream)

Контекстный менеджер нужен прежде всего для файловых потоков. Сам PdfReader можно создать из пути, открытого двоичного файла или BytesIO. Если передан внешний поток, закрывать его следует только после завершения всех операций чтения и клонирования. В длинных пакетных заданиях полезно освобождать ссылки на reader и writer после каждого документа, иначе крупные деревья объектов и распакованные потоки страниц дольше остаются в памяти.

Установка и подготовка окружения

Минимальная установка выполняется командой python -m pip install pypdf. Вызов через python -m pip предпочтительнее отдельной команды pip, потому что пакет попадает именно в тот интерпретатор, которым затем запускается сценарий. Поддерживается Python 3.9 и новее. Для проекта с виртуальным окружением сначала создают его командой python -m venv .venv, затем активируют и устанавливают зависимость внутри него. Так системные пакеты и инструменты разных проектов не смешиваются.

Базовый пакет умеет читать и записывать стандартные структуры PDF без обязательных тяжёлых компонентов. Дополнительные возможности подключаются по необходимости. Набор pypdf[crypto] устанавливает криптографическую зависимость для AES. Набор pypdf[image] добавляет Pillow, нужный для удобного декодирования и сохранения изображений. Вариант pypdf[full] объединяет поддерживаемые дополнительные зависимости. Для JBIG2 одного Python-пакета недостаточно: декодер этого формата должен присутствовать в системе отдельно.

python -m venv .venv
# Активируйте .venv средствами своей оболочки
python -m pip install --upgrade pip
python -m pip install "pypdf[full]"
python -c "import pypdf; print(pypdf.__version__)"

Ошибка ModuleNotFoundError: No module named 'pypdf' почти всегда означает, что установка и запуск используют разные интерпретаторы. Сравните вывод python -c "import sys; print(sys.executable)" с путём, который показывает python -m pip --version. В IDE дополнительно проверьте выбранный интерпретатор проекта. Импорт пишется строчными буквами: from pypdf import PdfReader. Имя старого пакета в коде не подходит для новых сценариев.

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

Безопасное открытие и первичная диагностика

Сразу после создания PdfReader полезно проверить len(reader.pages), reader.is_encrypted, reader.metadata и, при работе с формами, reader.get_fields(). Эти признаки позволяют рано выбрать правильную ветвь обработки. Пустой список страниц, исключение при обращении к каталогу или неожиданное шифрование следует считать причиной для остановки конкретного файла и записи диагностического сообщения, а не для продолжения пакетной сборки с частичным результатом.

Параметр strict управляет реакцией на отклонения от спецификации. В обычном режиме strict=False библиотека пытается восстановить распространённые дефекты и сообщает о них через журнал. strict=True полезен для контроля качества: несогласованные ссылки и другие нарушения чаще приводят к исключению. Для массовой обработки практична двухступенчатая схема: сначала строгая проверка, затем отдельная попытка мягкого чтения только для файлов, которые разрешено восстанавливать.

import logging
from pypdf import PdfReader
from pypdf.errors import PyPdfError

logging.basicConfig(level=logging.WARNING)
log = logging.getLogger("pdf-job")

try:
    reader = PdfReader("input.pdf", strict=True)
    page_count = len(reader.pages)
except PyPdfError as exc:
    log.error("Документ не прошёл строгую проверку: %s", exc)
    reader = PdfReader("input.pdf", strict=False)
    page_count = len(reader.pages)

При чтении недоверенных файлов ограничивайте размер входа, число страниц и время обработки на уровне приложения. Внутренние защитные пределы сокращают риск чрезмерного потребления ресурсов некоторыми объектами, но не заменяют ограничения процесса. Особенно опасны огромные распакованные потоки содержимого, вложенные структуры XMP, циклические ссылки и изображения с экстремальными размерами. Для серверной очереди разумно обрабатывать каждый файл в отдельном процессе с лимитами памяти и времени.

Проверка заголовка %PDF- недостаточна: он подтверждает только начало формата, но не корректность дерева объектов. Аналогично расширение .pdf ничего не говорит о содержимом. Надёжный конвейер сочетает ограничение размера, попытку чтения каталога, перебор страниц, выборочную распаковку потоков и последующее открытие результата независимым валидатором или просмотрщиком.

Извлечение текста и сохранение порядка чтения

Метод page.extract_text() возвращает текст, который уже присутствует в операторах страницы и связанных шрифтах. Он не анализирует пиксели. Если документ получен сканированием и содержит только изображения, результат будет пустым или почти пустым; тогда сначала требуется OCR, после которого распознанный слой можно читать обычным способом. Проверить ситуацию помогает сопоставление: страница визуально заполнена, extract_text() возвращает пустую строку, а page.images содержит крупное изображение на весь лист.

PDF хранит символы не как абзацы, а как команды рисования в координатах. Поэтому порядок в выходной строке может отличаться от визуального: колонки перемешиваются, колонтитулы попадают в середину, отдельные буквы слова записаны разными операциями. Обычный режим стремится дать связный текст, а extraction_mode="layout" сохраняет приблизительное горизонтальное расположение пробелами. Режим layout удобен для моноширинного просмотра таблиц, но не превращает их в структурированные строки и столбцы.

from pypdf import PdfReader

reader = PdfReader("report.pdf")
for number, page in enumerate(reader.pages, start=1):
    plain = page.extract_text() or ""
    layout = page.extract_text(extraction_mode="layout") or ""
    print(number, len(plain), len(layout))

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

Посетители дают более точный контроль. Функции visitor_text и visitor_operand_before вызываются во время разбора операторов, передавая текущие матрицы, шрифт и фрагмент текста. С их помощью можно отсеивать верхние и нижние поля по координате, собирать расположение слов или строить отладочную визуализацию. Координаты не всегда интуитивны: начало системы и преобразования зависят от страницы, поэтому правила следует проверять на реальном файле, особенно после поворота и вложенных трансформаций.

parts = []

def visitor(text, cm, tm, font_dict, font_size):
    y = tm[5]
    if 60 < y < 760:
        parts.append(text)

page = reader.pages[0]
page.extract_text(visitor_text=visitor)
body_text = "".join(parts)

Перед извлечением текста из необычно тяжёлой страницы оцените размер распакованного потока. Несколько сотен мегабайт команд рисования способны потребовать многие гигабайты оперативной памяти во время разбора. Получить приблизительную оценку можно через page.get_contents().get_data(), но сам этот вызов тоже распаковывает поток. Поэтому сначала контролируют размер исходного файла и объектные метаданные, а в серверной обработке задают жёсткий лимит памяти процессу.

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

Чтение и изменение метаданных

reader.metadata возвращает сведения из словаря Document Information: заголовок, автор, тема, ключевые слова, создатель и инструмент формирования. Значения могут отсутствовать, иметь нестандартную кодировку или не совпадать с тем, что показывает файловый менеджер. Даты PDF часто записываются строкой с префиксом D: и часовым поясом; для сравнения их лучше преобразовывать в объект даты, а при невозможности разбора сохранять исходное значение.

from pypdf import PdfReader, PdfWriter

reader = PdfReader("input.pdf")
metadata = reader.metadata
print(metadata.title, metadata.author)

writer = PdfWriter(clone_from=reader)
writer.add_metadata({
    "/Title": "Сводный отчёт",
    "/Author": "Отдел аналитики",
    "/Subject": "Проверенные данные",
})
writer.write("with-metadata.pdf")

Метод add_metadata добавляет или заменяет переданные ключи, не требуя пересобирать страницы вручную. Имена стандартных полей начинаются с косой черты. Пользовательские ключи тоже возможны, но сторонние программы могут их игнорировать. Если задача требует удалить нежелательные сведения, недостаточно записать пустую строку: нужно проверить и словарь Document Information, и XMP, потому что один и тот же автор или заголовок может присутствовать в обоих местах.

XMP представляет отдельный XML-пакет. Он используется для расширенных схем, идентификаторов, истории обработки и требований некоторых процессов публикации. PyPDF позволяет читать XMP через соответствующее свойство и создавать структуру для записи. Однако изменение одного набора метаданных не синхронизирует другой автоматически. После очистки откройте результат независимым просмотрщиком и проверьте свойства документа, а при требованиях PDF/A выполните специализированную валидацию.

Метаданные нельзя считать доказательством происхождения файла. Поля свободно редактируются, а даты нередко наследуются от шаблона. Их разумно использовать для сортировки и удобства поиска, но не для юридического подтверждения авторства или целостности. Для таких задач применяются цифровые подписи и доверенная инфраструктура; PyPDF может сохранять существующие объекты при некоторых операциях, но не создаёт криптографическую подпись.

Объединение, вставка и разделение страниц

Самый короткий способ собрать несколько документов — последовательно вызвать writer.append(...). Метод принимает путь, поток или reader и переносит страницы вместе с поддерживаемыми связанными объектами. Параметр pages ограничивает диапазон: можно передать кортеж начала и конца либо список индексов. Конечная граница диапазона в Python обычно не включается, поэтому выбор следует тестировать на небольшом файле и сверять число страниц результата.

from pypdf import PdfWriter

writer = PdfWriter()
writer.append("cover.pdf")
writer.append("report.pdf", pages=(0, 10), import_outline=True)
writer.append("appendix.pdf", pages=[0, 2, 4])
writer.write("combined.pdf")

merge(position=...) отличается тем, что вставляет материал перед указанной позицией уже собранного документа. Это удобно для титульного листа, разделителя или исправленной страницы. После вставки прежние индексы сдвигаются, поэтому список операций лучше планировать заранее или выполнять вставки от больших индексов к меньшим. При сложной перестановке проще сформировать новый writer в окончательном порядке, чем многократно менять существующую последовательность.

Разделение выполняется созданием отдельного PdfWriter для каждого диапазона. При этом нужно решить, что делать с закладками, формами, вложениями и метаданными. Простое add_page переносит страницу, но не всегда всю документную структуру. Если важны интерактивные поля, предпочтительнее клонировать документ или использовать append с диапазоном, затем удалить лишнее. Для обычных статических страниц цикл по reader.pages достаточно надёжен.

from pathlib import Path
from pypdf import PdfReader, PdfWriter

reader = PdfReader("book.pdf")
for index, page in enumerate(reader.pages, start=1):
    writer = PdfWriter()
    writer.add_page(page)
    writer.add_metadata({"/Title": f"Страница {index}"})
    writer.write(Path("pages") / f"page-{index:04d}.pdf")

При добавлении одной и той же страницы несколько раз библиотека может повторно использовать уже клонированные объекты. Если между копиями должны быть разные изменения, вызовите writer.reset_translation(reader) перед повторным клонированием или создайте независимую копию. Иначе изменение общего ресурса, например словаря изображения или шрифта, способно затронуть все экземпляры.

Объединение иногда увеличивает размер сильнее ожидаемого. Причины — дублированные шрифты, одинаковые изображения с разными ссылками, неиспользуемые объекты и повторная упаковка потоков. После сборки можно применить compress_identical_objects, но результат следует сравнить с исходным визуально. Агрессивное удаление неиспользуемых ссылок нежелательно для документов со сложными формами и нестандартными интерактивными объектами без отдельного теста.

Поворот, обрезка и система координат

Поворот страницы через page.rotate(90) меняет значение, которое просмотрщик учитывает при отображении. Угол должен быть кратен 90. Это не то же самое, что произвольное вращение содержимого матрицей: первый вариант сохраняет прямоугольную геометрию страницы и обычно лучше подходит для исправления ориентации сканов. Перед объединением страниц с наложением полезно вызвать transfer_rotation_to_content(), чтобы перенести визуальный поворот в команды рисования и упростить координаты.

У страницы есть несколько прямоугольников. mediabox задаёт физическую область, cropbox — видимую часть, а дополнительные box-поля могут описывать печать и обрез. Изменение cropbox скрывает внешнюю область, но не стирает лежащие там текст, изображения и аннотации. Поэтому такая операция не подходит для удаления конфиденциальных данных. Настоящее редактирование требует удалить или заменить соответствующие объекты и проверить, что скрытый текст не извлекается.

from pypdf import PdfReader, PdfWriter

reader = PdfReader("input.pdf")
page = reader.pages[0]

left = float(page.mediabox.left)
bottom = float(page.mediabox.bottom)
right = float(page.mediabox.right)
top = float(page.mediabox.top)

page.cropbox.lower_left = (left + 36, bottom + 36)
page.cropbox.upper_right = (right - 36, top - 36)
page.rotate(90)

writer = PdfWriter()
writer.add_page(page)
writer.write("cropped-rotated.pdf")

Координаты PDF измеряются в пунктах: 72 пункта соответствуют одному дюйму. Начало обычно находится в левом нижнем углу, но mediabox может иметь ненулевое или даже отрицательное начало. Нельзя без проверки считать, что нижний левый угол равен (0, 0). Для штампа или аннотации вычисляйте позицию относительно фактических границ страницы.

Результат наложения страниц в PyPDF

При наложении двух страниц метод merge_page объединяет их потоки и ресурсы. Параметр over=True помещает добавляемую страницу сверху, over=False — под существующим содержимым. Если размеры различаются, часть добавленного материала может оказаться вне видимой области. Для автоматического расширения границ у методов преобразованного наложения предусмотрен параметр expand=True, однако итоговый формат листа всё равно следует проверить.

Масштабирование и геометрические преобразования

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

from pypdf import PdfReader, PdfWriter, Transformation

reader = PdfReader("insert.pdf")
page = reader.pages[0]

operation = Transformation().scale(sx=0.5, sy=0.5).translate(tx=72, ty=144)
page.add_transformation(operation)

writer = PdfWriter()
writer.add_page(page)
writer.write("transformed.pdf")

Метод scale_by меняет и содержимое, и размер страницы пропорционально. scale_to приводит лист к заданным ширине и высоте, что может исказить пропорции. Если требуется разместить уменьшенную копию на прежнем листе, преобразуйте содержимое, но оставьте mediabox исходного размера. Различие хорошо заметно при печати: масштабирование страницы меняет физический формат, масштабирование содержимого оставляет формат и добавляет поля.

Различие масштабирования содержимого и страницы в PyPDF

Для компоновки нескольких страниц на одном листе создают пустую страницу нужного размера и поочерёдно накладывают уменьшенные копии с разными смещениями. Вариант 2×2 требует вычислить ширину и высоту ячейки, выбрать коэффициент без искажения пропорций и учесть поворот каждой исходной страницы. Нельзя изменять один и тот же объект страницы на каждой итерации без копирования: преобразования будут накапливаться.

Компоновка нескольких страниц на одном листе средствами PyPDF

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

Водяные знаки, штампы и фирменные бланки

PyPDF накладывает водяной знак как страницу PDF. Сначала готовят одностраничный шаблон с прозрачным текстом, логотипом или рамкой, затем объединяют его с каждой целевой страницей. При over=False шаблон оказывается под содержимым и может быть закрыт белым фоном; при over=True он расположен сверху. Выбор зависит от конструкции исходного документа, а не только от желаемого термина знак или штамп.

from pypdf import PdfReader, PdfWriter

content = PdfReader("input.pdf")
mark = PdfReader("mark.pdf").pages[0]
writer = PdfWriter()

for page in content.pages:
    page.merge_page(mark, over=True)
    writer.add_page(page)

writer.write("marked.pdf")

Если размеры страниц различаются, один шаблон не будет одинаково расположен на A4, Letter и альбомных листах. Сравнивайте ширину и высоту mediabox, рассчитывайте масштаб и перенос для каждого листа. Для диагонального текста часто удобнее сформировать шаблон в другом инструменте с уже заданной прозрачностью, а в PyPDF выполнять только размещение. Библиотека не является полноценным движком верстки новых документов.

Штамп на последней странице добавляют после отбора страниц, иначе перестановка может изменить его назначение. Для уникального номера, даты или имени получателя создавайте отдельный шаблон для каждого документа либо используйте библиотеку генерации PDF, а затем накладывайте получившуюся страницу. Встраивать текст напрямую в существующий поток сложнее из-за ресурсов шрифта и кодировок; наложение заранее созданной PDF-страницы обычно предсказуемее.

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

Аннотации и интерактивные пометки

Аннотации хранятся отдельно от обычного потока страницы и могут отображаться по-разному в разных просмотрщиках. PyPDF предоставляет классы для свободного текста, заметки, линии, ломаной, прямоугольника, эллипса, многоугольника, всплывающего окна, ссылки и выделения. После создания объект добавляют на конкретную страницу методом writer. Координаты задаются в системе страницы, поэтому перед пакетной разметкой нужно проверить mediabox и поворот.

Свободный текст

FreeText подходит для видимой надписи в прямоугольной области. Важны прямоугольник, текст, размер шрифта, цвет текста, цвет рамки и фон. Прозрачные или не заданные цвета иногда делают объект визуально незаметным, хотя он присутствует в структуре. После сохранения проверьте результат минимум в двух просмотрщиках, если документ отправляется внешним получателям.

Свободная текстовая аннотация, созданная PyPDF

from pypdf import PdfReader, PdfWriter
from pypdf.annotations import FreeText

reader = PdfReader("input.pdf")
writer = PdfWriter(clone_from=reader)

note = FreeText(
    text="Проверено",
    rect=(72, 500, 220, 560),
    font="Helvetica",
    bold=True,
    font_size="18pt",
    font_color="0000ff",
    border_color="0000ff",
    background_color="ffffff",
)
writer.add_annotation(page_number=0, annotation=note)
writer.write("free-text.pdf")

Заметки и всплывающие окна

Аннотация Text создаёт значок заметки с содержимым, которое просмотрщик показывает во всплывающей панели. Это удобно для рецензирования, но не годится для обязательного текста, который должен быть виден при печати. Значок и поведение панели зависят от программы просмотра. Для согласования храните автора и краткое содержание в понятных полях, не полагаясь на конкретный внешний вид значка.

Значок текстовой заметки, добавленный PyPDF

Всплывающее окно может быть связано с основной аннотацией. Его прямоугольник определяет положение панели при открытии, однако просмотрщик вправе изменить размер или разместить её иначе. Связь parent–popup должна оставаться корректной после копирования страниц. Если копируется только страница без всех связанных объектов, окно может потерять привязку.

Всплывающее окно аннотации в PDF после обработки PyPDF

Линии и геометрические фигуры

Линия задаётся начальной и конечной точками, цветом и параметрами границы. Ломаная содержит последовательность вершин. Эти объекты удобны для указателей и технической разметки, но координаты не привязаны к словам: при последующем масштабировании или обрезке страницы их нужно преобразовать отдельно.

Линейная аннотация, добавленная PyPDF

Ломаная аннотация, добавленная PyPDF

Прямоугольник и эллипс определяются ограничивающей областью. У многоугольника задаются вершины. Заливка может перекрыть текст, если выбрана непрозрачность и объект расположен сверху. Для рамки без заливки следует явно настроить соответствующие цвета и проверить результат: значения по умолчанию различаются между типами аннотаций.

Прямоугольная аннотация, созданная PyPDF

Эллиптическая аннотация, созданная PyPDF

Многоугольная аннотация, созданная PyPDF

Ссылки и выделение текста

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

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

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

Интерактивные формы

Поля AcroForm состоят из документного дерева полей и виджетов на страницах. reader.get_fields() возвращает словарь с именами и свойствами, а get_form_text_fields() удобен для текстовых значений. Имя поля может отличаться от подписи, видимой пользователю, и быть составным при иерархии. Перед заполнением выведите имена, типы и текущие значения на тестовом экземпляре.

from pypdf import PdfReader, PdfWriter

reader = PdfReader("form.pdf")
print(reader.get_form_text_fields())

writer = PdfWriter()
writer.append(reader)
writer.update_page_form_field_values(
    writer.pages[0],
    {"customer_name": "Анна Петрова", "order_id": "A-1042"},
    auto_regenerate=False,
)
writer.write("filled-form.pdf")

Для формы предпочтительнее append, потому что обычное add_page переносит страницу, но может не скопировать корневую структуру AcroForm. Если виджеты есть, а поля потерялись, метод reattach_fields помогает заново связать обнаруженные виджеты с деревом. Это восстановительная мера, а не замена корректному копированию.

Параметр auto_regenerate=False обычно уменьшает вероятность появления раздражающего сообщения о сохранении изменений при открытии. Внешний вид значения задаётся appearance-потоком. Разные просмотрщики по-разному реагируют на его отсутствие: один показывает новое значение, другой оставляет пустое поле до фокуса. Поэтому заполненную форму обязательно проверяют в среде получателя.

Флажки и переключатели используют экспортные значения, которые не всегда равны строкам Да и Нет. Изучите словарь конкретного поля и допустимые состояния. Группы переключателей имеют общее имя и разные значения дочерних виджетов. Случайная запись произвольной строки может оставить поле визуально пустым.

Уплощение формы означает перенос текущего внешнего вида на страницу и удаление интерактивных виджетов. В PyPDF можно обновить значения с параметром flatten и затем удалить виджеты, но результат зависит от уже сформированных appearance-потоков. Перед удалением интерактивности убедитесь, что все значения реально нарисованы. Уплощённый документ труднее исправить, поэтому храните отдельный заполненный экземпляр с полями.

JavaScript в формах способен выполнять вычисления и проверки, но его поддержка зависит от просмотрщика и политик безопасности. PyPDF может добавлять JavaScript на уровне документа, однако не выполняет его самостоятельно. Для обязательной бизнес-логики вычисляйте значения в Python и записывайте их явно, а сценарий PDF используйте только как необязательное удобство.

Закладки, назначения и навигация

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

from pypdf import PdfWriter

writer = PdfWriter()
writer.append("part-1.pdf", import_outline=False)
chapter = writer.add_outline_item("Часть 1", page_number=0)
writer.add_outline_item("Введение", page_number=1, parent=chapter)

start_part_2 = len(writer.pages)
writer.append("part-2.pdf", import_outline=False)
writer.add_outline_item("Часть 2", page_number=start_part_2)
writer.write("book-with-outline.pdf")

При append существующие закладки можно импортировать, но после отбора диапазона часть назначений перестаёт иметь смысл. Метод переносит те элементы, которые удаётся связать с добавленными страницами; результат всё равно нужно проверить. Если исходные деревья имеют повторяющиеся названия или слишком глубокую структуру, ручная сборка нового outline даёт более понятную навигацию.

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

Режимы Fit управляют тем, как просмотрщик открывает страницу: целиком, по ширине, по высоте или в заданной области. Точные координаты нужны, когда закладка должна привести к определённому фрагменту. Однако масштаб окна и пользовательские настройки могут изменить визуальный результат. Для универсальной навигации обычно достаточно страницы и подгонки по ширине.

Пароли и шифрование

reader.is_encrypted показывает наличие шифрования. Доступ к страницам может вызвать исключение, пока не выполнен reader.decrypt(password). Метод возвращает результат проверки пароля, поэтому его нужно анализировать, а не считать любое отсутствие исключения успехом. Пароль пользователя открывает документ с заданными разрешениями, пароль владельца даёт полный доступ, но соблюдение ограничений зависит от программы просмотра.

from pypdf import PdfReader, PdfWriter

reader = PdfReader("protected.pdf")
if reader.is_encrypted:
    status = reader.decrypt("input-password")
    if status == 0:
        raise ValueError("Пароль не подошёл")

writer = PdfWriter(clone_from=reader)
writer.encrypt("new-password", algorithm="AES-256")
writer.write("reprotected.pdf")

Для AES устанавливается дополнительная криптографическая зависимость. Алгоритм задавайте явно, чтобы результат не зависел от значения по умолчанию. Совместимость со старыми устройствами следует проверять отдельно: современное шифрование может не открываться в устаревшем встроенном просмотрщике, а слабые алгоритмы не подходят для чувствительных данных.

Пароль не заменяет удаление конфиденциальной информации. Получатель с разрешённым доступом сможет прочитать всё содержимое, включая метаданные и вложения. Кроме того, ограничения на печать и копирование не являются надёжной защитой от владельца среды просмотра. Для передачи секретных документов применяйте организационные меры, защищённый канал и минимизацию данных.

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

Изображения на страницах

Коллекция page.images предоставляет доступ к изображениям, связанным со страницей, включая некоторые вложенные графические объекты. Элемент содержит имя, данные и объект Pillow, когда формат удаётся декодировать. Имена не обязаны быть уникальными, поэтому путь вывода следует формировать из номера страницы, порядкового номера и безопасного расширения, а не только из внутреннего имени.

from pathlib import Path
from pypdf import PdfReader

reader = PdfReader("catalog.pdf")
out = Path("extracted-images")
out.mkdir(exist_ok=True)

for page_no, page in enumerate(reader.pages, start=1):
    for image_no, image in enumerate(page.images, start=1):
        suffix = Path(image.name).suffix or ".bin"
        filename = out / f"p{page_no:04d}-{image_no:03d}{suffix}"
        filename.write_bytes(image.data)

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

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

Удаление изображений уменьшает размер, но меняет смысл документа. Методы удаления могут затронуть обычные изображения, встроенные объекты и альтернативные представления. После операции сравните число страниц, извлечённый текст и визуальные миниатюры. Если задача состоит только в удалении фоновых сканов при сохранении OCR-слоя, отбор по размеру и положению должен быть очень аккуратным.

Вложения внутри PDF

PDF может содержать прикреплённые файлы, которые не видны на страницах. reader.attachments предоставляет словарь, где одному имени иногда соответствует несколько наборов байтов. Поэтому нельзя предполагать, что значение всегда одно. Более подробный перебор выполняется через attachment_list, который сохраняет отдельные объекты и их свойства.

from pathlib import Path
from pypdf import PdfReader

reader = PdfReader("container.pdf")
out = Path("attachments")
out.mkdir(exist_ok=True)

for name, contents in reader.attachments.items():
    safe = Path(name).name
    for index, data in enumerate(contents, start=1):
        target = out / f"{index:03d}-{safe}"
        target.write_bytes(data)

Имя вложения является недоверенным вводом. Обязательно удаляйте компоненты пути, запрещайте абсолютные пути и проверяйте итог, чтобы запись оставалась внутри выбранной папки. Не запускайте извлечённые файлы автоматически. Ограничивайте суммарный размер и число вложений, иначе небольшой PDF может развернуться в большой набор данных.

Для добавления используется writer.add_attachment(name, data). Данные передаются как байты. Если документ должен хранить несколько файлов с одинаковыми именами, задайте уникальные отображаемые имена, потому что поведение просмотрщиков при дубликатах различается. После добавления проверьте панель вложений и повторно прочитайте файл через PyPDF.

При очистке документа недостаточно удалить видимые страницы с упоминанием файла. Вложения остаются в каталоге, пока их не удалить из соответствующей структуры. Такой же принцип относится к JavaScript, метаданным, аннотациям и скрытым слоям: визуальный просмотр страниц не покрывает все данные PDF.

Уменьшение размера результата

После объединения вызов writer.compress_identical_objects(remove_duplicates=True, remove_unreferenced=True) помогает объединить одинаковые объекты и удалить недоступные ссылки. Применяйте её перед записью и сравнивайте размер с вариантом без оптимизации. На простом документе выигрыш может быть нулевым, а основную массу обычно составляют изображения.

Потоки содержимого страницы можно перепаковать методом compress_content_streams. Это сжимает команды рисования, но не перекодирует JPEG, JPEG2000 и другие уже сжатые изображения. Поэтому уменьшение иногда составляет лишь несколько процентов. Вызов требует, чтобы страница была связана с writer; после изменения потока проверьте отображение прозрачностей и нестандартных ресурсов.

Обрезка cropbox почти не уменьшает файл, поскольку скрытые за границей объекты остаются внутри. Масштабирование страницы тоже не снижает разрешение растрового изображения: меняется только размер его отображения. Для заметного уменьшения сканов требуется декодировать изображения, изменить разрешение или качество и заменить их. Такая операция необратима, поэтому исходный экземпляр сохраняют отдельно.

При выборе качества учитывайте назначение. Для просмотра на экране часто достаточно меньшего разрешения, для печати мелких схем требуется больше. Чёрно-белые документы могут эффективно сжиматься специальными алгоритмами, но доступность декодирования зависит от компонентов среды. Цветные фотографии лучше тестировать на нескольких значениях качества, сравнивая не только размер, но и читаемость текста поверх изображения.

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

Работа с потоками и облачными хранилищами

PdfReader принимает объект, похожий на двоичный файл, а PdfWriter.write записывает в такой объект. Это позволяет использовать io.BytesIO, временные файлы, ответы объектного хранилища и собственные адаптеры. Поток чтения должен поддерживать read и seek, потому что PDF требует переходов по смещениям. Поток только для последовательного чтения сначала буферизуют.

from io import BytesIO
from pypdf import PdfReader, PdfWriter

input_bytes = load_document_bytes()
source_stream = BytesIO(input_bytes)
reader = PdfReader(source_stream)

writer = PdfWriter()
writer.append(reader)

output_stream = BytesIO()
writer.write(output_stream)
result_bytes = output_stream.getvalue()
save_document_bytes(result_bytes)

Полная загрузка в BytesIO проста, но удваивает расход памяти: одновременно присутствуют входные байты, разобранные объекты и выходной буфер. Для крупных файлов лучше использовать временный файл или поток, который поддерживает случайный доступ без полного копирования в память. Однако сетевой адаптер с запросом диапазонов должен корректно реализовать seek и кэширование, иначе множество мелких чтений сделает обработку медленной.

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

Параллельная обработка разных документов возможна на уровне процессов или задач, но один writer не следует изменять из нескольких потоков. Разделяйте состояние по файлам. Число одновременных задач ограничивайте памятью: распаковка нескольких тяжёлых страниц может резко превысить размер входных файлов.

Ошибки, предупреждения и журнал

Исключения библиотеки собраны в модуле pypdf.errors. Базовый PyPdfError удобно перехватывать на границе обработки одного документа, оставляя непредвиденные ошибки видимыми. Слишком широкий except Exception без журнала скрывает дефекты кода и создаёт неполные результаты. В пакетной задаче записывайте путь, стадию, тип исключения и краткое сообщение.

import logging
from pathlib import Path
from pypdf import PdfReader
from pypdf.errors import PyPdfError

log = logging.getLogger("pypdf")
handler = logging.StreamHandler()
handler.setLevel(logging.WARNING)
log.addHandler(handler)
log.setLevel(logging.WARNING)

for path in Path("incoming").glob("*.pdf"):
    try:
        reader = PdfReader(path, strict=False)
        _ = len(reader.pages)
    except PyPdfError as exc:
        logging.error("Не удалось прочитать %s: %s", path.name, exc)

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

Не отключайте весь журнал глобально только ради чистой консоли. Настройте уровень для логгера pypdf или добавьте фильтр на конкретное известное сообщение. При обновлении зависимостей пересмотрите фильтры: текст предупреждения может измениться, а старая причина — стать ошибкой.

Повреждённый документ, который открывается в просмотрщике, не обязательно корректен. Просмотрщик может молча восстановить структуру и сохранить исправленную копию. PyPDF также старается работать в мягком режиме, но набор эвристик другой. Если файл не читается, безопасный путь — запросить корректный экземпляр или предварительно нормализовать его надёжным инструментом, затем повторить задачу.

Безопасность при обработке недоверенных PDF

PDF — контейнер со сжатыми потоками, вложениями, сценариями, формами и сложными ссылками. Даже без выполнения JavaScript разбор может потребовать много памяти или времени. Ограничьте размер файла до чтения, число страниц после открытия, суммарный объём извлекаемого текста и изображений, количество вложений и максимальное время на документ. На сервере используйте отдельного непривилегированного пользователя и каталог без права исполнения.

Не доверяйте именам файлов из вложений и метаданных. Нормализуйте Unicode, удаляйте управляющие символы, запрещайте компоненты пути и создавайте собственные уникальные имена. Не вставляйте извлечённый текст напрямую в HTML, SQL или командную строку без экранирования, потому что PDF может содержать произвольные строки.

Автоматическое добавление внешних ссылок и JavaScript должно быть отключено для входных данных, если это не обязательная функция. При копировании страниц связанные действия могут сохраниться. Для санитарной обработки нужно целенаправленно удалить действия, вложения и сценарии, а затем проверить структуру. Простая печать в новый PDF иногда визуально упрощает документ, но может потерять доступность, ссылки и качество, поэтому это отдельный осознанный процесс.

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

Совместимость формата и проверка результата

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

Соответствие PDF/A не возникает автоматически после записи. У такого документа есть требования к цветовым профилям, шрифтам, метаданным, запрещённым функциям и идентификаторам. PyPDF позволяет менять отдельные объекты, но не обещает полноценную проверку профиля. Если результат должен пройти нормативный контроль, используйте валидатор и исправляйте конкретные нарушения.

Цифровая подпись привязана к байтам и диапазонам исходного файла. Обычная перезапись writer создаёт новый набор байтов и обычно нарушает подпись. Библиотека не выполняет подписание. Для подписывания и проверки применяют специализированный инструмент, а PyPDF используют до этапа подписи для подготовки окончательного содержимого.

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

from pathlib import Path
from pypdf import PdfReader

result = Path("result.pdf")
reader = PdfReader(result, strict=True)
assert len(reader.pages) == expected_pages

for index, page in enumerate(reader.pages):
    text = page.extract_text() or ""
    if index in required_text_pages and not text.strip():
        raise ValueError(f"На странице {index + 1} пропал текстовый слой")

Практические пакетные сценарии

Сборка ежемесячного отчёта

Для отчёта сначала формируют упорядоченный список файлов из реестра, а не из случайного порядка файловой системы. Каждый документ открывают отдельно, проверяют число страниц и добавляют через append. Перед первым листом раздела записывают текущую длину writer и используют её как назначение закладки. После сборки добавляют общие метаданные, нумерационный штамп и выполняют проверку ожидаемого количества страниц.

Если некоторые разделы отсутствуют, не вставляйте пустой файл без отметки. Сформируйте явную страницу-разделитель или запишите пропуск в журнале. Иначе итоговый отчёт может выглядеть завершённым, хотя данных нет. Порядок и контрольные суммы исходных файлов полезно хранить отдельно от PDF.

Выделение диапазонов по ключевому слову

Сначала извлекают текст каждой страницы и строят список индексов, где встречается заголовок раздела. Затем диапазон продолжают до следующего найденного заголовка. Поиск выполняют после нормализации пробелов и регистра, но сохраняют исходный текст для проверки. Такой подход работает только с текстовым слоем; сканы предварительно распознают.

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

Удаление пустых страниц

Пустоту нельзя определять только по extract_text(), потому что на странице может быть схема или скан. Комбинируйте длину текста, наличие изображений, аннотаций и размер потока содержимого. Страница с невидимым техническим объектом может считаться пустой визуально, а белое полноформатное изображение — занимать большой объём. Для надёжности создавайте миниатюру независимым рендерером и проверяйте долю непустых пикселей.

Нормализация ориентации

Если ориентация известна из реестра, применяйте rotate по заданному правилу. Автоматическое определение по тексту сложнее: нужно оценить ориентации текстовых матриц или использовать распознавание изображения. После поворота проверьте ширину, высоту, cropbox и аннотации. Не меняйте ориентацию только потому, что ширина больше высоты: альбомный лист может быть правильным.

Заполнение серии форм

Подготовьте таблицу соответствий между бизнес-полями и именами AcroForm. Для каждой записи клонируйте чистый шаблон, обновляйте значения и формируйте уникальный путь вывода. После записи повторно читайте поля и сравнивайте значения. Затем выборочно открывайте документы в целевом просмотрщике, чтобы убедиться в наличии appearance-потоков.

Извлечение вложений с карантином

Сначала создайте отдельную папку на документ и собственные безопасные имена. Сохраняйте исходное имя только в манифесте. Ограничивайте суммарный размер и вычисляйте SHA-256 каждого объекта. Файлы не открываются и не запускаются автоматически; дальнейшая проверка выполняется отдельным процессом безопасности.

Производительность и масштабирование

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

Измеряйте стадии отдельно: открытие, перебор страниц, извлечение текста, преобразование, запись и повторную проверку. Общий таймер не покажет узкое место. Для каждой стадии фиксируйте число страниц и байты входа. Набор тестов должен включать текстовый документ, скан, форму, зашифрованный файл и повреждённый образец.

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

Кэширование текста полезно, если один и тот же файл анализируется несколькими правилами. Ключом служит криптографический хеш входных байтов и параметры извлечения. Не используйте только имя и дату изменения: файл могут заменить. Кэш преобразованных страниц сложнее, потому что объектные ссылки принадлежат конкретному reader или writer.

Сравнение PyPDF с аналогами

ПрограммаЛучше подходит дляГлавное ограничение
PyPDFАвтоматизации объединения, разбиения, метаданных, форм, аннотаций и шифрования в PythonНет визуального редактора и встроенного OCR
PDF CommanderРучной работы с PDF через понятные панели и команды без написания кодаНе заменяет программный Python API для массовых конвейеров
PyMuPDFБыстрого рендеринга страниц, извлечения текста и изображений с привязкой к координатамИспользует нативный движок и требует учитывать условия лицензирования
pikepdfНизкоуровневой работы со структурой PDF, исправления и оптимизации на базе QPDFНе ориентирован на удобное извлечение визуальной разметки текста
pdfplumberИзвлечения таблиц, слов и координат с визуальной диагностикой разметкиПрактически не предназначен для записи и редактирования PDF
pdfminer.sixГлубокого анализа текстовой разметки, символов, шрифтов и координатСложнее для перестановки страниц и записи изменённого документа

PyPDF выбирают, когда требуется собрать или изменить структуру документа средствами Python и сохранить управляемый сценарий. PDF Commander удобнее для разовой ручной правки. PyMuPDF полезен, когда критичны скорость отображения и координаты, pikepdf — при низкоуровневом ремонте и оптимизации, pdfplumber и pdfminer.six — при извлечении табличной и текстовой разметки. В одном конвейере инструменты можно сочетать: например, PyPDF собирает страницы, а специализированный анализатор находит координаты данных.

Типичные ошибки и способы исправления

После объединения пропали поля формы

Причина обычно в копировании страниц через add_page без корневой структуры AcroForm. Повторите сборку через append или клонирование документа. Если виджеты уже есть на страницах, проверьте reattach_fields. Затем прочитайте get_fields() из записанного результата и откройте его в целевом просмотрщике.

Текст извлекается в неправильном порядке

Попробуйте extraction_mode="layout", ограничение ориентаций и visitor-функции с координатами. Для многоколоночной страницы сначала сгруппируйте фрагменты по вертикальным диапазонам и колонкам. Универсального восстановления абзацев нет, потому что PDF хранит команды позиционирования, а не логическую структуру редактора.

Из скана возвращается пустая строка

Проверьте наличие полноформатного изображения и отсутствие текстовых объектов. Выполните OCR отдельным инструментом, добавьте распознанный слой и только затем используйте extract_text(). Увеличение strict или смена кодировки не создаст текст, которого нет в документе.

Штамп оказался за белым фоном

Шаблон был наложен под содержимым. Используйте over=True для размещения сверху либо сделайте фон исходной страницы прозрачным, если это контролируемый документ. Проверьте размер и координаты шаблона, особенно на альбомных страницах.

После обрезки файл почти не уменьшился

Cropbox только скрывает область. Растры и текст за границей остаются в потоках. Для уменьшения нужно удалить ненужные объекты или заменить изображения меньшего разрешения. Если данные конфиденциальны, визуальной обрезки недостаточно.

Аннотация создана, но не видна

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

AES вызывает ошибку зависимости

Установите набор pypdf[crypto] в тот же интерпретатор, которым запускается сценарий. Сверьте sys.executable и вывод pip. После установки явно укажите алгоритм и протестируйте открытие результата нужными программами.

Один изменённый рисунок поменялся на нескольких страницах

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

Запись поверх входного файла повредила документ

Сохраняйте во временный путь, повторно открывайте и проверяйте результат, затем атомарно заменяйте исходный файл. Нельзя открывать тот же путь на запись, пока reader ещё обращается к его данным. Для важных документов всегда сохраняйте контрольный экземпляр.

Память резко выросла при извлечении текста

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

Закладки ведут не туда после вставки

Индексы страниц сдвинулись. Стройте outline после окончательной компоновки или сохраняйте ссылки на фактические страницы при добавлении. Импортированные назначения перепроверьте, особенно если добавлялись только отдельные диапазоны.

Результат открывается, но не проходит PDF/A

Обычная запись не обеспечивает профиль соответствия. Выполните специализированную проверку и изучите нарушения: шрифты, цветовые профили, XMP, прозрачность, действия или вложения. Исправление каждого класса требует отдельной процедуры.

Как построить надёжный производственный конвейер

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

Разделите этапы на чтение, анализ, план изменений, запись и проверку. На этапе анализа ничего не меняйте: соберите число страниц, размеры, повороты, метаданные, поля, вложения и предупреждения. План изменений храните как обычные данные — список диапазонов, углов, штампов и значений полей. Это упрощает тестирование и повторный запуск.

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

Журнал должен связывать входной хеш, параметры операции, выходной хеш, время, предупреждения и результат проверки. Не помещайте в журнал пароли и конфиденциальный извлечённый текст. Для воспроизводимости фиксируйте зависимости окружения во внутренней конфигурации проекта и сохраняйте тестовый набор обезличенных PDF разных типов.

Автоматические тесты полезно строить на свойствах, а не только на байтовом совпадении. PDF может быть эквивалентным при другом порядке объектов и другом сжатии. Проверяйте число страниц, размеры, извлечённый текст, наличие закладок, значения форм, список вложений и возможность расшифрования. Для визуальных операций сравнивайте изображения страниц с допустимым порогом.

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

Когда PyPDF подходит лучше всего

Библиотека особенно эффективна для задач, где правила можно выразить через страницы и структуру документа: объединить набор файлов по реестру, извлечь диапазоны, повернуть выбранные листы, записать метаданные, заполнить AcroForm, добавить outline, вложение, пароль или аннотацию. Сценарий легко повторить на большой партии и включить в обработку данных.

Для ручной визуальной правки текста, свободной верстки, рисования новых страниц и точного редактирования графики удобнее специализированный редактор или генератор. Для сканов нужен OCR. Для цифровой подписи применяется отдельный инструмент. Для сложного извлечения таблиц и координат могут понадобиться pdfplumber, pdfminer.six или PyMuPDF. Правильный выбор определяется конкретной операцией, а не желанием решить весь жизненный цикл одним пакетом.

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

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