2008-11-10 6 views
77

Я пытаюсь выяснить, как разобрать текст письма из любого цитируемого текста ответа, который он может включить. Я заметил, что обычно почтовые клиенты будут помещать «В такую ​​и такую ​​дату так и так написано» или префикс строк с помощью угловой скобки. К сожалению, не все это делают. Кто-нибудь имеет представление о том, как программно обнаруживать текст ответа? Я использую C# для написания этого синтаксического анализатора.Проанализировать содержимое электронной почты от цитируемого ответа

+2

Вам повезло с этим? Я хочу сделать то же самое. – 2008-11-25 18:22:53

+0

любое окончательное решение с полным образцом исходного кода, работающим над этим? – Kiquenet 2013-06-18 12:56:11

+0

[Quotequail] (https://github.com/elasticsales/quotequail) делает это в Python – philfreo 2014-05-26 23:31:42

ответ

6

Нет универсального индикатора ответа по электронной почте. Лучшее, что вы можете сделать, это попытаться поймать наиболее распространенные и разобрать новые шаблоны, когда вы сталкиваетесь с ними.

Имейте в виду, что некоторые люди вставляют ответы внутри цитируемого текста (например, мой босс отвечает на вопросы в той же строке, что и я их спросил), поэтому, что бы вы ни делали, вы могли бы потерять некоторую информацию, которую вы хотели бы сохранить.

+0

gmail делает это ... по крайней мере, похоже, это так. Из того, что я помню, есть некоторый идентификатор потока, который не изменяется между оригиналом и ответами ... – kenny 2008-11-10 20:22:19

+0

gmail может добавлять '>, как и другие почтовые клиенты, но это не стандарт писем, а не то, на что вы можете рассчитывать – 3Doubloons 2008-11-11 23:04:49

56

Я сделал намного больше поиска по этому вопросу, и вот что я нашел. Существуют две ситуации, в которых вы делаете это: когда у вас есть весь поток, а когда нет. Я разбить его на эти две категории:

Если у вас есть нить:

Если у вас есть целый ряд писем, вы можете достичь очень высокого уровня уверенности, что то, что вы удаляете фактически цитируется текст. Есть два способа сделать это. Во-первых, вы можете использовать Message-ID сообщения, In-Reply-To ID и Thread-Index для определения отдельного сообщения, его родителя и потока, к которому он принадлежит. Для получения дополнительной информации об этом см. RFC822, RFC2822, this interesting article on threading, или this article on threading. После повторной сборки потока вы можете удалить внешний текст (например, To, From, CC и т. Д.), И все готово.

Если сообщения, с которыми вы работаете, не имеют заголовков, вы также можете использовать подобие, чтобы определить, какие части письма являются ответом. В этом случае вы застряли с совпадением подобия, чтобы определить повторяющийся текст. В этом случае вы можете посмотреть на Levenshtein Distance algorithm, например this one on Code Project или this one.

Независимо от того, если вас интересует процесс нарезки, ознакомьтесь с this great PDF on reassembling email threads.

Когда у вас нет нити:

Если вы застряли только с одним сообщением от потока, вы делаете, чтобы попытаться угадать, что цитата. В этом случае мы приводим различные методы котировки:

  1. a line (как видно в перспективе).
  2. Угловые скобки
  3. «--- Исходное сообщение ---»
  4. «О такой-то день, так что-то писал:»

Удалите текст оттуда вниз и вы сделали. Недостатком любого из них является то, что все они предполагают, что отправитель отправил свой ответ поверх цитируемого текста и не чередовал его (как и старый стиль в Интернете). Если это произойдет, удачи. Надеюсь, это поможет некоторым из вас!

26

Прежде всего, это сложная задача.

Вы должны собирать типичные ответы от разных почтовых клиентов и готовить правильные регулярные выражения (или что-то еще) для их анализа. Я собрал ответы от Outlook, Thunderbird, Gmail, Apple Mail и 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: [email protected] [mailto:[email protected]] 
Sent: Tuesday, January 13, 2009 1:27 PM 
---- 
2008/12/26 <[email protected]> 

> text 
---- 
[email protected] wrote: 
> text 
---- 
     [email protected] wrote:   text 
text 
---- 
2009/1/13 <[email protected]> 

> text 
---- 
[email protected] wrote:   text 
text 
---- 
2009/1/13 <[email protected]> 

> text 
> text 
---- 
2009/1/13 <[email protected]> 

> text 
> text 
---- 
[email protected] wrote: 
> text 
> text 
<response here> 
---- 
--- On Fri, 23/1/09, [email protected] <[email protected]> wrote: 

> text 
> text 

С наилучшими пожеланиями, Олег Ярошевич

+2

Добавить к этой фразе «on ... написал» в многоязычных языках ... – Evgeny 2012-09-19 18:35:43

3

Если вы контролируете исходное сообщение (например, уведомления из веб-приложения), вы можете поместить отдельный идентифицируемый заголовок на место и использовать его как разделитель для исходного сообщения.

22

Спасибо, Goleg, для регулярных выражений! Действительно помог. Это не C#, но для Googlers там, вот мой рубин разборе сценарий:

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 

Это работало довольно хорошо до сих пор.

11

До сих пор самый простой способ сделать это путем размещения маркера в содержании, например:

--- Пожалуйста, ответьте выше этой линии ---

Как вы, несомненно, заметили, разбор цитируемого текста не является тривиальной задачей, поскольку разные почтовые клиенты цитируют текст по-разному. Чтобы решить эту проблему должным образом, вам нужно учитывать и тестировать в каждом почтовом клиенте.

Facebook может это сделать, но если у вашего проекта большой бюджет, вы, вероятно, не сможете.

Олег решил проблему с помощью регулярных выражений, чтобы найти «13 июля 2012 года, в 13:09, xxx написал:« текст. Однако, если пользователь удаляет этот текст или отвечает в нижней части письма, как и многие люди, это решение не будет работать.

Аналогично, если почтовый клиент использует другую строку даты или не содержит строку даты, это приведет к ошибке.

4

Вот моя версия C# для кода Ruby @ hurshagrawal. Я не очень хорошо знаю Руби, так что это может быть отключено, но я думаю, что все правильно.

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(); 
} 
1

Это хорошее решение. Нашел его после долгого поиска.

Одно дополнение, как упомянуто выше, в этом случае, поэтому приведенные выше выражения не правильно анализировали мои ответы 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), 

Приветствия

Смежные вопросы