Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 KiB
parse_emails_tower_v1.3
Spuštění
První spuštění:
docker exec -d python-runner bash -c \
"python /scripts/parse_emails_tower_v1.3.py > /scripts/parse_emails_tower.log 2>&1"
Pokračování po přerušení (přeskočí už importované):
docker exec -d python-runner bash -c \
"python /scripts/parse_emails_tower_v1.3.py --skip-existing > /scripts/parse_emails_tower.log 2>&1"
Stav importu
Sledování průběhu (live log):
docker exec -it python-runner tail -f /scripts/parse_emails_tower.log
Počet emailů v MongoDB:
docker exec -it python-runner python -c \
"from pymongo import MongoClient; c=MongoClient('mongodb://192.168.1.76:27017'); print(c['emaily']['vbuzalka@its.jnj.com'].count_documents({}))"
Název: parse_emails_tower_v1.3.py
Verze: 1.3
Datum: 2026-06-08
Autor: vladimir.buzalka
Účel
Import všech .msg souborů do MongoDB. Z každého souboru extrahuje všechny dostupné vlastnosti — podobně jako EXIF u fotek.
- DB:
emaily - Kolekce:
vbuzalka@its.jnj.com _id= Internet Message-ID (nebofilename:<stem>jako fallback)- Bezpečné přerušit a opakovat — upsert podle
_id
Prostředí
Běží v Docker containeru python-runner na Unraid Tower.
| Komponenta | Umístění |
|---|---|
| Container | python-runner (Docker na Unraid Tower) |
| .msg soubory | /mnt/user/JNJEMAILS → /mnt/JNJEMAILS uvnitř containeru |
| Skripty | /mnt/user/Scripts → /scripts uvnitř containeru |
| MongoDB | 192.168.1.76:27017 (externí, mimo container) |
Spouštění (z Unraid terminálu)
Test na 50 emailech:
docker exec -it python-runner python /scripts/parse_emails_tower_v1.3.py --limit 50 --no-indexes
Kompletní import na pozadí (log do souboru):
docker exec -d python-runner bash -c \
"python /scripts/parse_emails_tower_v1.3.py > /scripts/parse_emails_tower.log 2>&1"
Pokračování po přerušení:
docker exec -d python-runner bash -c \
"python /scripts/parse_emails_tower_v1.3.py --skip-existing > /scripts/parse_emails_tower.log 2>&1"
Sledování průběhu (Ctrl+C ukončí sledování, import běží dál):
docker exec -it python-runner tail -f /scripts/parse_emails_tower.log
Všechny parametry
| Parametr | Popis |
|---|---|
--skip-existing |
Načte seznam hotových souborů z MongoDB a přeskočí je. Použij pro pokračování po přerušení. |
--limit N |
Zpracuje jen prvních N souborů. Vhodné pro test. |
--no-indexes |
Nevytváří indexy na konci. Použij pokud přerušíš uprostřed — indexy vytvoř ručně až je vše hotové. |
--msgs-dir PATH |
Přepíše výchozí cestu k .msg souborům (výchozí: /mnt/JNJEMAILS). |
Průběh na konzoli
Každý email na jednom řádku:
1/69371 OK RE: Protocol deviation CZ10022 jan.novak@its.jnj.com
2/69371 OK UCO3001: Draft FUL pro DD5-CZ10022 monitor@4gclinical.com
3/69371 ERR ? ?
Každých 500 emailů oddělovač s průběhem:
────────────────────────────────────────────────────────────────────────────────
Průběh: ok=498 err=2 0.4 msg/s ETA 47h12m
────────────────────────────────────────────────────────────────────────────────
Na konci souhrn:
====================================================
Vysledek: ok=69300 | skip=0 | err=71
Celkovy cas: 47h 23m 10s
Dokumentu v kolekci: 69300
Zdroje dat z každého .msg
| Pole | Popis |
|---|---|
| Předmět, normalized subject | |
| Odesílatel | email, jméno, SMTP adresa |
| Příjemci To/CC/BCC | strukturovaně [{type, email, name}] |
| Čas doručení a odeslání | UTC |
| Tělo | plaintext + HTML (max 2 MB) |
| Přílohy | metadata: jméno, velikost, MIME typ, inline flag |
| Internet headers | X-Originating-IP, Received, DKIM, X-Mailer, ... |
| MAPI | důležitost, citlivost, příznak, konverzační vlákno, kategorie |
| In-Reply-To, References | pro rekonstrukci vlákna |
| Raw MAPI properties | {0xXXXX: value} |
Hodnotové kódy
| Pole | Hodnota | Význam |
|---|---|---|
importance |
0 | Nízká |
| 1 | Normální | |
| 2 | Vysoká | |
sensitivity |
0 | Normální |
| 1 | Osobní | |
| 2 | Soukromé | |
| 3 | Důvěrné | |
flag_status |
0 | Bez příznaku |
| 1 | Označeno (follow up) | |
| 2 | Dokončeno |
MongoDB indexy
Automaticky vytvořeny na konci importu (--no-indexes přeskočí):
| Index | Pole |
|---|---|
| Chronologický | received_at, sent_at |
| Odesílatel | sender.email |
| Soubor | filename (unique) |
| Konverzace | conversation_topic |
| Filtry | has_attachments, categories, importance, flag_status |
| Full-text | subject + body_text + to + cc (text index text_search) |
Ukázkové dotazy (MongoDB shell / MCP)
Emaily o UCO3001 s přílohou:
db["vbuzalka@its.jnj.com"].find({
$text: { $search: "UCO3001" },
has_attachments: true
}).sort({ received_at: -1 })
Emaily od konkrétního odesílatele:
db["vbuzalka@its.jnj.com"].find({
"sender.email": /covance/i
}).sort({ received_at: -1 })
Celé konverzační vlákno:
db["vbuzalka@its.jnj.com"].find({
conversation_topic: "Protocol deviation CZ10022"
}).sort({ received_at: 1 })
Statistiky podle odesílatele (top 20):
db["vbuzalka@its.jnj.com"].aggregate([
{ $group: { _id: "$sender.email", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 20 }
])
Chybový log
Soubory které selhaly jsou zalogovány do samostatného parse_emails_tower_errors.log vedle skriptu (tj. /scripts/parse_emails_tower_errors.log → \\tower\Scripts\parse_emails_tower_errors.log). Tento log je oddělený od Graph importu, aby v něm nebyl bordel:
2026-06-08 12:40:33 | open failed [7A3F...0000.msg]: <důvod>
2026-06-08 12:41:02 | per-dokument selhal [_id=<...>]: <důvod>
Stdout (průběh) jde do parse_emails_tower.log — rovněž samostatný.
Záchrana problémových .msg (v1.3)
Některé .msg defaultní extract_msg neumí otevřít a celý soubor zahodí, i když email je naprosto v pořádku (jde otevřít v Outlooku). Tři příčiny a jejich řešení:
| Příčina | Příklad | Řešení |
|---|---|---|
Vadná příloha bez PR_ATTACH_METHOD |
„Attachment method missing" | errorBehavior=SUPPRESS_ALL — vadnou přílohu přeskočí, zbytek (tělo, ostatní přílohy) načte |
| Tělo deklaruje codepage 1200 (UTF-16), ale bajty jsou cp1250/gb2312 | české � místo diakritiky |
raw-OLE čtení + kaskádové dekódování |
| Vnořený email (Outlook item) | „not an MSG file", extract_msg vrátí prázdno |
raw-OLE čtení klíčových MAPI streamů |
Jak to funguje:
open_message()— kaskádové otevření:normal→SUPPRESS_ALL→+overrideEncoding(dle codepage property).- raw-OLE fallback — když extract_msg vrátí prázdno/
�nebo musel hádat kódování, klíčová pole (subject, sender, body, html) se dočtou přímo z OLE streamů (__substg1.0_0037/0C1A/5D01/1000/1013) s kaskádovým dekódováním:Hlavičkám o kódování se nevěří (často si protiřečí); bere se první kódování, které projde striktně bez chyby.utf-8 (strict) → kódování dle CPID → cp1250 → cp1252 → gb2312 → latin-1utf-8 strictje silný rozlišovač.
Nová pole v dokumentu:
| Pole | Význam |
|---|---|
parse_mode |
normal / suppress_all / override:<enc> — jak byl soubor otevřen |
parse_degraded |
true = byl potřeba fallback (vadná příloha nebo hádané kódování) |
Ověřeno: všech 126 dříve selhaných souborů z běhu 8.6. se obnoví čistě (74× suppress_all, 52× override:cp1250), 0 prázdných, 0 s �.
Dohledání degradovaných:
db["vbuzalka@its.jnj.com"].find({ parse_degraded: true })
Výkon
| Parametr | Hodnota |
|---|---|
| Počet souborů | ~69 000 |
| Rychlost | ~0.4 msg/s (htmlBody dekódování) |
| Odhadovaný čas | 48 hodin |
| Batch size | 200 dokumentů / bulk_write |
| Odhadovaná velikost DB | 2–5 GB |
Závislosti (v Docker image python-runner)
extract-msg==0.55.0
olefile
pymongo
python-dateutil
Image sestaven z Dockerfile v /mnt/user/Scripts/python-runner/.
Historie verzí
| Verze | Datum | Změna |
|---|---|---|
| 1.0 | 2026-06-01 | Iniciální verze |
| 1.1 | 2026-06-02 | Nasazení na Unraid Tower v Docker containeru python-runner; MSGS_DIR změněno z SMB share (\\tower\JNJEMAILS) na lokální mount (/mnt/JNJEMAILS); aktualizován popis spouštění pro docker exec |
| 1.2 | 2026-06-08 | Oprava to_bson: int mimo rozsah int64 (BSON umí jen 8-byte ints) se převede na string — dřív celý bulk_write spadl na MongoDB can only handle up to 8-byte ints a zahodil celou dávku 200 dokumentů (běh v1.1 z 8.6. neuložil nic). flush() má fallback per-dokument (vadný záznam zahodí sám, ne celou dávku). bool() testován před int(). Samostatné logy parse_emails_tower.log + parse_emails_tower_errors.log. |
| 1.3 | 2026-06-08 | Záchrana dříve selhaných .msg (cca 126 z běhu 8.6.): open_message() kaskádové otevření (normal→SUPPRESS_ALL→+overrideEncoding) řeší vadné přílohy i „not an MSG file"; raw-OLE fallback dočítá subject/sender/body/html přímo z OLE streamů s kaskádovým dekódováním (utf-8 strict→CPID→cp1250…), když extract_msg vrátí prázdno/�. Nová pole parse_mode, parse_degraded. Nová závislost olefile. Ověřeno: 126/126 obnoveno čistě. |