인용 된 답장에서 이메일 콘텐츠 구문 분석


88

포함 할 수있는 인용 된 답장 텍스트에서 이메일 텍스트를 구문 분석하는 방법을 알아 내려고합니다. 나는 보통 이메일 클라이언트가 "그런 날짜에 그렇게 썼다"를 붙이거나 줄 앞에 꺾쇠 괄호를 붙인다는 것을 알아 챘다. 불행히도 모든 사람이 이렇게하는 것은 아닙니다. 응답 텍스트를 프로그래밍 방식으로 감지하는 방법에 대한 아이디어가있는 사람이 있습니까? 이 파서를 작성하기 위해 C #을 사용하고 있습니다.


2
이것에 행운이 있었나요? 나는 똑같은 일을하려고합니다.
— steve_c

전체 소스 코드 샘플이 작동하는 최종 솔루션이 있습니까?
— Kiquenet 2013-06-18

Quotequail 은 Python 으로이 작업을 수행합니다
— philfreo

누구든지 PHP 버전을 도울 수 있습니까?
— user4271704 2015

답변:


60

나는 이것에 대해 더 많은 검색을했고 여기에 내가 찾은 것이 있습니다. 기본적으로이 작업을 수행하는 두 가지 상황이 있습니다. 전체 스레드가있는 경우와없는 경우입니다. 이 두 가지 범주로 나눌 것입니다.

스레드가있는 경우 :

일련의 전체 이메일이있는 경우 제거중인 항목이 실제로 인용 된 텍스트라는 매우 높은 수준의 확신을 얻을 수 있습니다. 이를 수행하는 두 가지 방법이 있습니다. 첫째, 메시지의 Message-ID, In-Reply-To ID 및 Thread-Index를 사용하여 개별 메시지, 상위 메시지 및 해당 스레드를 확인할 수 있습니다. 이에 대한 자세한 내용은 RFC822 , RFC2822 , 스레딩에 대한 흥미로운 기사 또는 스레딩에 대한이 기사를 참조하십시오. . 스레드를 다시 어셈블하면 외부 텍스트 (예 : To, From, CC 등 ... 행)를 제거 할 수 있으며 완료됩니다.

작업중인 메시지에 헤더가없는 경우 유사성 일치를 사용하여 이메일의 어떤 부분이 회신 텍스트인지 확인할 수도 있습니다. 이 경우 반복되는 텍스트를 결정하기 위해 유사성 일치를 수행해야합니다. 이 경우 코드 프로젝트 또는 이 알고리즘 과 같은 Levenshtein Distance 알고리즘 을 살펴볼 수 있습니다 .

스레딩 프로세스에 관심이 있다면 이메일 스레드 재 조립에 대한이 훌륭한 PDF를 확인하십시오 .

스레드가없는 경우 :

스레드에서 단 하나의 메시지 만 갇힌 경우 인용문이 무엇인지 추측해야합니다. 이 경우 내가 본 다른 견적 방법은 다음과 같습니다.

  1. 선 (Outlook에서 볼 수 있음).
  2. 꺾쇠 괄호
  3. "--- 원본 메시지 ---"
  4. "그런 날에 그렇게 썼다."

거기에서 텍스트를 제거하면 완료됩니다. 이들 중 하나의 단점은 모두 발신자가 인용 된 텍스트 위에 답장을 넣고 인터리브하지 않았다고 가정한다는 것입니다 (인터넷의 이전 스타일처럼). 그럴 경우 행운을 빕니다. 나는 이것이 당신 중 일부를 돕기를 바랍니다!


32

우선 이것은 까다로운 작업입니다.

다른 전자 메일 클라이언트에서 일반적인 응답을 수집하고이를 구문 분석 할 올바른 정규식 (또는 기타)을 준비해야합니다. 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: 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

감사합니다, Oleg Yaroshevych


이메일 주소를 모르면 어떻게합니까?
— harsimranb

Shyamal-Parikh @이는 일반적으로 일반 텍스트 메시지는 전자 메일 메시지하지 HTML 이메일에 대한 작업 만 포함됩니다
— maembe

25

정규식에 대해 Goleg, 감사합니다! 정말 도움이되었습니다. 이것은 C #이 아니지만 Google 직원을위한 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

지금까지 꽤 잘 작동했습니다.


1
루비 질문을 만들고 ac # 질문에 게시하는 대신이 코드로 답해야합니다.
— Matthieu 2011 년

6
@Matthieu, C # 질문이 아니라 이메일 및 이메일 구문 분석 질문입니다. 제 생각에는 완전히 관련이 있습니다.
— Trent

@Trent : 그런 다음 C # 태그를 삭제해야합니다.
— Matthieu

7
재미있는 점은 인터넷 검색에서 해당 주제 (언어가 아님)에 대한이 질문을 찾았고 실제로 Ruby로 구현해야했습니다. 그래서 건배!
— bratsche 2012

2
이것은 지금까지 최고의 응답입니다. Regex는 언어에 구애받지 않습니다. 게시 주셔서 감사합니다
— superluminary

11

이를 수행하는 가장 쉬운 방법은 다음과 같은 콘텐츠에 마커를 배치하는 것입니다.

---이 줄 위에 답장 해주세요 ---

의심의 여지없이, 인용 된 텍스트를 구문 분석하는 것은 다른 이메일 클라이언트가 다른 방식으로 텍스트를 인용하므로 사소한 작업이 아닙니다. 이 문제를 제대로 해결하려면 모든 이메일 클라이언트를 고려하고 테스트해야합니다.

Facebook은 이것을 할 수 있지만 프로젝트에 큰 예산이 없다면 아마 그렇게 할 수 없습니다.

Oleg는 정규식을 사용하여 "On 13 Jul 2012, at 13:09, xxx written :"텍스트를 찾아 문제를 해결했습니다. 그러나 사용자가이 텍스트를 삭제하거나 이메일 하단에 답장하면 많은 사람들이하는 것처럼이 솔루션은 작동하지 않습니다.

마찬가지로 이메일 클라이언트가 다른 날짜 문자열을 사용하거나 날짜 문자열을 포함하지 않으면 정규식이 실패합니다.


이 방법은 회신 할 때마다 해당 줄을 입력하지 않는 한 회신에 대한 회신으로 실패합니다.
— jpw

1
예, 단점이 있습니다. 사용자가 행 문자열 위의 응답을 삭제하면 응답이 실패합니다. 이 경우를 파악하고 웹 앱을 통해 회신 할 수있는 링크와 함께 메시지가 실패했음을 알리는 직접 메시지를 사용자에게 보냅니다. 대부분의 사용자는 별다른 문제없이 사용할 수있는 것 같습니다.
— 슈퍼 루미

이것은 받아 들여진 대답이어야합니다. 그러나 줄이 제거되면 대답이 성공하지 못한다는 정보를 추가합니다.
— Benni

@Benni-예, 줄이 제거되면 실패합니다. 안타깝게도 이메일 클라이언트에서 텍스트를 인용하는 표준 방법은 없습니다. 줄이 제거 된 경우 모든 텍스트를 회신으로 처리 할 수 ​​있습니다. 이 경우 완벽한 해결책은 불가능하다고 생각합니다.
— superluminary

@superluminary 내 말은 라인에 추가 할 것입니다. 그래서 그것은 같은 것 -- Please reply above this line. DO NOT REMOVE IT! --입니다. 또한 내가 경험 한 것은 일부 이메일 클라이언트 xxx wrote on <datetime>:가 전체 견적 앞과 그 라인 앞에 한 줄을 추가하기 때문에 항상 작동하지 않는다는 것입니다 . 이 줄은 정규식으로 구문 분석 할 수 있지만 이메일 클라이언트가 다르기 때문에 언어와 형식이 다를 수 있습니다.
— Benni

7

전자 메일에는 회신에 대한 보편적 인 표시가 없습니다. 할 수있는 최선의 방법은 가장 일반적인 패턴을 포착하고 새로운 패턴을 발견 할 때 파싱하는 것입니다.

어떤 사람들은 인용 된 텍스트 안에 답글을 삽입하므로 (예를 들어 내 상사가 내가 질문 한 것과 같은 줄에 질문에 답함) 어떤 일을하더라도 보관하고 싶었던 정보를 잃을 수도 있습니다.


Gmail은 그것을합니다 ... 적어도 그것을하는 것 같습니다. 내가 기억하는 것에서 원본과 응답 사이에 변경되지 않는 스레드 ID가 있습니다 ...
— kenny

Gmail은 다른 이메일 클라이언트와 마찬가지로 '>'를 추가 할 수 있지만 이메일의 표준이 아니며 믿을 수있는 것이 아닙니다.
— 3Doubloons

5

다음은 @hurshagrawal의 Ruby 코드의 C # 버전입니다. 루비를 잘 모르기 때문에 꺼질 수 있지만 제대로 된 것 같습니다.

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

3

원본 메시지 (예 : 웹 응용 프로그램의 알림)를 제어하는 ​​경우 고유하고 식별 가능한 헤더를 배치하고 원본 게시물의 구분자로 사용할 수 있습니다.


0

이것은 좋은 해결책입니다. 너무 오래 찾아서 찾았습니다.

위에서 언급했듯이 한 가지 추가 사항은 대소 문자가 현명하기 때문에 위의 표현은 내 gmail 및 outlook (2010) 응답을 올바르게 구문 분석하지 않았으므로 다음 두 개의 Regex를 추가했습니다. 문제가 있으면 알려주세요.

//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),

건배


누구든지 PHP 버전을 도울 수 있습니까?
— user4271704 2015


-1

그러나 github 에 답장을 추출 하는 Ruby lib 가 있다는 것을 알고 있는지 확실하지 않습니다 . .NET을 사용하는 경우 https://github.com/EricJWHuang/EmailReplyParser에 .NET이 있습니다.


1
외부 리소스에 대한 링크가 권장되지만 링크 주변에 컨텍스트를 추가하여 동료 사용자가 그것이 무엇이며 왜 거기에 있는지 알 수 있도록하십시오. 대상 사이트에 연결할 수 없거나 영구적으로 오프라인 상태가되는 경우를 대비하여 항상 중요한 링크에서 가장 관련성이 높은 부분을 인용하십시오.
— pableiros

그 라이브러리를 최신 상태로 유지하고 있습니까? C # 라이브러리가 Office 365에서 Outlook의 간단한 전자 메일을 제대로 구문 분석하지 못하기 때문에 검색했습니다. 그런 다음 루비 소스 코드를 살펴본 결과 테스트 사례에 동일한 테스트 사례가 있다는 사실을 발견했습니다. 그것.
— Greg Veres 19 년

-2

SigParser.com 의 API 를 사용 하는 경우 단일 이메일 텍스트 문자열에서 회신 체인에있는 모든 분리 된 이메일의 배열을 제공합니다. 따라서 10 개의 이메일이있는 경우 10 개의 이메일 모두에 대한 텍스트를 받게됩니다.

여기에 이미지 설명 입력

여기에서 자세한 API 사양을 볼 수 있습니다.

https://api.sigparser.com/

여기에 이미지 설명 입력

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.