Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

Адаптивный парсинг PDF: как каскад проверок выявляет таблицы и рисунки без лишних затрат

Три бесплатные эвристики для предварительной классификации страниц PDF: подсчёт символов, обнаружение пустых bbox и кластеризация координат строк. Сократите зат

Коротко

Что будет в материале

  1. 01

    Почему один парсер на все страницы - это переплата

  2. 02

    Что именно ломает дешёвые парсеры: три главные аномалии

  3. 03

    Каскад детерминированных проверок: архитектура принятия решений

  4. 04

    Когда подключать платные модели: Azure Document Intelligence и vision LLM

Стандартный пайплайн обработки PDF-документов устроен просто: берём один парсер и гоним через него все страницы подряд. Результат предсказуем - 80% бюджета уходит на обработку страниц, которым дорогой инструмент не нужен. Эта статья показывает альтернативу: каскад из трёх бесплатных эвристик, который за доли секунды определяет, справится ли дешёвый парсер с конкретной страницей или пора подключать Azure Document Intelligence либо vision LLM.

Мы разберём конкретные алгоритмы - подсчёт символов, обнаружение пустых bounding box изображений и кластеризацию координат строк. Каждый метод сопровождается кодом на Python, который можно скопировать и запустить. В конце - сквозной кейс на финансовом отчёте с цифрами экономии.

Почему один парсер на все страницы - это переплата

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

Проблема возникает, когда мы отправляем весь документ в Azure Document Intelligence. Стоимость одного вызова - от $1.50 за 1000 страниц для prebuilt-layout. Для документа на 500 страниц это $0.75, но реальная цена растёт из-за задержек и лимитов. Если 400 из этих страниц - чистый текст, мы переплачиваем за них в 5-10 раз по сравнению с локальным парсингом.

Vision LLM, такие как GPT-4V или Claude, обходятся ещё дороже: $0.01-0.03 за изображение при разрешении high. Обработка 100 страниц с диаграммами через vision LLM стоит $1-3, что сопоставимо с месячным бюджетом на парсинг небольшого проекта. При этом текстовые страницы, скормленные vision LLM, не дают прироста качества - модель просто переписывает то, что уже извлёк PyMuPDF.

Каскадный подход решает эту проблему. Мы прогоняем каждую страницу через три быстрых проверки. Если страница «чистая» - парсим локально. Если обнаружена аномалия - отправляем в платный API. Результат: платные вызовы сокращаются на 70-90%, а качество извлечения данных остаётся на том же уровне.

Для тех, кто работает с большими корпусами документов, этот подход критичен. В анализе 146 тысяч тикетов Service Desk мы показали, как учёт реальных паттернов сокращает объём проблемных данных в 4.5 раза. Аналогичная логика применима и к парсингу PDF: не все страницы одинаково сложны, и платить за простые - ошибка.

Что именно ломает дешёвые парсеры: три главные аномалии

Дешёвые парсеры работают с текстовым слоем PDF. Они извлекают символы, их координаты и шрифты, но не понимают визуальную структуру. Три типа контента систематически вызывают сбои.

Flattened-таблицы: когда текст есть, а таблицы нет

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

Пример вывода PyMuPDF для такой страницы:

Выручка 1 234 567 Себестоимость 890 123 Валовая прибыль 344 444
Операционные расходы 156 789 Чистая прибыль 187 655

Восстановить таблицу из такой каши невозможно. Нужен парсер, который анализирует координаты и строит сетку ячеек - а это уже функциональность Azure Document Intelligence или специализированных моделей. Если вы выбираете OCR-инструмент для сложных документов, обратите внимание на сравнение Nanonets-OCR2-3B, DeepSeek-OCR и OlmOCR-2 - там разобрана точность на таблицах и формулы для открытых моделей.

Рисунки с текстом: пустой bbox - не всегда ошибка

Диаграмма с подписями, встроенная в PDF как растровое изображение, не имеет текстового слоя. PyMuPDF видит объект типа «image» с bounding box, но внутри этого bbox нет текстовых блоков. Парсер молча пропускает страницу или возвращает пустую строку.

Техническая причина: PDF хранит изображения как XObject, а текст - в отдельных текстовых объектах. Если создатель документа вставил диаграмму скриншотом из Excel, текстовые метки «сплавлены» с пикселями. Извлечь их можно только через OCR или vision LLM.

Обнаружить такую страницу просто: находим все bbox изображений, проверяем наличие текста внутри них. Если изображение занимает более 30% страницы и не содержит текста - это кандидат на платный парсинг.

Многоколоночная вёрстка: когда строки перемешиваются

Научная статья в две колонки - классический пример. Дешёвый парсер читает строки слева направо по всей ширине страницы. Строка из левой колонки, затем строка из правой, снова из левой. Текст превращается в бессмыслицу.

Пример из двухколоночной статьи:

Методология исследования основана на previous work in natural language processing. Мы применили трансформерную архитектуру The experimental setup included 8 NVIDIA A100 GPUs. с механизмом внимания на 16 голов. Training took approximately 72 hours.

Первое и третье предложения - из левой колонки. Второе и четвёртое - из правой. Восстановить порядок можно только зная геометрию колонок, которую даёт кластеризация x-координат строк.

Каскад детерминированных проверок: архитектура принятия решений

Каскад - это последовательность из трёх эвристик. Каждая работает быстро и бесплатно. Порядок важен: от самой дешёвой проверки к более ресурсоёмкой. Если страница не проходит хотя бы одну - отправляем в платный парсер.

Схема принятия решений:

  1. Подсчёт символов на странице и сравнение со средним по документу.
  2. Обнаружение изображений с пустыми bounding box.
  3. Кластеризация x-координат строк для детекции колонок.

Все три эвристики реализуются на Python с библиотекой PyMuPDF. Код занимает менее 100 строк и выполняется за миллисекунды на страницу.

Эвристика 1: аномалии количества символов

Страницы с таблицами содержат аномально много цифр и спецсимволов. Алгоритм подсчитывает долю цифр среди всех символов страницы и сравнивает со средним значением по документу. Отклонение более чем на 2 стандартных отклонения - сигнал.

import fitz  # PyMuPDF
import numpy as np

def char_anomaly_score(page, doc_avg_digit_ratio, doc_std_digit_ratio):
    text = page.get_text()
    if len(text) == 0:
        return 0.0
    digits = sum(1 for c in text if c.isdigit())
    digit_ratio = digits / len(text)
    if doc_std_digit_ratio == 0:
        return 0.0
    return abs(digit_ratio - doc_avg_digit_ratio) / doc_std_digit_ratio

# Вычисляем статистику по документу
doc = fitz.open("report.pdf")
ratios = []
for page in doc:
    text = page.get_text()
    if len(text) > 0:
        ratios.append(sum(1 for c in text if c.isdigit()) / len(text))
doc_avg = np.mean(ratios)
doc_std = np.std(ratios)

# Проверяем каждую страницу
for i, page in enumerate(doc):
    score = char_anomaly_score(page, doc_avg, doc_std)
    if score > 2.0:
        print(f"Страница {i+1}: аномалия символов, score={score:.2f}")

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

Эвристика 2: обнаружение пустых bbox изображений

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

def has_empty_image_bbox(page, min_image_area_ratio=0.3):
    text_blocks = page.get_text("blocks")
    images = page.get_images(full=True)
    
    for img in images:
        xref = img[0]
        # Получаем bbox изображения через обёртку
        img_rects = page.get_image_rects(xref)
        if not img_rects:
            continue
        
        for rect in img_rects:
            bbox = rect[0]  # fitz.Rect
            page_area = page.rect.width * page.rect.height
            img_area = bbox.width * bbox.height
            
            if img_area / page_area < min_image_area_ratio:
                continue  # Слишком маленькое изображение
            
            # Проверяем, есть ли текст внутри bbox
            text_inside = False
            for block in text_blocks:
                block_rect = fitz.Rect(block[:4])
                if bbox.intersects(block_rect):
                    text_inside = True
                    break
            
            if not text_inside:
                return True  # Найдено изображение без текста
    
    return False

Параметр min_image_area_ratio=0.3 отсекает мелкие иконки и логотипы. Если изображение занимает менее 30% страницы, оно вряд ли содержит критичные данные. Для документов с большими диаграммами порог можно снизить до 0.2.

Эвристика 3: кластеризация координат строк для детекции колонок

Собираем x-координаты левого края всех текстовых блоков на странице. Применяем кластеризацию KMeans с K=2. Если расстояние между центрами кластеров превышает 100 пунктов - страница многоколоночная.

from sklearn.cluster import KMeans

def detect_columns(page, min_cluster_distance=100):
    blocks = page.get_text("blocks")
    x_coords = [b[0] for b in blocks if b[6] == 0]  # b[0] - x0, b[6] - тип блока (0=текст)
    
    if len(x_coords) < 10:
        return False
    
    x_coords = np.array(x_coords).reshape(-1, 1)
    kmeans = KMeans(n_clusters=2, n_init=10, random_state=42)
    kmeans.fit(x_coords)
    
    centers = sorted(kmeans.cluster_centers_.flatten())
    distance = centers[1] - centers[0]
    
    return distance > min_cluster_distance

Порог в 100 пунктов (около 35 мм на стандартном A4) разделяет одноколоночные и многоколоночные макеты. Для документов с широкими полями порог можно увеличить до 150. Метод не требует предварительного знания числа колонок - KMeans сам находит два кластера, если они есть.

Когда подключать платные модели: Azure Document Intelligence и vision LLM

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

Azure Document Intelligence (prebuilt-layout) оптимизирован для таблиц. Он возвращает структуру с номерами строк и колонок, объединёнными ячейками и заголовками. Стоимость - $1.50 за 1000 страниц. Для страниц с таблицами это лучший выбор: качество распознавания структуры выше, чем у vision LLM общего назначения.

Vision LLM (GPT-4V, Claude 3.5 Sonnet) нужны для рисунков с текстом и нестандартных макетов. Они понимают контекст, могут описать диаграмму словами или извлечь текст из скриншота. Стоимость выше - $0.01-0.03 за изображение, но для 10% страниц документа это всё равно дешевле, чем обработка всего PDF.

Правило выбора:

  • Сработала эвристика 1 (аномалия символов) → Azure Document Intelligence.
  • Сработала эвристика 2 (пустой bbox) → vision LLM.
  • Сработала эвристика 3 (многоколоночность) → Azure Document Intelligence, если в колонках таблицы; vision LLM, если текст.

Для сравнения: обработка 500-страничного отчёта целиком через Azure Document Intelligence стоит $0.75. Каскадный подход отправляет в платный парсинг 50 страниц (10%) и платит $0.075. Экономия - 90%. На дистанции в 1000 документов разница составляет $675.

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

Ограничения и подводные камни: когда эвристики ошибаются

Каскад не гарантирует 100% точности. Ложные срабатывания и пропуски неизбежны, но их можно минимизировать калибровкой порогов.

Ложные срабатывания эвристики 1: страница с прайс-листом, где много цифр, но нет табличной структуры. Решение - добавить проверку на наличие линий или сетки через анализ векторной графики. Страница с фотографией, где bbox изображения пуст, но данные не нужны. Решение - проверять расширение изображения: JPEG и PNG обычно фотографии, а EMF и WMF - диаграммы из офисных пакетов.

Пропуски: таблица с текстовыми ячейками без цифр (например, расписание) не даст аномалии символов. Одноколоночный документ со сносками, где координаты строк скачут из-за надстрочных индексов. Решение для второго случая - фильтровать блоки по высоте: сноски обычно мельче основного текста.

Рекомендация: начинайте с консервативных порогов (2.5 сигмы для символов, 0.4 для площади изображений, 150 пунктов для колонок). Прогоните каскад на 50-100 документах из вашего корпуса, вручную проверьте расхождения. За две-три итерации вы подберёте пороги, которые дают менее 5% ошибок.

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

Практический пример: адаптивный парсинг финансового отчёта

Возьмём квартальный отчёт компании на 45 страниц. Состав: 30 страниц текста с описанием бизнеса, 10 страниц с таблицами финансовых показателей, 5 страниц с диаграммами выручки по сегментам.

Прогоняем через каскад:

  • Эвристика 1 (символы): 12 страниц показали аномалию. 10 из них - таблицы, 2 - страницы с перечислением числовых KPI в текстовом виде (ложное срабатывание).
  • Эвристика 2 (пустые bbox): 5 страниц с диаграммами обнаружены. Все 5 - корректно.
  • Эвристика 3 (колонки): 0 страниц - отчёт одноколоночный.

Итог: 15 страниц из 45 отправлены в платный парсинг (33%). 30 страниц обработаны PyMuPDF локально.

Сравнение затрат:

  • Полный парсинг через Azure Document Intelligence: 45 страниц × $0.0015 = $0.0675.
  • Каскадный подход: 15 страниц × $0.0015 = $0.0225.
  • Экономия: 67%.

Для одного отчёта разница незаметна. Но для ежеквартальной обработки 500 отчётов экономия составляет $22.50 против $33.75 - разница в $11.25 за квартал, или $45 в год. При добавлении vision LLM для диаграмм (5 страниц × $0.01 = $0.05) полная стоимость одного отчёта через каскад - $0.0725, что всё ещё сопоставимо с полным Azure-парсингом, но качество извлечения данных из диаграмм выше.

Ключевой вывод: каскад не всегда даёт драматическую экономию на маленьких документах. Его преимущество раскрывается на масштабе - тысячи страниц, сотни документов, где 70-90% контента не требуют платного парсинга. Для разовых задач проще заплатить за весь документ. Для production-пайплайнов каскад окупается за первую неделю.

Если вы проектируете пайплайн обработки документов и хотите понять, какие инструменты выбрать для разных типов контента, изучите возможности Hugging Face AI Sheets для бескодового извлечения данных из изображений. Там показано, как комбинировать OCR и визуальный анализ в одном интерфейсе без программирования.

Подписаться на канал