Как извлечь таблицы из большого PDF и проверить цифры
Практический порядок извлечения таблиц из PDF через Gemini API: проектирование схемы с null, выбор Files API, запрос цитат и номеров страниц как подсказок и сверка данных с оригиналом перед экспортом.
Содержание

Когда в обработку поступает сложный PDF-документ — например, многостраничный отчет об инспекции оборудования или финансовая ведомость, — он редко состоит из однородного текста. В реальных файлах цифровые страницы соседствуют со сканами, таблицы не имеют явных разделителей, а важные показатели прячутся в сносках.
Передать такой файл в мультимодальную модель и сразу записать результат в базу данных — опасный путь. Модель остается вероятностной системой, поэтому надежный рабочий процесс строится не на слепом доверии к ответу, а на предварительном проектировании схемы, сборе контекстных подсказок и последующей сверке данных с оригиналом.
1. Схема данных: фиксируем поля и разрешаем null
Механизм Gemini Structured Outputs обеспечивает соответствие формата ответа заданной схеме: на выходе формируется синтаксически корректный JSON. Если поле объявлено числовым, в нем не появится посторонний текст.
Однако синтаксическая структура JSON защищает лишь от ошибок формата, но не гарантирует смысловой точности:
- Модель может перепутать соседние строки или столбцы в плотной таблице;
- На нечетком скане цифра
8легко превращается в3, а десятичная точка может потеряться; - Если показатель отсутствует или замазан, модель без явного разрешения на пропуск может попытаться подобрать правдоподобное значение.
Чтобы снизить риск домысливания, в схеме важно объявлять поля допускающими отсутствие значения (Optional или null), а в промпте явно указывать требование возвращать null, если число не читается надежно. Это помогает уменьшить вероятность галлюцинаций, хотя само по себе не дает абсолютной гарантии их отсутствия.
2. Передача файла: когда выбирать Files API
Согласно документации по обработке документов Gemini, установлены ограничения: до 50 МБ или до 1000 страниц на один PDF-файл (действуют ограничения как по размеру, так и по числу страниц, без гарантии одновременного достижения обоих максимумов — обработка упрется в тот лимит, который будет достигнут первым).
Способ передачи документа зависит от размера файла и задачи:
- Прямая передача данных в запросе (inline) подходит для небольших документов и разовых обращений.
- Files API (
client.files.upload) предназначен для более крупных файлов, а также для сценариев, где к одному и тому же документу обращаются несколько раз (например, сначала для классификации разделов, затем для извлечения конкретных таблиц). Это избавляет от повторной передачи тела документа.
3. Запрос данных: схема, цитаты-подсказки и гипотетический ответ
Чтобы результат можно было проверить, помимо целевых значений у модели запрашивают метаданные: ориентировочный номер страницы (page_number) и короткую дословную цитату (evidence_quote).
Важно помнить:
page_numberиevidence_quote— это не доказательства факта, а исключительно эвристические ориентиры для поиска. Модель сама генерирует эти поля: цитата может быть склеена с ошибкой или взята из соседнего абзаца, а номер страницы в визуальном слое может не совпадать с порядковым индексом листа в PDF.
Гипотетический пример задачи
Представим гипотетическую иллюстрацию задачи (реальный PDF-файл не предоставлялся, не загружался и не исследовался, а реальный API-запрос не выполнялся): смоделируем извлечение сводных показателей из гипотетического отчета по насосным агрегатам. В иллюстративном примере фигурирует гипотетическая таблица из двух строк:
| Идентификатор | Давление (МПа) | Вибрация (мм/с) | Статус | Примечание |
|---|---|---|---|---|
| Н-101-А | 1.45 | 2.1 | В норме | Плановый осмотр |
| Н-102-В | (неразборчиво) | 7.8 | Внимание | Повышенный люфт |
Ниже приведен пример описания схемы на Pydantic и синтаксиса вызова официального SDK:
from google import genai
from pydantic import BaseModel, Field
from typing import List, Optional
class PumpRecord(BaseModel):
unit_id: str = Field(
description="Идентификатор агрегата точно как в таблице"
)
inlet_pressure_mpa: Optional[float] = Field(
default=None,
description="Давление в МПа. Если значение неразборчиво или отсутствует — null"
)
vibration_mms: Optional[float] = Field(
default=None,
description="Уровень вибрации в мм/с. При отсутствии данных — null"
)
status: str = Field(
description="Статус узла (например, 'В норме', 'Внимание')"
)
page_number: Optional[int] = Field(
default=None,
description="Оценочный номер страницы документа (подсказка для аудитора, не подтверждена)"
)
evidence_quote: Optional[str] = Field(
default=None,
description="Короткий фрагмент строки (до 10 слов), откуда взяты числа (подсказка, не подтверждена)"
)
class InspectionPayload(BaseModel):
records: List[PumpRecord]
client = genai.Client()
uploaded_file = client.files.upload(file="hypothetical_inspection.pdf")
response = client.interactions.create(
model="gemini-3.8-flash",
input=[
{
"type": "document",
"uri": uploaded_file.uri,
"mime_type": uploaded_file.mime_type,
},
{
"type": "text",
"text": (
"Извлеки показатели агрегатов в соответствии со схемой. "
"Если число неразборчиво или отсутствует, возвращай null. "
"Для каждой записи заполни номер страницы и короткую цитату-подтверждение."
),
},
],
response_format={
"type": "text",
"mime_type": "application/json",
"schema": InspectionPayload.model_json_schema(),
},
)
payload = InspectionPayload.model_validate_json(response.output_text)
Иллюстративный JSON-ответ модели
Следующий JSON иллюстрирует гипотетический ответ модели на подобный запрос. Подчеркнем: данный вывод является гипотетической иллюстрацией структуры, а не результатом фактического выполнения запроса или реальным измерением API:
{
"records": [
{
"unit_id": "Н-101-А",
"inlet_pressure_mpa": 1.45,
"vibration_mms": 2.1,
"status": "В норме",
"page_number": 12,
"evidence_quote": "Н-101-А 1.45 2.1 В норме"
},
{
"unit_id": "Н-102-В",
"inlet_pressure_mpa": null,
"vibration_mms": 7.8,
"status": "Внимание",
"page_number": 12,
"evidence_quote": "Н-102-В [пятно] 7.8 Внимание"
}
]
}
В этом гипотетическом ответе оба поля page_number: 12 и обе цитаты evidence_quote имеют статус неверифицированных подсказок (unverified hints). Модель вернула null для неразборчивого давления второго насоса, однако ни одно из значений a priori не считается подтвержденным фактом.
4. Сверка с визуальным оригиналом и правила экспорта
Полученные данные нельзя сразу передавать в производственные базы данных. Необходим сквозной аудит каждого поля относительно визуального рендеринга исходной страницы PDF.
Важное уточнение о характере примера: Двухстрочная таблица, страница 12, физический сдвиг на 14-й лист, компрессорное отделение и процедура визуальной сверки представляют собой исключительно гипотетическую иллюстрацию процесса. Никакой реальный PDF-документ не предоставлялся и не инспектировался, а описанные ниже шаги отражают то, что рецензент проверял бы на практике, и формулируют условные решения на случай, если визуальный рендеринг подтвердит указанные значения.
Пошаговая сверка записей: что проверял бы рецензент
-
Агрегат
Н-101-А:- Страница и локализация: Модель указала подсказку
page_number: 12. Рецензент открывал бы визуальный рендеринг страницы 12 документа (если бы в файле присутствовал физический сдвиг на титульные листы, это мог бы оказаться, например, 14-й лист) и искал бы целевую таблицу компрессорного отделения. - Идентификатор: В первом столбце строки рецензент сверял бы идентификатор
Н-101-Ана предмет полного совпадения. - Давление и единицы: В столбце «Давление на входе (МПа)» рецензент проверял бы, различимо ли число
1.45и совпадают ли физические единицы с требованиями схемы (МПа). - Вибрация и единицы: В столбце «Вибрация (мм/с)» рецензент проверял бы наличие значения
2.1и совпадение единиц (мм/с). - Статус: В графе состояния проверялось бы указание
В норме. - Условное решение: Если рендеринг страницы подтверждает все поля, типы и единицы измерения, строка была бы одобрена к экспорту (Export / Принято).
- Страница и локализация: Модель указала подсказку
-
Агрегат
Н-102-В:- Страница и локализация: В той же гипотетической таблице рецензент переходил бы ко второй строке.
- Идентификатор: Проверялось бы наличие идентификатора
Н-102-В. - Вибрация и статус: Рецензент сопоставлял бы значение
7.8и статусВниманиес визуальным слоем. - Давление: Модель вернула
null. Рецензент исследовал бы соответствующую ячейку на рендеринге: если на месте числа действительно обнаруживается темное смазанное пятно (дефект сканирования), это подтвердило бы обоснованность возвратаnull, однако ключевой физический параметр при этом отсутствует. - Условное решение: Если рендеринг подтверждает отсутствие критически важного показателя давления, строка была бы заблокирована для автоматического экспорта и переведена в статус ручной проверки (Hold for manual review) с необходимостью запроса повторного скана или сверки с дублирующим журналом.
Проверенный результат сверки (Checked Output)
Итоговая таблица формулирует, что именно проверял бы рецензент и какое условное решение принимал бы конвейер валидации при подтверждении значений рендерингом:
| Агрегат | Давление (МПа) | Вибрация (мм/с) | Статус | Что проверял бы рецензент (гипотетическая сверка) | Условное решение конвейера (если рендеринг подтверждает значения) |
|---|---|---|---|---|---|
| Н-101-А | 1.45 | 2.1 | В норме | Проверил бы совпадение идентификатора, разрядов и единиц (МПа, мм/с) по рендерингу | Экспорт был бы разрешен (Готово к загрузке) — при условии полного визуального подтверждения всех полей |
| Н-102-В | null (пропуск) | 7.8 | Внимание | Проверил бы дефект скана (пятно) в ячейке давления и сопоставил бы значение вибрации | Строка была бы удержана (Ручной разбор оператором) — из-за подтвержденного отсутствия критического показателя |
Архитектурное правило валидации
Качественный пайплайн разделяет данные на два потока:
- Зеленый коридор (Verified Export): Только строки, где каждое обязательное поле подтверждено рендерингом оригинала, а единицы измерения строго приведены к эталону.
- Карантин (Review Queue): Любые строки с
nullв критических полях, расхождениями единиц или сомнительными цитатами задерживаются для ручной правки оператором.