Нейросеть для перевода PDF в Excel: таблицы без ручного копирования

Разбираем рабочий конвейер: достаём таблицу из PDF, просим модель вернуть JSON, пишем в xlsx и сверяем итоги, чтобы не поймать тихую ошибку.

4 мин · · автоматизация · разработка

Если у вас десятки счетов, выписок или прайс-листов в PDF, ручное копирование в таблицу съедает вечер. Нейросеть для перевода PDF в Excel решает это за несколько строк кода, но только если не доверять ей вслепую: модель может пропустить строку или перепутать запятую в сумме. Ниже конвейер, в котором результат проверяется арифметикой, и расчёт стоимости на страницу.

Два вида PDF и почему это важно

Сначала определите, что лежит внутри файла.

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

Быстрая проверка в коде: если extract_text() вернул пустую строку или мусор, страница скорее всего является картинкой.

Шаг 1. Достаём текст или картинку

Для текстового слоя подойдёт pdfplumber, для растеризации страниц PyMuPDF:

bash
pip install pdfplumber pymupdf openpyxl openai
bash
import base64
import pdfplumber
import fitz  # PyMuPDF


def page_text(path: str, page_no: int) -> str:
    with pdfplumber.open(path) as pdf:
        return pdf.pages[page_no].extract_text() or ""


def page_png_b64(path: str, page_no: int, dpi: int = 150) -> str:
    doc = fitz.open(path)
    pix = doc[page_no].get_pixmap(dpi=dpi)
    return base64.b64encode(pix.tobytes("png")).decode("ascii")

Разрешение 150 dpi обычно хватает для печатного текста. Для мелкого шрифта поднимайте, но помните: больше картинка, больше входных токенов.

Шаг 2. Просим модель вернуть JSON

Главное правило: не просите «сделай таблицу», просите строго заданную структуру. Так результат легко проверить кодом.

bash
import json
from openai import OpenAI

client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_ваш_ключ")

PROMPT = """Извлеки из страницы таблицу позиций. Верни только JSON без пояснений:
{"rows": [{"name": "...", "qty": 0, "price": 0.0, "total": 0.0}],
 "document_total": 0.0}
Числа пиши как числа с точкой, без пробелов и символа валюты.
Если значения нет в документе, ставь null. Ничего не придумывай."""


FENCE = "`" * 3  # обрамление markdown, если модель его добавила

Для текстового пути взята deepseek-v4.1-flash: она без картинок, зато самая дешёвая в нашей таблице. Для сканов нужна модель с картинками на вход, например claude-sonnet-5-5 или gpt-6.1-sol. Подробнее про форматы запросов в документации по chat completions.

Шаг 3. Проверяем суммы до записи в файл

Это самый важный шаг. Модель могла ошибиться в одной цифре, а таблица выглядит убедительно.

bash
from decimal import Decimal


def validate(data: dict) -> list[str]:
    problems = []
    rows = data["rows"]
    for i, row in enumerate(rows):
        if None in (row["qty"], row["price"], row["total"]):
            problems.append(f"строка {i}: пустое значение")
            continue
        calc = Decimal(str(row["qty"])) * Decimal(str(row["price"]))
        if abs(calc - Decimal(str(row["total"]))) > Decimal("0.01"):
            problems.append(f"строка {i}: {calc} != {row['total']}")
    s = sum(Decimal(str(r["total"])) for r in rows if r["total"] is not None)
    doc_total = data.get("document_total")
    if doc_total is not None and abs(s - Decimal(str(doc_total))) > Decimal("0.01"):
        problems.append(f"сумма строк {s} не равна итогу {doc_total}")
    return problems

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

Шаг 4. Пишем xlsx через openpyxl

bash
from openpyxl import Workbook


def write_xlsx(pages: list[dict], out_path: str) -> None:
    wb = Workbook()
    ws = wb.active
    ws.append(["Наименование", "Кол-во", "Цена", "Сумма"])
    for data in pages:
        for r in data["rows"]:
            ws.append([r["name"], r["qty"], r["price"], r["total"]])
    last = ws.max_row
    ws.append(["Итого", None, None, f"=SUM(D2:D{last})"])
    wb.save(out_path)

Формула SUM в конце даёт второй независимый контроль: сравните её результат с итогом из PDF глазами.

Сколько это стоит

Цены на 18 октября 2026, долларов за 1M токенов. Формула: вход × цена входа + выход × цена выхода.

Текстовая страница, deepseek-v4.1-flash (0.013 / 0.038). Допустим, 1500 токенов на входе и 800 на выходе:

1500 × 0.013 / 1 000 000 + 800 × 0.038 / 1 000 000 = 0.0000195 + 0.0000304 ≈ $0.00005

На 100 страниц выходит около $0.005.

Страница-картинка, claude-sonnet-5-5 (0.4 / 2). Сколько токенов займёт изображение, зависит от размера, поэтому возьмём допущение 1600 токенов входа и 800 выхода:

1600 × 0.4 / 1 000 000 + 800 × 2 / 1 000 000 = 0.00064 + 0.0016 = $0.00224

На 100 страниц около $0.22. Реальное число токенов на картинку смотрите в поле usage ответа и замерьте на трёх-пяти своих страницах, прежде чем запускать пачку. Актуальные цены: GET https://api.zukko.pro/v1/models или раздел «Модели» в кабинете.

Практические советы

  • Не отправляйте в модель персональные данные, если они не нужны для задачи: шаблоны часто содержат ФИО и реквизиты.
  • Многостраничные таблицы режьте по страницам и склеивайте в коде, а не просите модель обработать 80 страниц сразу.
  • Сложные многоуровневые шапки и объединённые ячейки лучше описать в промпте словами: какие колонки есть и в каком порядке.
  • Разбор документов других типов описан в статьях про обработку PDF через vision и про извлечение данных из документов.

Ключ создаётся в кабинете, код выше работает с ним без изменений. Базовый пример на Python есть в документации.

Частые вопросы

Можно ли обойтись без кода?

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

Что делать со сканами плохого качества?

Поднимите dpi, обрежьте поля и повторите. Если модель отвечает null, это честнее, чем выдуманное значение: такие строки проверьте вручную.

Какая модель точнее?

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

Сколько страниц можно обработать за раз?

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

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

Нейросеть для перевода PDF в Excel: код и проверка · Zukko