【问题标题】:Parse email content from quoted reply从引用的回复中解析电子邮件内容
【发布时间】:2010-09-21 16:23:47
【问题描述】:

我试图弄清楚如何从电子邮件可能包含的任何引用回复文本中解析出电子邮件的文本。我注意到,通常电子邮件客户会写上“在某某日期某某写”或在行前加上尖括号。不幸的是,并不是每个人都这样做。有谁知道如何以编程方式检测回复文本?我正在使用 C# 编写这个解析器。

【问题讨论】:

  • 你有什么运气吗?我想做同样的事情。
  • 任何带有完整源代码示例的最终解决方案?
  • Quotequail 在 Python 中执行此操作
  • 任何人都可以帮助它的 php 版本吗?

标签: c# ruby email email-parsing


【解决方案1】:

首先,这是一项棘手的任务。

您应该收集来自不同电子邮件客户端的典型响应,并准备正确的正则表达式(或其他)来解析它们。我收集了来自 Outlook、thunderbird、Gmail、Apple 邮件和 mail.ru 的回复。

我正在使用正则表达式以下列方式解析响应:如果一个表达式不匹配,我尝试使用下一个。

new Regex("From:\\s*" + Regex.Escape(_mail), RegexOptions.IgnoreCase);
new Regex("<" + Regex.Escape(_mail) + ">", RegexOptions.IgnoreCase);
new Regex(Regex.Escape(_mail) + "\\s+wrote:", RegexOptions.IgnoreCase);
new Regex("\\n.*On.*(\\r\\n)?wrote:\\r\\n", RegexOptions.IgnoreCase | RegexOptions.Multiline);
new Regex("-+original\\s+message-+\\s*$", RegexOptions.IgnoreCase);
new Regex("from:\\s*$", RegexOptions.IgnoreCase);

最后去掉引号:

new Regex("^>.*$", RegexOptions.IgnoreCase | RegexOptions.Multiline);

这是我的一小部分测试响应(样本除以 --- ):

From: test@test.com [mailto:test@test.com] 
Sent: Tuesday, January 13, 2009 1:27 PM
----
2008/12/26 <test@test.com>

>  text
----
test@test.com wrote:
> text
----
      test@test.com wrote:         text
text
----
2009/1/13 <test@test.com>

>  text
----
 test@test.com wrote:         text
 text
----
2009/1/13 <test@test.com>

> text
> text
----
2009/1/13 <test@test.com>

> text
> text
----
test@test.com wrote:
> text
> text
<response here>
----
--- On Fri, 23/1/09, test@test.com <test@test.com> wrote:

> text
> text

【讨论】:

  • 不知道邮箱怎么办?
  • @Shyamal-Parikh 这不适用于 html 电子邮件,但通常电子邮件中还包含纯文本消息
【解决方案2】:

如果您使用SigParser.com 的 API,它会为您提供来自单个电子邮件文本字符串的回复链中所有已中断电子邮件的数组。因此,如果有 10 封电子邮件,您将获得所有 10 封电子邮件的文本。

您可以在此处查看详细的 API 规范。

https://api.sigparser.com/

【讨论】:

    【解决方案3】:

    这是旧帖子,但是,不确定您是否知道 github 有 a Ruby lib 提取回复。如果您使用 .NET,我在https://github.com/EricJWHuang/EmailReplyParser 有一个 .NET

    【讨论】:

    • 鼓励链接到外部资源,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。
    • 您是否使该库保持最新?我来搜索是因为 C# 库无法正确解析来自 Office 365 的 Outlook 的简单电子邮件。然后我查看了 ruby​​ 源代码,发现他们的测试用例中有一个相同的测试用例,很明显他们认为他们应该解析它。
    【解决方案4】:

    到目前为止,最简单的方法是在您的内容中放置一个标记,例如:

    ---请在此行上方回复---

    正如您毫无疑问地注意到的那样,解析引用的文本并非易事,因为不同的电子邮件客户端以不同的方式引用文本。要正确解决此问题,您需要在每个电子邮件客户端中进行说明和测试。

    Facebook 可以做到这一点,但除非您的项目预算很大,否则您可能做不到。

    Oleg 已使用正则表达式解决了该问题,以查找“2012 年 7 月 13 日,13:09,xxx 写道:”文本。但是,如果用户像许多人一样删​​除此文本或在电子邮件底部回复,则此解决方案将不起作用。

    同样,如果电子邮件客户端使用不同的日期字符串,或者不包含日期字符串,则正则表达式将失败。

    【讨论】:

    • 除非您每次回复时都输入该行,否则此方法会在回复回复时失败。
    • 是的,它有缺点。如果用户删除了行字符串上方的回复,那么您的回复将失败。我发现了这种情况,并向用户发送了一条直接消息,让他们知道他们的消息失败,并带有一个通过网络应用程序回复的链接。大多数用户似乎都能轻松使用它。
    • 这应该是公认的答案。但是,我会添加信息,如果删除该行,答案将不会成功。
    • @Benni - 是的,如果删除该行,它将失败。不幸的是,没有一种标准的跨电子邮件客户端引用文本的方法。在删除该行的情况下,您可以将所有文本视为回复。在这种情况下,我认为不可能有完美的解决方案。
    • @superluminary 我的意思是,我会将它添加到行中。所以它类似于-- Please reply above this line. DO NOT REMOVE IT! --。此外,我所经历的是它并不总是有效,因为一些电子邮件客户端在整个报价之前添加了 xxx wrote on &lt;datetime&gt;: 行,因此在该行之前。该行可以用正则表达式解析,但是由于电子邮件客户端不同,它可能使用不同的语言和不同的格式。
    【解决方案5】:

    我对此进行了更多搜索,这就是我找到的。基本上有两种情况下你会这样做:当你有整个线程时,当你没有时。我将它分为这两类:

    当你有话题时:

    如果您拥有整个系列的电子邮件,则可以高度确保您删除的内容实际上是引用的文本。有两种方法可以做到这一点。一,您可以使用消息的 Message-ID、In-Reply-To ID 和 Thread-Index 来确定单个消息、它的父级以及它所属的线程。有关这方面的更多信息,请参阅RFC822RFC2822this interesting article on threadingthis article on threading。重新组装线程后,您可以删除外部文本(例如 To、From、CC 等...行),您就完成了。

    如果您正在处理的邮件没有标题,您还可以使用相似性匹配来确定电子邮件的哪些部分是回复文本。在这种情况下,您只能进行相似性匹配以确定重复的文本。在这种情况下,您可能需要查看Levenshtein Distance algorithm,例如this one on Code Projectthis one

    无论如何,如果您对线程处理感兴趣,请查看this great PDF on reassembling email threads

    当你没有话题时:

    如果您只被线程中的一条消息卡住,那么您将不得不尝试猜测引用是什么。既然如此,以下是我见过的不同报价方式:

    1. 一条线(如前景中所见)。
    2. 角括号
    3. "---原始消息---"
    4. “某某日,某某写道:”

    从那里删除文本,你就完成了。其中任何一个的缺点是他们都假设发件人将他们的回复放在引用的文本之上并且没有交错(就像互联网上的旧风格一样)。如果发生这种情况,祝你好运。我希望这对你们中的一些人有所帮助!

    【讨论】:

      【解决方案6】:

      这是一个很好的解决方案。找了好久终于找到了。

      如上所述,一个补充,这是明智的,所以上面的表达式没有正确解析我的 gmail 和 Outlook (2010) 响应,为此我添加了以下两个正则表达式。如有任何问题,请告诉我。

      //Works for Gmail
      new Regex("\\n.*On.*<(\\r\\n)?" + Regex.Escape(address) + "(\\r\\n)?>", RegexOptions.IgnoreCase),
      //Works for Outlook 2010
      new Regex("From:.*" + Regex.Escape(address), RegexOptions.IgnoreCase),
      

      干杯

      【讨论】:

      【解决方案7】:

      这是我的 C# 版本的 @hurshagrawal 的 Ruby 代码。我不太了解 Ruby,所以它可能会关闭,但我认为我做对了。

      public string ExtractReply(string text, string address)
      {
          var regexes = new List<Regex>() { new Regex("From:\\s*" + Regex.Escape(address), RegexOptions.IgnoreCase),
                              new Regex("<" + Regex.Escape(address) + ">", RegexOptions.IgnoreCase),
                              new Regex(Regex.Escape(address) + "\\s+wrote:", RegexOptions.IgnoreCase),
                              new Regex("\\n.*On.*(\\r\\n)?wrote:\\r\\n", RegexOptions.IgnoreCase | RegexOptions.Multiline),
                              new Regex("-+original\\s+message-+\\s*$", RegexOptions.IgnoreCase),
                              new Regex("from:\\s*$", RegexOptions.IgnoreCase),
                              new Regex("^>.*$", RegexOptions.IgnoreCase | RegexOptions.Multiline)
                          };
      
          var index = text.Length;
      
          foreach(var regex in regexes){
              var match = regex.Match(text);
      
              if(match.Success && match.Index < index)
                  index = match.Index;
          }
      
          return text.Substring(0, index).Trim();
      }
      

      【讨论】:

        【解决方案8】:

        感谢 Goleg 的正则表达式!真的有帮助。这不是 C#,但是对于那里的 googlers,这是我的 Ruby 解析脚本:

        def extract_reply(text, address)
            regex_arr = [
              Regexp.new("From:\s*" + Regexp.escape(address), Regexp::IGNORECASE),
              Regexp.new("<" + Regexp.escape(address) + ">", Regexp::IGNORECASE),
              Regexp.new(Regexp.escape(address) + "\s+wrote:", Regexp::IGNORECASE),
              Regexp.new("^.*On.*(\n)?wrote:$", Regexp::IGNORECASE),
              Regexp.new("-+original\s+message-+\s*$", Regexp::IGNORECASE),
              Regexp.new("from:\s*$", Regexp::IGNORECASE)
            ]
        
            text_length = text.length
            #calculates the matching regex closest to top of page
            index = regex_arr.inject(text_length) do |min, regex|
                [(text.index(regex) || text_length), min].min
            end
        
            text[0, index].strip
        end
        

        到目前为止效果还不错。

        【讨论】:

        • 你应该提出一个 ruby​​ 问题并用这段代码回答它,而不是在 c# 问题上发布它。
        • @Matthieu,它不仅仅是一个 C# 问题,而是一个电子邮件和电子邮件解析问题。在我看来完全相关。
        • @Trent : C# 标签应该被删除。
        • 有趣的是,我通过谷歌搜索主题(不是语言)发现了这个问题,实际上我需要在 Ruby 中实现一些东西。所以,干杯!
        • 这是迄今为止最好的回应。正则表达式与语言无关。感谢发帖
        【解决方案9】:

        如果您控制原始消息(例如来自网络应用程序的通知),您可以放置​​一个独特的、可识别的标头,并将其用作原始帖子的分隔符。

        【讨论】:

          【解决方案10】:

          电子邮件中没有通用的回复指示符。您能做的最好的事情就是尝试捕捉最常见的模式并在遇到新模式时对其进行解析。

          请记住,有些人会在引用的文本中插入回复(例如,我的老板在我问他们的同一行回答问题),所以无论您做什么,您都可能会丢失一些您希望保留的信息。

          【讨论】:

          • gmail 做到了……至少它似乎做到了。据我记得有一些线程ID在原始和回复之间没有变化......
          • gmail 可能会像其他电子邮件客户端一样添加“>”,但这不是电子邮件的标准,也不是您可以依靠的东西
          猜你喜欢
          • 1970-01-01
          • 2018-04-29
          • 2020-09-14
          • 1970-01-01
          • 2012-07-22
          • 1970-01-01
          • 2015-05-03
          • 2015-03-21
          • 1970-01-01
          相关资源
          最近更新 更多