4

スタッフ メンバーの 1 人がメールボックスを紛失しましたが、幸いなことに mbox 形式の電子メールのダンプがあります。どうにかして mbox ファイル内のすべてのメッセージを取得し、テクニカル サポート データベースに送り込む必要があります (カスタム ツールであるため、利用可能なインポート ツールはありません)。

メッセージを分解するSharpMimeToolsを見つけましたが、mbox ファイル内の一連のメッセージを反復処理することはできません。

RFC を学習して書き出す必要なく開くまともなパーサーを知っている人はいますか?

4

4 に答える 4

19

MimeKitと呼ばれる C# の MIME & mbox パーサーに取り組んでいます。

これは、私が作成した以前の MIME および mbox パーサー ( GMimeなど) に基づいていますが、これは非常に高速でした (1.2 GB の mbox ファイル内のすべてのメッセージを約 1 秒で解析できました)。

MimeKit のパフォーマンスはまだテストしていませんが、C で使用したのと同じ手法を C# で多数使用しています。C の実装よりも遅くなると思いますが、ボトルネックは I/O であり、MimeKit はGMime のように最適な (4k) 読み取りを行うように書かれているため、かなり近いはずです。

現在のアプローチが遅いと感じている理由 (StreamReader.ReadLine()、テキストを結合してから SharpMimeTools に渡す) は、次の理由によるものです。

  1. StreamReader.ReadLine() は、ファイルからデータを読み取る最適な方法ではありません。StreamReader() が内部バッファリングを行うことは確かですが、次の手順を実行する必要があります。

    A) ファイルから読み取ったバイトのブロックを Unicode に変換します (これには、ストリームから読み取ったバイトを Unicode char[] に変換するために、ディスクから読み取った byte[] 内のバイトを反復処理する必要があります)。

    B) 次に、内部の char[] を繰り返し処理し、「\n」が見つかるまで各文字を StringBuilder にコピーする必要があります。

    したがって、行を読み取るだけで、mbox 入力ストリームに少なくとも 2 つのパスがあります。進行中のすべてのメモリ割り当ては言うまでもありません...

  2. 次に、読み取ったすべての行を単一のメガストリングに結合します。これには、入力に対する別のパスが必要です (おそらく ReadLine() から読み取った各文字列からすべての文字を StringBuilder にコピーしますか?)。

    現在、入力テキストに対して最大 3 回の反復が行われていますが、解析はまだ行われていません。

  3. これで、SharpMimeMessageStream を使用する SharpMimeTools にメガ文字列を渡します... (/facepalm) は、文字セット変換を行う別の StreamReader の上にある ReadLine() ベースのパーサーです。これにより、何かが解析される前に 5 回の反復が行われます。SharpMimeMessageStream には、読みすぎた場合に ReadLine() を「元に戻す」方法もあります。したがって、彼がそれらの行のいくつかを少なくとも 2 回スキャンしていると想定するのは合理的です。進行中のすべての文字列割り当ては言うまでもありません...うーん。

  4. 各ヘッダーについて、SharpMimeTools がライン バッファーを取得すると、フィールドと値に分割されます。それは別のパスです。これまでのところ、パスは最大 6 つです。

  5. 次に、SharpMimeTools は string.Split() (これは、この MIME パーサーが標準に準拠していないことを示すかなり良い指標です) を使用して、「,」で分割することによってアドレス ヘッダーをトークン化し、分割することによってパラメーター化されたヘッダー (Content-Type や Content-Disposition など) をトークン化します。の上 ';'。それは別のパスです。(現在、最大 7 パスです。)

  6. それらを分割すると、string.Split() から返された各文字列に対して正規表現一致が実行され、rfc2047 エンコードされた単語トークンごとにさらに正規表現が渡されてから、エンコードされた単語の文字セットとペイロード コンポーネントに対して別のパスが最終的に作成されます。この時点までに、入力の大部分で少なくとも 9 回または 10 回のパスについて話しています。

GMime と MimeKit が必要とするパスの数がすでに 2 倍以上になっており、パーサーを最適化してパスを少なくとも 1 回少なくすることができることを知っているため、これ以上の調査は断念しました。

また、補足として、byte[] (または sbyte[]) の代わりに文字列を解析する MIME パーサーは、決して優れたものにはなりません。電子メールの問題は、非常に多くのメール クライアント/スクリプト/その他が、ヘッダーとメッセージ本文で宣言されていない 8 ビット テキストを送信することです。ユニコード文字列パーサーはどうすればそれを処理できるでしょうか? ヒント: できません。

using (var stream = File.OpenRead ("Inbox.mbox")) {
    var parser = new MimeParser (stream, MimeFormat.Mbox);
    while (!parser.IsEndOfStream) {
        var message = parser.ParseMessage ();

        // At this point, you can do whatever you want with the message.
        // As an example, you could save it to a separate file based on
        // the message subject:
        message.WriteTo (message.Subject + ".eml");

        // You also have the ability to get access to the mbox marker:
        var marker = parser.MboxMarker;

        // You can also get the exact byte offset in the stream where the
        // mbox marker was found:
        var offset = parser.MboxMarkerOffset;
    }
}

2013-09-18 更新: MimeKit を mbox ファイルの解析に使用できるようになり、ねじれをうまく解決することができましたが、私の C ライブラリほど高速ではありません。これは iMac でテストされたので、I/O パフォーマンスは私の古い Linux マシンほど良くありません (GMime が同様のサイズの mbox ファイルを ~1 秒で解析できる場所です):

[fejj@localhost MimeKit]$ mono ./mbox-parser.exe larger.mbox 
Parsed 14896 messages in 6.16 seconds.
[fejj@localhost MimeKit]$ ./gmime-mbox-parser larger.mbox 
Parsed 14896 messages in 3.78 seconds.
[fejj@localhost MimeKit]$ ls -l larger.mbox 
-rw-r--r--  1 fejj  staff  1032555628 Sep 18 12:43 larger.mbox

ご覧のとおり、GMime はまだかなり高速ですが、MimeKit のパーサーのパフォーマンスを改善する方法についていくつかのアイデアがあります。fixedC# のステートメントは非常に高価であることが判明したため、使用方法を変更する必要があります。たとえば、昨日行った単純な最適化により、全体の時間が約 2 ~ 3 秒短縮されました (記憶が正しければ)。

最適化の更新:以下を置き換えることで、パフォーマンスがさらに 20% 向上しました。

while (*inptr != (byte) '\n')
    inptr++;

と:

do {
    mask = *dword++ ^ 0x0A0A0A0A;
    mask = ((mask - 0x01010101) & (~mask & 0x80808080));
} while (mask == 0);

inptr = (byte*) (dword - 1);
while (*inptr != (byte) '\n')
    inptr++;

最適化の更新: Enum.HasFlag() の使用をやめ、代わりにダイレクト ビット マスキングを使用することで、最終的に MimeKit を GMime と同じくらい高速にすることができました。

MimeKit は、3.78 秒で同じ mbox ストリームを解析できるようになりました。

比較のために、SharpMimeTools は 20分以上かかります(これをテストするには、SharpMimeTools は mbox ファイルを解析できないため、メールを個別のファイルに分割する必要がありました)。

別の更新:コード全体にさまざまな調整を加えることで、3.00 秒フラットまで下げました。

于 2013-09-13T13:11:07.393 に答える
2

パーサーは知りませんが、mbox は非常に単純な形式です。新しいメールは「From」(From+スペース) で始まる行から始まり、各メールの末尾に空行が追加されます。電子メール自体の行の先頭に「From」が出現する場合は、これが引用されます (先頭に「>」を追加することにより)。

このトピックに関するウィキペディアのエントリも参照してください。

于 2009-05-24T13:05:38.317 に答える
0

Python の使用に拡張できる場合は、標準ライブラリに1 つあります。悲しいことに、.NET のものが見つかりません。

于 2009-05-24T13:05:40.160 に答える