【问题标题】:Reading an mbox file in C#在 C# 中读取 mbox 文件
【发布时间】:2010-10-28 13:42:14
【问题描述】:

我们的一位员工丢失了他的邮箱,但幸运的是,他的电子邮件转储为 mbox 格式。我需要以某种方式获取 mbox 文件中的所有消息并将它们喷射到我们的技术支持数据库中(因为它是一个自定义工具,所以没有可用的导入工具)。

我发现 SharpMimeTools 分解了一条消息,但不允许您遍历 mbox 文件中的一堆消息。

有谁知道无需学习 RFC 就可以打开的体面的解析器吗?

【问题讨论】:

  • 哇,你们真快!感谢您的回复,现在我正在逐行查找“From”,如果找到它,我会从那里转到下一次出现的 FROM 并将其传递给 SharpMimeTools SharpMimeMessage 处理程序。唯一的问题是,使用 StreamReader 和 .ReadLine 速度非常慢,因为 mbox 文件 > 20Mb(并且 mbox 文件的总大小为 1.2Gb!)当然有一种更快的锻炼方式,可以在某些“From”发生并抓取段(可能是正则表达式?)

标签: c# email mime mbox


【解决方案1】:

我正在使用 C# 开发一个名为 MimeKit 的 MIME 和 mbox 解析器。

它基于我编写的早期 MIME 和 mbox 解析器(例如 GMime),速度非常快(可以在大约 1 秒内解析 1.2GB mbox 文件中的每条消息)。

我还没有测试 MimeKit 的性能,但我在 C# 中使用了许多我在 C 中使用的相同技术。我怀疑它会比我的 C 实现慢,但因为瓶颈是 I/O并且 MimeKit 是为像 GMime 一样进行最佳(4k)读取而编写的,它们应该非常接近。

您发现当前方法运行缓慢(StreamReader.ReadLine(),合并文本,然后将其传递给 SharpMimeTools)的原因如下:

  1. StreamReader.ReadLine() 不是从文件中读取数据的最佳方式。虽然我确定 StreamReader() 会进行内部缓冲,但它需要执行以下步骤:

    A) 将从文件中读取的字节块转换为 unicode(这需要遍历从磁盘读取的 byte[] 中的字节,以将从流中读取的字节转换为 unicode char[])。

    B) 然后它需要遍历其内部的 char[],将每个 char 复制到 StringBuilder 中,直到找到一个 '\n'。

    因此,只需读取行,您就至少有 2 次通过您的 mbox 输入流。更不用说正在进行的所有内存分配......

  2. 然后你将你读过的所有行组合成一个单一的巨型字符串。这需要再次传递您的输入(大概将从 ReadLine() 读取的每个字符串中的每个字符复制到 StringBuilder 中?)。

    我们现在对输入文本进行了多达 3 次迭代,甚至还没有进行任何解析。

  3. 1234563在解析任何内容之前进行 5 次迭代。如果 SharpMimeMessageStream 发现它读得太远,它还有一种方法可以“撤消” ReadLine()。因此可以合理地假设他至少扫描了 一些 行。更不用说正在进行的所有字符串分配......呃。
  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 位文本。 unicode 字符串解析器可能如何处理呢?提示:不能。

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 年 9 月 18 日更新: 我已将 MimeKit 升级到现在可用于解析 mbox 文件并成功解决问题的地步,但速度并没有那么快作为我的 C 库。这是在 iMac 上测试的,因此 I/O 性能不如我的旧 Linux 机器(GMime 能够在大约 1 秒内解析类似大小的 mbox 文件):

[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 解析器的性能有一些想法。事实证明,C# 的fixed 语句非常昂贵,所以我需要重新设计它们的用法。例如,a simple optimization 我昨天做了大约 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 秒。

【讨论】:

    【解决方案2】:

    我不知道任何解析器,但 mbox 确实是一种非常简单的格式。一封新电子邮件以“发件人”(发件人+空格)开头的行开始,并在每封邮件的末尾附加一个空行。如果在电子邮件本身的行首出现“From”,则会将其引用(通过在前面添加“>”)。

    另见Wikipedia's entry on the topic

    【讨论】:

    • 这可能是最简单的解决方案是的。
    • 并且以“>From”开头的行应该被引用“>>From”,依此类推。
    • @JimG - 这将是首选方式,但也有一个没有这样做的报价方案(谷歌 MBOXO 和 MBOXRD)。
    【解决方案3】:

    如果您可以扩展到使用 Python,标准库中有 one。遗憾的是,我找不到任何适用于 .NET 的内容。

    【讨论】:

    • 是的,这是可能的。使用 IronPython 中的其他 .NET 代码很容易,但使用其他 .NET 中的 IronPython 很难。
    【解决方案4】:

    要读取 .mbox 文件,您可以使用第三方库 Aspose.Email。 该库是一套完整的电子邮件处理 API,用于构建跨平台应用程序,能够在不使用 Microsoft Outlook 的情况下创建、操作、转换和传输电子邮件。

    请看一下我在下面提供的示例。

    using(FileStream stream = new FileStream("ExampleMbox.mbox", FileMode.Open, FileAccess.Read))
    {
        using(MboxrdStorageReader reader = new MboxrdStorageReader(stream, false))
        {
            // Start reading messages
            MailMessage message = reader.ReadNextMessage();
    
            // Read all messages in a loop
            while (message != null)
            {
                // Manipulate message - show contents
                Console.WriteLine("Subject: " + message.Subject);
                // Save this message in EML or MSG format
                message.Save(message.Subject + ".eml", SaveOptions.DefaultEml);
                message.Save(message.Subject + ".msg", SaveOptions.DefaultMsgUnicode);
    
                // Get the next message
                message = reader.ReadNextMessage();
            }
        }
    }
    

    它易于使用。我希望这种方法能让您和其他搜索者满意。

    我在 Aspose 担任开发人员宣传员。

    【讨论】:

      猜你喜欢
      • 2012-02-15
      • 2020-04-28
      • 2019-12-13
      • 1970-01-01
      • 2022-06-13
      • 2012-11-19
      • 1970-01-01
      • 2019-12-12
      相关资源
      最近更新 更多