↩️ Назад

Категории

Парсер писем для бухгалтерии

23.07.2026 | Статья из категории: python

Задача: автоматически забирать вложения из почтового ящика, сохранять их в папку и вести учёт — без нейросетей, только парсер.

Это первый шаг к AI-агенту для бухгалтерии. Сначала собираем документы, потом подключаем распознавание.

📁 Структура проекта

/tmp/invoices/
├── files/
│  ├── 2026-07-23_10-15_ooo_tehno_invoice.pdf
│  ├── 2026-07-23_10-20_ooo_business_act.pdf
│  └── ...
└── manifest.json ← описание всех файлов

manifest.json хранит метаданные каждого файла:

{
  "date": "2026-07-23",
  "total": 5,
  "files": [
    {
      "id": "inv_001",
      "filename": "2026-07-23_10-15_ooo_tehno_invoice.pdf",
      "path": "/tmp/invoices/files/2026-07-23_10-15_ooo_tehno_invoice.pdf",
      "size": 245760,
      "from": "ooo_tehno@mail.ru",
      "subject": "Счет-фактура №СФ-0042",
      "date": "2026-07-23T10:15:00",
      "status": "pending"
    }
  ]
}

🐍 Полный скрипт парсера

Подключается по IMAP, забирает непрочитанные письма, сохраняет вложения в папку и обновляет manifest.json.

#!/usr/bin/env python3
# parse_mail.py — парсер писем без нейросети

import imaplib
import email
import os
import json
import re
from datetime import datetime
from email.policy import default

# ====== КОНФИГ ======
IMAP_HOST = 'imap.yandex.ru'
EMAIL = 'your-email@yandex.ru'
PASSWORD = 'your-app-password'

BASE_DIR = '/tmp/invoices'
FILES_DIR = os.path.join(BASE_DIR, 'files')
MANIFEST_FILE = os.path.join(BASE_DIR, 'manifest.json')

os.makedirs(FILES_DIR, exist_ok=True)

# ====== ЗАГРУЖАЕМ MANIFEST ======
def load_manifest():
    if os.path.exists(MANIFEST_FILE):
        with open(MANIFEST_FILE, 'r') as f:
            return json.load(f)
    return {"date": datetime.now().strftime("%Y-%m-%d"), "total": 0, "files": []}

def save_manifest(manifest):
    with open(MANIFEST_FILE, 'w') as f:
        json.dump(manifest, f, indent=2, ensure_ascii=False)

# ====== БЕЗОПАСНОЕ ИМЯ ФАЙЛА ======
def safe_filename(from_addr, subject, date_str):
    match = re.search(r'@([\w-]+)\.', from_addr)
    org = match.group(1) if match else 'unknown'
    clean_subject = re.sub(r'[^\w\s-]', '', subject).replace(' ', '_')[:30]
    return f"{date_str}_{org}_{clean_subject}.pdf"

# ====== ПАРСИМ ПОЧТУ ======
def parse_mail():
    print("📬 Подключаемся к почте...")
    mail = imaplib.IMAP4_SSL(IMAP_HOST)
    mail.login(EMAIL, PASSWORD)
    mail.select('inbox')

    since_date = datetime.now().strftime("%d-%b-%Y")
    status, messages = mail.search(None, f'(UNSEEN SINCE "{since_date}")')

    if status != 'OK' or not messages[0]:
        print("❌ Нет новых писем за сутки")
        mail.close()
        mail.logout()
        return

    manifest = load_manifest()
    new_count = 0

    for msg_id in messages[0].split():
        _, data = mail.fetch(msg_id, '(RFC822)')
        raw = data[0][1]
        msg = email.message_from_bytes(raw, policy=default)

        from_addr = msg['From']
        subject = msg['Subject'] or 'no_subject'
        date_str = datetime.now().strftime("%Y-%m-%d_%H-%M")

        print(f"📨 От: {from_addr}")
        print(f"📌 Тема: {subject}")

        for part in msg.walk():
            if part.get_content_maintype() == 'multipart':
                continue
            if part.get_content_disposition() is None:
                continue

            filename = part.get_filename()
            if filename:
                ext = filename.split('.')[-1].lower()
                if ext in ['pdf', 'jpg', 'jpeg', 'png']:
                    safe_name = safe_filename(from_addr, subject, date_str)
                    filepath = os.path.join(FILES_DIR, safe_name)

                    with open(filepath, 'wb') as f:
                        f.write(part.get_payload(decode=True))

                    file_info = {
                        "id": f"inv_{len(manifest['files']) + 1:04d}",
                        "filename": safe_name,
                        "path": filepath,
                        "size": os.path.getsize(filepath),
                        "from": from_addr,
                        "subject": subject,
                        "date": datetime.now().isoformat(),
                        "status": "pending"
                    }
                    manifest['files'].append(file_info)
                    new_count += 1
                    print(f"   💾 Сохранен: {safe_name}")

    manifest['total'] = len(manifest['files'])
    save_manifest(manifest)

    print(f"✅ Сохранено файлов: {new_count}")
    mail.close()
    mail.logout()

if __name__ == '__main__':
    parse_mail()

🔧 Настройка

1. Пароль приложения

Для Яндекса / Google нужно создать пароль приложения, а не использовать основной пароль.

2. Запуск вручную

python3 parse_mail.py

3. Добавление в cron (каждые 5 минут)

*/5 * * * * /usr/bin/python3 /usr/local/bin/parse_mail.py >> /var/log/parse_mail.log 2>&1

🧠 Что дальше?

Когда парсер соберёт файлы, можно подключить распознавание:

📌 Преимущества такого подхода:
✅ Парсер и распознавание работают независимо
✅ Можно отлаживать по частям
✅ Если нейросеть ошиблась — просто сбрасываем статус
✅ Экономия ресурсов — нейросеть запускается только когда нужно

📝 Что можно улучшить

👍 Оцените статью
Всего голосов: 0



Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA