25 KiB
Импорт банковских выписок за последний год
Создано: 2026-06-23 Цель: Получить все транзакции за последний год (середина 2025 — июнь 2026) в Budget App, автоматизируя импорт банковских выписок как можно полнее.
Проблема
В Budget.xlsx данные заканчиваются в середине 2025 (май-июнь 2025, в зависимости от счёта). Последние ~12 месяцев транзакций не внесены в Excel. Вручную вспомнить каждую трату за год — нереалистично.
Существующий инструмент: budget-bank-statement-converter
Путь: ~/Developer/budget-bank-statement-converter/
Язык: Swift (macOS command-line tool)
Формат вывода: CSV с колонками [дата, сумма, дебет, кредит, категория, комментарий, курс] — совпадает с форматом Excel.
Поддерживаемые банки
| Банк | Формат входа | Конфиг | Статус |
|---|---|---|---|
| Demir | CSV (из PDF → Adobe Extract → CSV) | demir-config.json |
✅ Работает |
| Сбер | CSV (выгрузка из СберБизнес) | sber-config.json |
✅ Работает |
| Тинькофф | CSV (выгрузка из Тинькофф) | tinkoff-config.json |
✅ Работает |
| ВТБ | CSV (из PDF → Adobe Extract → CSV) | vtb-config.json |
✅ Работает |
| Альфа | — | — | ❌ fatalError("Alfa not implemented") |
Как работает
- PDF → CSV: использует Adobe PDF Extract API (
pdfservices-api-credentials.json) — загружает PDF, получает ZIP с CSV-таблицами. - CSV → формат App: разбирает CSV, маппит категории через regex-конфиг, нормализует double-entry (дебет/кредит/конверсии).
- Если нужно — автоматически конкатенирует
fileoutpart0001.csv… файлы. - Использует OpenAI GPT-3.5-turbo для AI-категоризации (закомментировано,
aiMaxTokens = 80).
Что нужно для использования
- Xcode (для сборки Swift-проекта)
OPENAI_API_KEYв env (не обязательно, выключено)pdfservices-api-credentials.jsonдля Adobe Extract- JSON config для каждого банка:
Сбер config,Tinkoff config,Demir config,VTB config
План импорта
Шаг 1: Получить выписки из банков
Что нужно выгрузить за июнь 2025 — июнь 2026:
| Счёт | Банк | Как получить выписку |
|---|---|---|
| Нал RUB | Наличные | Ручной ввод (см. ниже про наличные) |
| Нал KGS | Наличные | Ручной ввод |
| Нал USD | Наличные | Ручной ввод |
| Нал KZT | Наличные | Ручной ввод |
| Demir ИП | Demir | CSV (интернет-банк/моб. приложение) |
| Demir ИП USD | Demir | CSV (интернет-банк/моб. приложение) |
| Demir KGS | Demir | CSV (интернет-банк/моб. приложение) |
| Demir USD | Demir | CSV (интернет-банк/моб. приложение) |
| Тинькофф Black | Тинькофф | CSV (выгрузка из Тинькофф) |
| Тинькофф Кредитка | Тинькофф | CSV (выгрузка из Тинькофф) |
| Сбер | Сбер | CSV (СберБизнес / PDF) |
| Сбер Кредитка | Сбер | CSV (СберБизнес / PDF) |
| Альфа | Альфа | CSV — но конвертер Альфу не поддерживает |
| Альфа Кредитка | Альфа | — |
| ВТБ | ВТБ | PDF → Adobe Extract → CSV |
| ВТБ Кредитка | ВТБ | PDF → Adobe Extract → CSV |
Шаг 2: Конвертировать выписки в CSV формата App
Запуск для каждого банка:
./budget-bank-statement-converter --bank <bank> --account <account> <input.csv>
Выход: _processed.csv с колонками дата, сумма, дебет, кредит, категория, комментарий, курс.
Шаг 3: Написать Python импортёр CSV → Budget App DB
Существующий xlsx_import.py читает из Excel. Нужен новый: csv_bank_import.py, который:
- Читает CSV в формате App (колонки из
csvHeadersвCommon.swift) - Привязывает
дебет/кредитк существующим счетам в БД (по имени) - Маппит категории из CSV на существующие категории в БД (по имени подкатегории)
- Игнорирует дубликаты (hash по
date + amount + source + dest + comment) - Поддерживает несколько CSV-файлов за раз (много выписок)
- Выводит отчёт: сколько добавлено, сколько пропущено (дубликаты), какие категории не найдены
Шаг 4: Импортировать в Budget App
cd ~/Developer/budget-app
uv run python src/budget/importers/csv_bank_import.py <output1.csv> <output2.csv> ...
Шаг 5: Дописать недостающее в Swift-конвертере
- AlfaToCSV: реализовать парсер для Альфа-банка (CSV выгрузка из моб. банка/СберБизнес)
- AI-категоризация: раскомментировать и обновить (GPT-3.5 → DeepSeek/local LLM?)
Наличные расходы — проблема и решение
Проблема
Наличные траты не трекались последний год. У нас есть конечный остаток налички на руках сейчас, но нет истории по категориям.
Подходы
A. Снять остаток наличных сейчас → счёт в БД (простой)
- Посчитать физическую наличку сейчас → записать как
initial_balanceдляНал RUB,Нал KGS,Нал USD,Нал KZT. - Все траты наличными за год никогда не будут зафиксированы.
- Минус: дыра в данных большого объёма (вероятно значительная часть расходов).
B. Экстраполяция по историческим трендам (средний)
- Взять помесячные тренды наличных трат по категориям за 2023–первую половину 2025.
- Экстраполировать на июнь 2025 — июнь 2026 с учётом сезонности.
- Создать транзакции-плейсхолдеры с пометкой
import_id = 'cash_estimate'. - Минус: неточность, может не отражать реальные изменения.
C. Ретроспектива через месяц-два + экстраполяция (предпочтительный)
- Сейчас: начать трекать наличные расходы (вручную или через мобильный интерфейс Budget App).
- Через 1–2 месяца: по собранным данным наличных трат вычислить реальные помесячные паттерны.
- Экстраполировать на пропущенный год с этими паттернами.
- Плюс: база для экстраполяции будет основана на реальных данных, а не на исторических.
D. None of the above — принять дыру
- Сделать только безналичный импорт. Наличные начинаем трекать с сегодня.
- В аналитике отмечать периоды как "без наличных".
- Плюс: не надо ничего выдумывать.
Рекомендация: C+D combined
- Трекать наличку вручную через UI Budget App начиная с сегодня.
- Через 2 месяца посчитать реальные тренды и решить, стоит ли экстраполировать на прошлый год.
- Если нет — просто принять дыру и жить с хорошей аналитикой начиная с 2026-06.
Что уже реализовано в Budget App для импорта
- ✅
xlsx_import.py— полный импорт из Budget.xlsx (34k строк) - ✅ Все счета, категории, курсы, транзакции — в БД
- ✅ Идемпотентный UPSERT для счетов и курсов
- ✅ Транзакции добавляются обычным insert (без import_hash после фикса)
Реализованный скрипт: bank_scraper
Путь: ~/Developer/budget-app/scripts/bank_scraper/
Структура:
scripts/bank_scraper/
├── __init__.py
├── .gitignore # config.yaml + data/imports/ не коммитятся
├── config.example.yaml # шаблон для копирования в config.yaml
├── base_driver.py # base class BankDriver + load_config()
├── orchestrator.py # entry point (н.п.)
└── drivers/
└── demir.py # Demir IB драйвер (н.п.)
Статус: ✅ base + Demir driver написаны, Playwright установлен. НО — Demir требует QR-логин через мобильное приложение, не логин/пароль на сайте.
Match placeholder transactions
Проблема
Ручной ввод расходов с debit/credit счетов через CashInput создаёт placeholder-транзакции (is_placeholder=true). У них есть категория и комментарий, но нет реального подтверждения из банка. Когда выписка импортируется, те же траты появляются как новые транзакции из банка — без категории и комментария. Нужно сопоставить плейсхолдеры с реальными транзакциями.
Механика матчинга
При импорте CSV-выписки скрипт (будущий csv_bank_import.py или orchestrator) выполняет:
- Для каждой строки из выписки найти плейсхолдеры в БД с теми же:
source_account_id(тот же счёт)is_placeholder = true- Сумма совпадает с точностью ±0.01
- Дата в пределах ±3 дней от даты выписки
- Если найден ровно 1 плейсхолдер:
UPDATE transaction SET is_placeholder = false, import_id = '<import_id>', category_id = <placeholder.category_id>, comment = <placeholder.comment> WHERE id = <placeholder.id>- Реальная транзакция не создаётся — плейсхолдер становится постоянным с перенесёнными категорией и комментарием
- Если найдено несколько плейсхолдеров:
- Создать реальную транзакцию как есть
- Пометить плейсхолдеры как "требуют ручного разрешения" (флаг в будущем через TG-бот)
- Если не найден ни один:
- Создать обычную транзакцию (is_placeholder=false, категория не указана)
SQL для матчинга (ориентир)
-- Найти плейсхолдер для строки выписки
SELECT id, category_id, comment
FROM transaction
WHERE user_id = $user_id
AND source_account_id = $source_account_id
AND is_placeholder = true
AND ABS(amount - $amount) < 0.01
AND ABS(EXTRACT(EPOCH FROM (date - $date))) < 259200 -- ±3 дня в секундах
ORDER BY ABS(amount - $amount), ABS(EXTRACT(EPOCH FROM (date - $date)))
LIMIT 1
Реальность Demir IB
Сайт 93.171.215.109 (и apps.demirbank.kg/ib/) — Flutter web SPA с QR-аутентификацией. Нет формы логина с паролем — нужно сканировать QR мобильным приложением Demir.
Варианты решения:
A. Продолжить с Playwright + session persistence
- Один раз залогиниться руками (QR → моб. приложение)
- Сохранить session cookies/storage в persistent context
- Дальше переиспользовать сессию для выгрузок (пока не протухнет)
- Плюс: минимум кода
- Минус: сессия рано или поздно протухнет, нужен ручной ре-логин
B. Appium / ADB — эмуляция мобильного приложения
- Демонстратор Android/iOS эмулятора с мобильным приложением Demir
- Appium для UI automation внутри приложения
- Плюс: полный контроль
- Минус: сложно, накладно
C. Заменить Demir на первый банк с логином/паролем
- Тинькофф имеет API для разработчиков (OAuth)
- Сбер — есть API SberBusinessAPI (хотя для юрлиц)
- Можно начать с Тинькофф: Tinkoff API → выписка без браузера
- Плюс: самый простой tech-wise
- Минус: Demir пока под вопросом
D. Парсить CSV выписки, которые уже есть в mobile/email
- Возможно Demir присылает выписки на email
- Или можно скачать через мобильное приложение → экспорт → AirDrop/email себе
- Это полу-ручной подход (но быстрее чем QR scraping)
Решение
Рекомендация: A + D
- Самый ценный банк — Тинькофф (есть API) — начинаем с него
- Demir — разово выгрузить через мобильное приложение (Export CSV/email)
- Если сессия Demir долго живёт — Playwright persistent context отработает
Новый порядок разработки
- ✅ Demir driver (написан, но упирается в QR)
- Tinkoff API driver — следующий приоритет (без браузера, REST API)
- Сбер / ВТБ / Альфа — Playwright или Tinkoff-style API
- Parse Demir CSV — Python-версия DemirToCSV для уже скачанных файлов
Файл вывода Swift-конвертера
csvHeaders = ["дата", "сумма", "дебет", "кредит", "категория", "комментарий", "курс"]
дата—dd.MM.yyyy HH:mm(формат EUR)сумма— строка с суммой (±знак)дебет— имя счёта-источника (пусто = доход извне)кредит— имя счёта-получателя (пусто = расход вовне)категория— имя подкатегориикомментарий— очищенный тексткурс— кросс-курс при внутреннем переводе между валютами
Автоматизация выгрузки выписок из банков
1. Browser automation libraries (CV-driven)
| Библиотека | Язык | Браузеры | CV | 2FA/SMS |
|---|---|---|---|---|
| Playwright (MS) | Python, JS, Java, .NET ⭐ | Chromium, Firefox, WebKit | Есть (locator screenshots) | page.wait_for_selector на поле ввода кода |
| Puppeteer (Google) | JS (Python через pyppeteer) | Chromium | Есть | — |
| Selenium | Python, Java, JS и др. | Все major | Через сторонние утилиты | — |
Рекомендация: Playwright Python — де-факто стандарт в 2025, cross-browser, async, видит элементы даже в SPA, встроенные ожидания. Подходит и для РФ-банков (Сбер, Тинькофф, Альфа-клик — все на SPA).
2. Готовые решения на GitHub
AploBankParsers (github.com/Zaurrex1/AploBankParsers):
- Парсер выписок СберБизнес (production-ready) — читает xlsx/сsv из уже выгруженного файла
- Заглушки для Альфа, ВТБ, Тинькофф
- Это парсер уже скачанных файлов, не скрапер
bank_scrapers (github.com/eebette/bank_scrapers):
- Playwright-based для scraping bank websites
- Generic, не специфичен под РФ-банки
Sber API — официальный REST API Сбера:
developers.sber.ru/docs/ru/sber-api/specifications/statement/transactions- Получение выписки по счёту за 5 лет
- Требует корпоративного доступа (SberBusinessAPI / ДБО), не подойдёт для личного СберБанк
Готового решения "под ключ" для РФ-банков (Playwright → bank login → 2FA → CSV выписка) нет в открытом доступе. Каждый банк — свой уникальный UI и flow. Придётся писать самим.
3. Архитектура скрипта
┌─────────────────────────────────┐
│ Telegram Bot (Hermes/кит) │ ← запрашивает SMS-код
├─────────────────────────────────┤
│ Orchestrator (Python) │ ← запускает по крону / кнопке
│ ┌─────────────────────────┐ │
│ │ Playwright browser │ │ ← drives bank login page
│ │ - headless=false │ │ (visible для отладки)
│ │ - persistent context │ │ (сессия не слетает)
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ Bank drivers: │ │
│ │ - tinkoff.py │ │
│ │ - sber.py │ │
│ │ - alfa.py │ │
│ │ - demir.py │ │
│ │ - vtb.py │ │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ Output: CSV в формате │ │
│ │ budget-bank-statement- │ │
│ │ converter │ │
│ └─────────────────────────┘ │
└─────────────────────────────────┘
4. Flow для каждого банка
1. Запустить headless Playwright (или visible=False для отладки)
2. Открыть страницу логина банка
3. Ввести credentials (из конфига, НЕ скрипта)
4. Если запрошен SMS-код:
→ отправить в Telegram: "Код из смс для {bank}:"
→ ждать ответа (polling/async)
→ ввести полученный код
5. Дождаться загрузки дашборда
6. Перейти на страницу выписок/истории
7. Указать период: 2025-06-01 — 2026-06-23
8. Скачать CSV/Excel
9. Сохранить в ~/Developer/budget-app/data/imports/{bank}/{date}.csv
10. Конвертировать через budget-bank-statement-converter (или Python-версию)
11. Импортировать в БД
12. Закрыть браузер
5. Обработка SMS-кодов (Telegram)
Скрипт не должен хранить сессию банка, каждый запуск — новая авторизация.
Варианты:
- Telegram Bot (inline keyboard): скрипт ждёт сообщение, когда нужен код — присылает кнопку "Отправить код для {bank}", пользователь вводит → скрипт вставляет
- Hermes-агент: крон-джоб спрашивает в Telegram нужный код, ждёт ответа через webhook
- Простой stdin: скрипт пишет "Введите код для Тинькофф:" и ждёт ввод (если запуск из терминала)
Рекомендация: вариант 1 — TG bot минимальная зависимость, полный контроль.
Для реализации: существующий Hermes/Zulip может служить relay. Или простой скрипт на Python + python-telegram-bot с await incoming_message.
6. Чувствительность данных — ограничения
Скрипт будет:
- Знать логины/пароли банков (хранятся в локальном конфиге, НЕ в коде)
- Открывать браузер на машине Алекса (никаких VPN/прокси)
- Передавать только SMS-коды через TG — пароли не передаются
- Работать локально, без LLM/агентов в browser automation
Код пишем так, чтобы ни одна строка credentials не была в скрипте:
# config.yaml (chmod 600)
banks:
tinkoff:
login: "7999..."
password: "..."
phone: "7999..."
sber:
login: "..."
password: "..."
7. Альтернатива: API банков (без browser)
| Банк | REST API для личных счетов | Комментарий |
|---|---|---|
| Тинькофф | Есть (Tinkoff API для разработчиков) | Требует регистрации приложения, OAuth |
| Сбер | Sber API для юрлиц, нет для личных | Не подходит |
| Альфа | Альфа-Бизнес API (юрлица) | Не подходит |
| Demir | Нет публичного API | — |
| ВТБ | Нет публичного API | — |
Тинькофф — единственный из списка, у кого есть адекватный API для физлиц (Tinkoff API / Tinkoff Invest API). Можно получить выписку через API, без browser. Остальные — только SPA scraping.
Код: 10 swift-файлов, ~2 400 строк.
Что хорошо:
- Хорошая архитектура: каждый банк = отдельный struct с чётким интерфейсом
- Конфиги вынесены из кода (JSON)
- Regex-маппинг категорий гибкий
- Умеет объединять multi-part CSV и извлекать из PDF через Adobe API
- Формат вывода совпадает со структурой Excel/Budget App
Чего не хватает:
- Парсер Альфа-банка (только заглушка)
- AI-категоризация закомментирована (GPT-3.5, устарела)
- Нет интеграции с Budget App (только → CSV, не → БД)
- Нет обработки для Demir ИП USD / Demir USD / Demir KGS отдельно (DemirToCSV один конфиг на все)
- PDF-парсер привязан к Adobe PDF Extract API (платный сервис, credentials нужны)
- Нет обработки для Сбер Кредитка как отдельного счёта (SberToCSV один конфиг)
- Нет автоматического определения новых форматов CSV от банков