simple-email-parse 0.1.3
Библиотека для парсинга email переписки
License: MIT
Requires Python: >=3.10
Keywords: email, parser, html, conversation, mail, message

# Simple Email Parse

Библиотека для парсинга email-переписок из HTML-файлов. Извлекает структурированную информацию о сообщениях, включая заголовки (From, Sent, To, Subject) и текст сообщений.

## Установка

```bash
pip install simple-email-parse
```

## Быстрый старт

```python
from simple_email_parser import EmailParser

parser = EmailParser("email.htm", remove_img=True)

messages = parser.get_dict()
print(f"Найдено сообщений: {len(messages)}")

for msg in messages:
    if msg['header']:
        print(f"От: {msg['header']['From']['email']}")
        print(f"Дата: {msg['header']['Sent']}")
    print(f"Текст: {msg['text'][:100]}...")
    print("---")

parser.save_json("output.json")
```

## Архитектура

Библиотека состоит из трех основных компонентов:

### 1. EmailParser

Главный класс для парсинга email. Последовательно применяет HtmlProcessor и JsonProcessor для извлечения структурированных данных.

**Параметры:**
- `filepath` - путь к HTML-файлу
- `html` - HTML-строка (альтернатива filepath)
- `soup` - готовый BeautifulSoup объект (альтернатива filepath/html)
- `encoding` - кодировка файла (автоопределение: utf-8, windows-1251)
- `remove_img` - удалять изображения из текста
- `main_contact` - основной контакт для сообщений без заголовка

**Методы:**
- `get_dict()` - возвращает список словарей с сообщениями
- `get_json()` - возвращает JSON-строку
- `save_json(filepath)` - сохраняет результат в JSON-файл
- `save_html(filepath)` - сохраняет обработанный HTML

### 2. HtmlProcessor

Обрабатывает HTML-код email, нормализуя структуру и выделяя заголовки сообщений.

#### Алгоритм работы

**Этап 1: Инициализация и предобработка**
- Чтение HTML из файла/строки/soup
- Удаление BOM (\ufeff) и DOCTYPE
- Обработка изображений: замена `<img>` на текстовые маркеры
- Упрощение ссылок: преобразование `<a>` в span/p с форматом `[текст](url)`

**Этап 2: Очистка HTML**
- Удаление системных тегов (html, body, head, style, script, meta)
- Удаление пустых тегов (рекурсивно)
- Сохранение специальных атрибутов (`simple-email-parse-attr`)
- Удаление всех остальных атрибутов (class, style, id)

**Этап 3: Упрощение структуры**
- Преобразование всех тегов в div/span/br/blockquote/table
- Блочные теги (p, h1-h6, ul, ol, li) → div
- Инлайн теги (b, i, strong, em) → span

**Этап 4: Обработка сиротных узлов**
- Поиск последовательностей текст + span
- Оборачивание в div только если содержимое является заголовком
- Проверка через адаптеры заголовков

**Этап 5: Распаковка span**
- Специальные span с `quote_header` → div
- Если span содержит div → unwrap
- Текстовые span → склеиваются с пробелами

**Этап 6: Обнаружение заголовков**
Используются адаптеры для поиска заголовков:
1. **Контекстный поиск:**
   - После `<hr>` (с игнорированием `<br>`)
   - Перед/внутри `<blockquote>`
2. **Глобальный поиск:** все div, p, span
3. **Сортировка кандидатов** по глубине (от вложенных к корневым)
4. **Применение адаптеров:** DividerHeader → MultipleDivHeader → KeyValueHeader → OnelineHeader

**Этап 7: Обработка пересылаемых сообщений (Fwd)**
- Поиск разделителей "---Пересылаемое сообщение---"
- Поиск заголовка после разделителя (с игнорированием `<br>`)
- Оборачивание в `<blockquote>` до маркера конца

**Этап 8: Улучшение структуры цитат**
1. Превращение `div[simple-email-parse-attr="quote"]` → `<blockquote>`
2. Распаковка заголовков из лишних оберток
3. Распаковка цитат из лишних div (unwrap родителя, если единственный значимый контент)
4. Перенос заголовков внутрь следующей цитаты
5. Финальная распаковка пустых оберток

**Этап 9: Упрощение div-блоков**
Выполняется циклически (while changed):
1. **Wrap Orphans:** оборачивание сирот (текст, таблицы) в div
2. **Flattening:** если div содержит структурные блоки как прямых детей → unwrap
3. **Backward Merge:** слияние с предыдущим div или затягивание контента
4. **Forward Suck:** затягивание следующего контента

**Этап 10: Обеспечение blockquote**
- Оборачивание блоков после `quote_header` в `<blockquote>`
- Жадный захват всех элементов до следующего заголовка

**Этап 11: Вложение соседних цитат**
- Обработка снизу вверх
- Если перед цитатой стоит другая цитата → перемещение внутрь

**Этап 12: Перенос остатков**
- Обработка сверху вниз (рекурсивно)
- Перенос "хвостов" после цитаты в div перед цитатой
- Объединение с существующим контентом через `<br>`

**Этап 13: Преобразование переносов строк**
- `<br>` → `\n`
- SHY (\xad) → удаление
- &nbsp; → пробел
- Схлопывание пробелов
- Удаление пробелов вокруг \n
- Ограничение: 2+ \n → \n\n

### 3. JsonProcessor

Извлекает сообщения из обработанного HTML и парсит заголовки.

#### Алгоритм работы

**Этап 1: Рекурсивное извлечение сообщений**
- Обход дерева blockquote
- Поиск заголовков (`simple-email-parse-attr="quote_header_*"`)
- Извлечение текста между заголовком и вложенными цитатами
- Сохранение HTML-содержимого (включая таблицы)

**Этап 2: Парсинг контактов**
Поддерживаемые форматы:
- `Name <email@example.com>`
- `Name [mailto:email@example.com]`
- `"Name" <email@example.com>`
- `email@example.com`

**Этап 3: Парсинг даты/времени**
Поддерживаемые форматы:
- `14.05.2024, 17:35`
- `Вторник, 14 мая 2024, 17:35 +03:00`
- `Wednesday, May 08, 2024 1:34 PM`
- `пт, 15 апр. 2022 г. в 20:47`
- `8 мая 2024 г., 13:55:58 +03:00`

**Этап 4: Парсинг заголовков**

*Блочный формат (KeyValue):*
```
From: Sender <sender@example.com>
Sent: Tuesday, May 14, 2024 5:35 PM
To: Receiver <receiver@example.com>
Subject: Test Subject
```

*Однострочный формат:*
```
21.09.2023, 16:13, 'Name' <email@example.com>:
```

**Этап 5: Разворот порядка сообщений**
- Самое глубокое (старое) сообщение → первое
- Реверсирование списка

**Этап 6: Обработка временных меток**
Вычисление времени для сообщений без явного указания:
- **Первое сообщение:** время следующего минус 1 час
- **Последнее сообщение:** время предыдущего плюс 1 час
- **Среднее сообщение:** среднее между предыдущим и следующим
- Поиск ближайших сообщений с реальным временем (не 00:00:00)
- Согласование timezone (добавление МСК если нужно)

**Этап 7: Конвертация в МСК**
- Все временные метки конвертируются в московское время (UTC+3)
- Если timezone не указан → считается МСК

**Этап 8: Формирование результата**
```json
[
  {
    "header": {
      "From": {"name": "Sender", "email": "sender@example.com"},
      "Sent": "2024-05-14T17:35:00+03:00",
      "To": {"name": "Receiver", "email": "receiver@example.com"},
      "Subject": "Test"
    },
    "text": "Message text..."
  }
]
```

## Адаптеры заголовков

### DividerHeaderAdapter
Обнаруживает разделители пересылаемых сообщений:
```
---Пересылаемое сообщение---
---Forwarded message---
```

### MultipleDivHeaderAdapter
Обнаруживает многострочные заголовки, разбитые на несколько div:
```html
<div>Кому: email@example.com;</div>
<div>Тема: Test Subject;</div>
<div>29.10.2025, 09:16, "Sender" <email@example.com>:</div>
```

Правила:
- Минимум 2 элемента с ключевыми словами
- Первый div должен начинаться с ключевого слова
- Не более 5 элементов в группе
- Общая длина не более 600 символов

### KeyValueHeaderAdapter
Обнаруживает блочные заголовки с ключами:
```
From: sender@example.com
Sent: Tuesday, May 14, 2024
To: receiver@example.com
Subject: Test
```

Правила:
- Минимум 2 ключа (From, Sent, To, Subject, Cc)
- Первый ключ в начале текста (не далее 50 символов)
- Отсутствие разбиения между соседними элементами

### OnelineHeaderAdapter
Обнаруживает однострочные заголовки:
```
21.09.2023, 16:13, 'Name' <email@example.com>:
Вы писали 8 мая 2024 г., 13:55:58:
```

Правила:
- Длина 10-350 символов
- Не более 3 переносов строк
- Наличие даты + времени + email/ключевых слов
- Заканчивается на двоеточие

## Особенности и узкие места

### Обработка пробелов и переносов
- При распаковке span всегда добавляются пробелы для предотвращения слипания слов
- Важно игнорировать `<br>` при поиске заголовков, так как они часто окружают заголовки
- Разделители `<br>` правильно обрабатываются при слиянии div

### Обработка заголовков
- Проверка вложенности: родительские контейнеры отступают, если внутри уже найден заголовок
- Look Behind: проверка предыдущего соседа для избежания ложных срабатываний на фрагментах заголовков
- Сортировка кандидатов по глубине позволяет обрабатывать сначала вложенные элементы

### Обработка цитат
- Распаковка цитат выполняется дважды: до и после переноса заголовков
- Проверка "мусорных" узлов (`_is_removable_node`) включает пустые текстовые узлы, `<br>` и пустые div
- При переносе заголовков используется `_is_removable_node` для пропуска `<br>`

### Обработка div-блоков
- Защита от бесконечного цикла: проверка только прямых потомков (`children`), не всех (`descendants`)
- Важно различать: для unwrap_div нужен `<br>` как контент (ignore_br=False), для process_headers - нет (ignore_br=True)

### Обработка временных меток
- Особый случай: время 00:00:00 без timezone → признак отсутствия времени (только дата)
- Время 00:00:00 С timezone → реальное время полуночи
- Сохранение даты при вычислении времени для корректности

## Примеры использования

### Базовое использование
```python
from simple_email_parser import EmailParser

parser = EmailParser("email.htm")
messages = parser.get_dict()

for msg in messages:
    print(f"Header: {msg['header']}")
    print(f"Text: {msg['text']}")
```

### Использование с HTML-строкой
```python
html = "<div>Email content...</div>"
parser = EmailParser(html=html, remove_img=True)
json_output = parser.get_json()
```

### Использование с основным контактом
```python
from simple_email_parser import EmailParser, Contact

main_contact = Contact(email="me@example.com", name="My Name")
parser = EmailParser("email.htm", main_contact=main_contact)

messages = parser.get_dict()
```

### Раздельное использование процессоров
```python
from simple_email_parser import HtmlProcessor, JsonProcessor

html_proc = HtmlProcessor("email.htm", remove_img=True)
html_proc.process()

json_proc = JsonProcessor(html=html_proc.soup)
messages = json_proc.process()
```

## Лицензия

MIT