📨

PythonでMS Outlookのmsgファイルを読む(msg-parser)

2022/10/21に公開

はじめに

Outlookで受信したメールのうち、特定のメーリングリスト宛に誰からどのくらいの頻度でメールが来ているか調べて欲しいと言われた。
調査用データとして、過去数年分のメールデータ(Outlook .msg形式)数千件が提供された。

Outlookに取り込んでルールで振り分けを試みたが、メーリングリストに投稿したユーザーで振り分けることができないことが分かった。
(やり方が悪いのかもしれないが…)

msgファイルを1通ごと開いて調べるのは非現実的なので、Pythonの力を借りて自動化した。

コード

実証実験コード

「.msg」形式を読むためのパッケージは複数存在するが、今回はmsg-parserを用いた。

test.py
import json
from msg_parser import MsOxMessage

msg = MsOxMessage('./test.msg')

print(json.dumps(msg.header_dict))
output
{
	"Return-path": "<ml-bounces@example.jp>",
	"Envelope-to": "kuma@example.jp",
	"Delivery-date": "Fri, 15 Aug 2014 15:57:01 +0900",
	"Received": "from localhost ([127.0.0.1] helo=mail.example.jp)\r\n\tby mail.example.jp with esmtp (Exim 4.69)\r\n\t(envelope-from <ml-bounces@example.jp>)\r\n\tid 1XIBRa-0007sf-Ew; Fri, 15 Aug 2014 15:56:58 +0900",
	"From": [
		"=?ISO-2022-JP?B?GyRCJWYhPCU2ITwbKEI=?= <user@example.jp>"
	],
	"Message-Id": "<xxxxxxxx-xxxxx-xxxxx-xxxx-xxxxx@example.jp>",
	"Date": "Fri, 15 Aug 2014 15:56:59 +0900",
	"To": [
		"=?ISO-2022-JP?B?GyRCJWEhPCVqJXMlMCVqJTklSBsoQg==?= <ml@example.jp>"
	],
	"Mime-Version": "1.0 (1.0)",
	"Subject": "[ml 373] =?ISO-2022-JP?B?GyRCOiM3biRORGpOYzJxNUQkSyREJCQkRhsoQg==?=",
	"Content-Type": "multipart/mixed; boundary=\"===============7918749803048529676==\"",
	"Sender": "ml@example.jp",
	"Errors-To": "ml-bounces@example.jp",
	"CC": [],
	"BCC": [],
	"Reply-To": []
}

ヘッダー情報をdictで取れてくることが分かった。
また、本来の送信者(上記サンプルで言うuser@example.jp)も取れてきそうということが判明した。これは使える。

また、ヘッダー情報以外にも、送信日や本文を取得するメソッドが別途用意されている。
(詳しくはドキュメントを参照されたい)

本番コード

ターゲットディレクトリ内に存在する.msgファイルを走査し、「送信日」「送信者」を取得、集計できるよう準備をする。

extract.py
import glob
import re
from datetime import date, datetime
from msg_parser import MsOxMessage

files = glob.glob("./target/*.msg")
for file in files:
    msg = MsOxMessage(file)
    from_raw = msg.header_dict['From'][0]
    from_str=re.search(r'<(.+@.+)>', from_raw)[1]

    sent_date_raw = msg.sent_date
    sent_date = datetime.strptime(sent_date_raw, '%a, %d %b %Y %H:%M:%S %z')

    print (sent_date.strftime('%Y%m') + "," + from_str)
output
201809,user_k1@example.jp
201809,user_k1@s.example.jp
201809,user_k2@example.jp
201809,user_i1@example.jp

注意点

  1. ヘッダの「From」は配列なので、1件目を取ってくる。
    そもそもメールに「From」が複数存在するパターンってあるの?と思うが、パッケージの仕様のようなので従う。
  2. 「From」にセットされてくる値はメールアドレスだけではないので、正規表現でメールアドレスだけ抽出
    =?ISO-2022-JP?B?GyRCJWEhPCVqJXMlMCVqJTklSBsoQg==?= <ml@example.jp>メーリングリスト <ml@example.jp> といった形式で格納されている。
    このまま使ってもよいが、文字列部分はメーラ側で自由に変更できてしまうので、<>で囲まれたメールアドレス部だけを使うことにした。
  3. 「sent_date」はそのままでは日付時刻としてパースできない。
    これはメールサーバ側の仕様に合わせて書式を指定してパースしてやる必要がある。
    今回は Fri, 15 Aug 2014 15:56:59 +0900のような形式で取得できたので、
    %a, %d %b %Y %H:%M:%S %zと書式を決め打ちした。%cでのパースは失敗した。

後処理

雑にCSV形式で表示しただけなので、ファイルにリダイレクトしてやる。
python extract.py > extract.csv

しかる後、Excelにインポートしてピボットテーブルで件数集計を実施した。

ピボットテーブル

done!

Discussion