← К техническим практикам

CI/CD практика

pdf-inspector PDF OCR: подключение и деплой

Около 3 мин чтения

pdf-inspector PDF OCR: подключение и деплой

Файл уходит в OCR, хотя в нём уже есть текст, а смешанный PDF после распознавания теряет структуру страниц.

Самое быстрое решение — поставить pdf-inspector перед OCR: текстовый PDF отправлять в обычный парсер, сканированный — в OCR, смешанный — обрабатывать постранично; при низкой уверенности сохранять резервный маршрут и направлять файл на ручную проверку.

Кому стоит читать этот материал: backend-разработчикам, которые обрабатывают договоры, отчёты и научные статьи пакетно; AI-инженерам, строящим RAG-конвейеры; командам, которым нужно заранее проверить параллелизм и способ доставки результатов в удалённой среде.

Последнее обновление: 10 августа 2026 года. Данные и API сверены с текущими README, Python-документацией, примерами и материалами релизов проекта.

Этап подготовки: сначала определите результат маршрутизации

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

Для рабочего конвейера достаточно разделить входящие файлы на четыре ветки:

  • Текстовый PDF — извлечение встроенного текста с сохранением порядка чтения, заголовков, ссылок и таблиц.
  • Сканированный PDF — передача страниц в OCR с сохранением результата распознавания и его координат.
  • Смешанный PDF — отдельная обработка страниц, где есть текстовый слой, и страниц без пригодного текста.
  • Аномальный файл — карантин, повторная попытка другим парсером или ручная проверка.

Официальная документация проекта указывает, что pdf-inspector различает text_based, scanned, image_based и mixed, а также возвращает уверенность, страницы, которым нужен OCR, и причины перехода на резервный маршрут. Это делает его не OCR-движком, а слоем предварительной маршрутизации. (репозиторий проекта на GitHub)

Перед реализацией ответьте на три вопроса:

  1. Нужен ли RAG-конвейеру чистый текст или Markdown со структурой?
  2. Должны ли таблицы переходить в отдельные объекты?
  3. Нужно ли сохранять координаты слов для поиска по фрагменту страницы?

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

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

Этап первого запуска: соберите минимальную рабочую цепочку

Текущая Python-документация проекта показывает установку через пакет pdf-inspector, а для разработки из исходного дерева — сборку с помощью maturin. В чистом окружении сначала сверяйте команды с официальной Python-документацией проекта, потому что интерфейсы и имена полей могут измениться между версиями. (документация Python для проекта)

Минимальный сценарий должен состоять из пяти действий:

  1. Создайте отдельное виртуальное окружение Python.
  2. Установите пакет способом, указанным в текущем README.
  3. Подготовьте один PDF с копируемым текстом.
  4. Подготовьте один PDF, состоящий из изображений страниц.
  5. Сохраните полный результат классификации в JSON для сравнения.

Для приложения, которое уже работает с байтами, используйте обработку из памяти, а не временный файл. В текущем API предусмотрен вызов process_pdf_bytes, а для быстрого предварительного решения — detect_pdf и detect_pdf_bytes. Пример ниже повторяет опубликованный интерфейс и не добавляет вымышленных параметров:

import json
import pdf_inspector

with open("document.pdf", "rb") as source:
    data = source.read()

result = pdf_inspector.detect_pdf_bytes(data)

record = {
    "pdf_type": result.pdf_type,
    "page_count": result.page_count,
    "confidence": result.confidence,
    "pages_needing_ocr": result.pages_needing_ocr,
}

print(json.dumps(record, ensure_ascii=False, indent=2))

Если нужен полный локальный результат для текстового PDF, используйте process_pdf. Документация также описывает извлечение текста, текстовых элементов с координатами и Markdown по страницам. (описание методов обработки PDF)

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

Решение для смешанных файлов: выбирайте постраничную обработку

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

В README проекта описан список страниц, которым требуется OCR, что позволяет строить постраничную маршрутизацию вместо правила «весь файл или ничего». Классификатор анализирует содержимое потоков PDF и наличие текстовых и графических операторов; для больших документов также предусмотрены стратегии сканирования страниц. (описание определения страниц для OCR)

Смешанный PDF лучше распознавать целиком или по страницам?
По умолчанию выбирайте обработку по страницам, если следующий сервис умеет объединять результаты. Текстовые страницы направляйте в нативное извлечение, страницы из списка pages_needing_ocr — в OCR, а затем собирайте общий документ с исходными номерами страниц.

Целиком отправлять смешанный файл в OCR оправдано только при одном из условий:

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

Решение по условиям: какой маршрут выбрать

Используйте этот список как рабочий инструмент при проектировании сервиса:

  • Если тип text_based, уверенность достаточна для вашей выборки и нет признака проблемной кодировки, то используйте нативное извлечение без OCR.
  • Если тип scanned или image_based, то передавайте весь документ в OCR.
  • Если тип mixed и следующий сервис умеет объединять страницы, то извлекайте текстовые страницы локально, а страницы из pages_needing_ocr отправляйте в OCR.
  • Если тип mixed, но объединение результатов невозможно, то выбирайте полный OCR только после проверки качества на размеченной выборке.
  • Если значение confidence низкое, присутствует has_encoding_issues или результат пустой, то применяйте резервный маршрут: полный OCR, альтернативный парсер или ручную очередь.
  • Если файл зашифрован, повреждён или не открывается, то не повторяйте бесконечно одну и ту же задачу — помещайте её в карантин с причиной отказа.
  • Если после OCR изменилось число страниц, исчезли обязательные страницы или текст пуст, то останавливайте индексацию и создавайте задачу контроля качества.

Такой подход отвечает на вопрос «нужен ли OCR», но не подменяет проверку качества. Наличие текстового слоя ещё не означает, что текст пригоден для индексации: шрифты CID, неправильная кодировка и сложная вёрстка могут дать технически успешный, но бесполезный результат. Проект отдельно указывает на обнаружение проблем кодировки и возможность перехода к OCR. (документация по проблемам кодировки)

Этап интеграции: подключите pdf-inspector к Python-сервису

Как встроить pdf-inspector в существующее Python-приложение?
Не помещайте вызов классификатора внутрь OCR-воркера. Создайте отдельный этап inspect, который получает байты PDF, возвращает нормализованное решение и только после этого публикует задачу в очередь извлечения или OCR.

Рекомендуемая схема выглядит так:

загрузка PDF
   ↓
проверка хеша и базовой доступности файла
   ↓
pdf-inspector: тип, уверенность, страницы OCR
   ↓
решение маршрутизации
   ├─ native_extract
   ├─ page_ocr
   ├─ full_ocr
   └─ quarantine
   ↓
проверка результата
   ↓
индексация, RAG или передача клиенту

На этапе inspect сохраняйте минимум следующие поля:

  • хеш исходного файла;
  • размер и имя объекта;
  • версию pdf-inspector;
  • тип PDF;
  • значение уверенности;
  • количество страниц;
  • список страниц для OCR;
  • признаки проблемной кодировки;
  • выбранную ветку;
  • причину перехода на резервный маршрут;
  • время начала и окончания каждого этапа.

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

Официальный README описывает также CLI-режимы detect-pdf, JSON-вывод и обработку выбранных страниц. Если ваш сервис написан не на Python, это позволяет вынести инспекцию в отдельный процесс или контейнер и обмениваться JSON через очередь. (CLI и JSON-режимы проекта)

Этап пакетной обработки: ограничьте параллелизм до проверки ресурсов

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

Разделите задачи на очереди:

  • inspect — быстрая классификация и извлечение метаданных;
  • native_extract — локальный разбор текстовых PDF;
  • page_ocr — OCR отдельных страниц;
  • full_ocr — резервный режим для неразделимых документов;
  • quarantine — повреждённые, защищённые и неоднозначные файлы.

Для каждой очереди задайте собственные ограничения:

  • максимальное время выполнения;
  • допустимый размер входного файла;
  • число одновременно обрабатываемых документов;
  • число страниц в одной OCR-задаче;
  • количество повторных попыток;
  • политику удаления временных изображений.

На уровне хранилища сохраняйте исходный PDF отдельно от производных результатов. Markdown, распознанный текст, JSON классификации и журнал ошибок должны иметь связь с одним идентификатором документа. Это упростит повторную обработку после обновления парсера и позволит сравнить старый и новый результат на одинаковом входе.

Не используйте заявление проекта о скорости как SLA вашего сервиса. В опубликованном бенчмарке сравнивались 200 PDF без OCR на Apple M4 Pro, а результаты зависели от версий движков, конфигурации и методики измерения. В текущей версии README указаны совокупный показатель 0,875 и время обработки набора 0,470 секунды, но это не обещание для ваших договоров, сканов и сетевого OCR. (опубликованные результаты сравнения)

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

Этап обработки ошибок: заранее опишите резервный маршрут

Что делать, если проверка типа PDF завершилась ошибкой?
Сначала отделите ошибку чтения файла от неуверенной классификации. Если PDF не открывается или повреждена структура объектов, повторный вызов с теми же параметрами обычно не решает проблему. Такой файл нужно отправить в карантин, записать исключение и передать оператору или альтернативному инструменту.

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

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

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

Этап приёмки: проверяйте не только классификацию

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

Проверка должна включать пять уровней:

  1. Классификация — выбран ли правильный тип и список страниц OCR.
  2. Маршрутизация — действительно ли текстовый PDF минует OCR.
  3. Качество текста — не пропали ли символы, заголовки, строки таблиц и порядок колонок.
  4. Стабильность — одинаков ли результат при повторной обработке того же хеша.
  5. Аварийный путь — создаётся ли понятная задача для каждого исключения.

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

После каждого обновления пакета запускайте тот же набор регрессии. Сравнивайте не только поле pdf_type, но и список страниц OCR, длину извлечённого текста, наличие пустых результатов, структуру заголовков и число ошибок. В официальном README указано, что проект публикует версии движков и воспроизводимые результаты сравнения; используйте этот принцип и для собственного набора. (регрессионная проверка и версии проекта)

Перед запуском удобно пройти короткий список приёмки:

  • [ ] Есть отдельный образец текстового PDF.
  • [ ] Есть отдельный образец сканированного PDF.
  • [ ] Есть смешанный PDF с разными типами страниц.
  • [ ] Для каждого файла сохранены хеш и версия pdf-inspector.
  • [ ] Для смешанного документа проверен список страниц, которым нужен OCR.
  • [ ] Низкая уверенность переводит задачу на резервный маршрут.
  • [ ] Повреждённые и зашифрованные файлы попадают в карантин.
  • [ ] После OCR проверяются число страниц и непустой результат.
  • [ ] В RAG проверяются заголовки, границы фрагментов и номера страниц.
  • [ ] После обновления зависимости запускается тот же набор регрессии.
  • [ ] В мониторинге отдельно видны время инспекции, OCR и записи результата.
  • [ ] Есть ручной путь для спорных документов.

Если хотя бы два пункта не выполнены, расширять параллелизм рано: вы будете измерять не производительность, а количество неразобранных ошибок.

Постоянный контроль: наблюдайте за долей OCR и резервными переходами

После запуска добавьте метрики, которые объясняют поведение конвейера:

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

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

Для нагрузки сначала тестируйте только этап инспекции, затем добавляйте нативное извлечение и после этого OCR. Так вы поймёте, где возникает ограничение: процессор, память, дисковое хранилище, сеть до OCR-сервиса или очередь результатов.

Если для такого теста нужна временная среда Mac, выбирайте её по способу передачи данных и длительности эксперимента. Для короткой проверки можно рассмотреть аренду Mac mini, а при распределённой команде заранее сравнить аренду Mac mini в США. Важно проверить не только доступ по SSH, но и объём диска, способ загрузки PDF, сохранение результатов и допустимый уровень параллельных задач.

Перед расширением: выберите выборку или масштабирование

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

Текущая схема на Windows или Linux может быть достаточной для постоянной нагрузки, но у неё остаются типичные ограничения: среда уже занята другими задачами, трудно быстро выделить изолированный стенд, а проверка разных вариантов процессора и диска требует ручной настройки. Для временного стресс-теста аренда Mac через Kvmkit удобнее, если вам нужен отдельный удалённый узел без покупки оборудования и долгой подготовки. При этом аренда не заменяет собственную промышленную инфраструктуру: для длительной стабильной нагрузки, специальных физических интерфейсов или строгих требований к хранению данных нужно отдельно оценить локальный сервер или постоянный кластер.

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

CI/CD на M4 Mac mini — без лишних хлопот

Xcode, Fastlane, CocoaPods, and SPM are first-class on macOS. Mac mini M4 unified memory keeps signing and archiving smooth; ~4W standby power suits 24/7 build nodes.

View Kvmkit plans

Нужна техническая поддержка или консультация?

При проблемах с Mac-инстансами или CI/CD сначала загляните в центр помощи.