E-posta içeriğini alıntılanan yanıttan ayrıştırın


88

Bir e-postanın metnini, içerebileceği alıntılanmış yanıt metninden nasıl ayrıştıracağımı anlamaya çalışıyorum. E-posta istemcilerinin genellikle "Şu ve böyle bir tarihte" yazacağını veya satırların önüne açılı ayraç koyacağını fark ettim. Ne yazık ki bunu herkes yapmaz. Yanıt metninin programlı olarak nasıl tespit edileceği konusunda herhangi bir fikri olan var mı? Bu ayrıştırıcıyı yazmak için C # kullanıyorum.


2
Bununla hiç şansın oldu mu? Ben de aynı şeyi yapmaya çalışıyorum.
steve_c

tam kaynak kod örneği üzerinde çalışan herhangi bir nihai çözüm var mı?
Kiquenet

Quotequail bunu Python'da yapıyor
philfreo

Biri onun php sürümü için yardım edebilir mi?
user4271704

Yanıtlar:


60

Bu konuda çok daha fazla araştırma yaptım ve işte bulduklarım. Bunu yaptığınız temelde iki durum vardır: iş parçacığının tamamına sahip olduğunuzda ve olmadığınızda. Bunu bu iki kategoriye ayıracağım:

Konuya sahip olduğunuzda:

Tüm e-posta dizisine sahipseniz, kaldırmakta olduğunuz şeyin aslında alıntılanmış metin olduğuna dair çok yüksek düzeyde bir güvence elde edebilirsiniz. Bunu yapmanın iki yolu var. Birincisi, mesajın Mesaj Kimliği, Yanıtla Kimliği ve Konu Dizini'ni tek tek mesajı, üst iletiyi ve ait olduğu iş parçacığını belirlemek için kullanabilirsiniz. Bununla ilgili daha fazla bilgi için RFC822 , RFC2822'ye , diş açma hakkındaki bu ilginç makaleye veya diş açma hakkındaki bu makaleye bakın . İş parçacığını yeniden birleştirdikten sonra, harici metni (Kime, Kimden, Bilgi, vb. Satırlar gibi) kaldırabilirsiniz ve bitirdiniz.

Üzerinde çalıştığınız iletilerin başlıkları yoksa, benzerlik eşleştirmesini bir e-postanın hangi bölümlerinin yanıt metni olduğunu belirlemek için de kullanabilirsiniz. Bu durumda, tekrarlanan metni belirlemek için benzerlik eşleştirmesi yapmak zorunda kalırsınız. Bu durumda Code Project veya bunun gibi bir Levenshtein Mesafe algoritmasına bakmak isteyebilirsiniz .

Ne olursa olsun, iş parçacığı oluşturma süreciyle ilgileniyorsanız, e-posta ileti dizilerini yeniden birleştirme hakkındaki bu harika PDF'ye göz atın .

Konuya sahip olmadığınızda:

Mesaj dizisindeki tek bir mesajla takılıp kalırsanız, alıntının ne olduğunu tahmin etmeye çalışmanız gerekir. Bu durumda, gördüğüm farklı teklif yöntemleri şunlardır:

  1. bir çizgi (görünümde görüldüğü gibi).
  2. Açılı Parantez
  3. "---Orijinal mesaj---"
  4. "Şu ve bu günde, şöyle yazmış:"

Metni oradan kaldırın ve bitirdiniz. Bunlardan herhangi birinin dezavantajı, gönderenin yanıtını alıntılanan metnin üstüne koyduğunu ve araya eklemediğini varsaymasıdır (internetteki eski stil gibi). Böyle bir şey olursa iyi şanslar. Umarım bu bazılarınıza yardımcı olur!


32

Her şeyden önce, bu zor bir görevdir.

Farklı e-posta istemcilerinden tipik yanıtlar toplamalı ve bunları ayrıştırmak için doğru normal ifadeler (veya her neyse) hazırlamalısınız. Outlook, thunderbird, gmail, apple mail ve mail.ru'dan yanıtlar topladım.

Yanıtı şu şekilde ayrıştırmak için normal ifadeler kullanıyorum: eğer ifade eşleşmezse, bir sonrakini kullanmaya çalışırım.

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

Sonunda teklifi kaldırmak için:

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

İşte benim küçük test yanıtları koleksiyonum (örneklerin --- ile bölünmesi ):

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

Saygılarımızla, Oleg Yaroshevych


Ya e-posta adresini bilmiyorsam?
harsimranb

@ Shyamal-Parikh bu html e-postaları için işe yaramaz, ancak genellikle düz metin bir mesaj e-posta iletilerine dahil edilir
maembe

26

Normal ifadeler için teşekkürler Goleg! Gerçekten yardımcı oldu. Bu C # değil, ancak oradaki Google çalışanları için Ruby ayrıştırma komut dosyam:

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

Şimdiye kadar oldukça iyi çalıştı.


1
Ruby sorusu yapmalı ve ac # sorusuna göndermek yerine bu kodla cevaplamalısınız.
Matthieu

6
@Matthieu, bu sadece bir C # sorusu değil, bir e-posta ve e-posta ayrıştırma sorusu. bence tamamen alakalı.
Trent

@Trent: C # etiketi daha sonra bırakılmalıdır.
Matthieu

7
İşin garibi, bu soruyu Googling tarafından konu için buldum (dil değil) ve aslında Ruby'de bir şey uygulamaya ihtiyacım vardı. Öyleyse şerefe!
bratsche

2
Bu şimdiye kadarki en iyi yanıt. Regex oldukça dilden bağımsızdır.
Gönderi

11

Bunu yapmanın en kolay yolu, içeriğinize aşağıdakiler gibi bir işaretçi yerleştirmektir:

--- Lütfen bu satırın yukarısını yanıtlayın ---

Hiç şüphe duymadığınız gibi, alıntılanan metni ayrıştırmak önemsiz bir iş değildir, çünkü farklı e-posta istemcileri metni farklı şekillerde alıntılamaktadır. Bu sorunu düzgün bir şekilde çözmek için her e-posta istemcisini hesaba katmanız ve test etmeniz gerekir.

Facebook bunu yapabilir, ancak projenizin büyük bir bütçesi yoksa, muhtemelen yapamazsınız.

Oleg sorunu normal ifadeler kullanarak çözdü ve "13 Temmuz 2012 tarihinde 13: 09'da xxx şunu yazdı:" metnini buldu. Ancak, kullanıcı bu metni silerse veya e-postanın altında yanıtlarsa, birçok kişinin yaptığı gibi, bu çözüm çalışmayacaktır.

Benzer şekilde, e-posta istemcisi farklı bir tarih dizesi kullanıyorsa veya bir tarih dizesi içermiyorsa normal ifade başarısız olacaktır.


Bu yaklaşım, her yanıt verdiğinizde bu satırı koymadığınız sürece yanıtlara verilen yanıtlarla başarısız olur.
jpw

1
Evet, sakıncaları var. Kullanıcı satır dizesinin üstündeki yanıtı silerse, yanıtınız başarısız olur. Bu vakayı yakalıyorum ve kullanıcıya mesajlarının başarısız olduğunu bildiren bir doğrudan mesaj gönderiyorum ve web uygulaması üzerinden cevap verebilecek bir bağlantı var. Çoğu kullanıcı bunu çok fazla sorun yaşamadan kullanabiliyor gibi görünüyor.
süper parlak ışık

Kabul edilen cevap bu olmalıdır. Ancak, satır kaldırılırsa cevabın başarılı olmayacağı bilgisini ekleyeceğim.
Benni

@Benni - evet, hat kaldırılırsa başarısız olur. Ne yazık ki, e-posta istemcileri arasında metin alıntı yapmanın tek bir standart yolu yoktur. Satırın kaldırılması durumunda, tüm metni bir yanıt olarak kabul edebilirsiniz. Bu durumda mükemmel bir çözümün mümkün olduğunu düşünmüyorum.
süper aydınlık

@superluminary Demek istediğim, satıra ekleyecektim. Yani bu gibi bir şey -- Please reply above this line. DO NOT REMOVE IT! --. Ayrıca, bazı e-posta istemcileri xxx wrote on <datetime>:tüm tekliften önce ve dolayısıyla bu satırdan önce bir satır ekledikleri için her zaman işe yaramayacağını deneyimledim . Bu satır normal ifade ile ayrıştırılabilir, ancak e-posta istemcileri farklı olduğundan farklı dillerde ve farklı bir biçimde olabilir.
Benni

7

Bir e-postada yanıtın evrensel bir göstergesi yoktur. Yapabileceğiniz en iyi şey, en yaygın olanları yakalamaya ve karşılaştığınız yeni kalıpları çözümlemeye çalışmaktır.

Bazı kişilerin alıntılanan metnin içine yanıtlar eklediklerini unutmayın (örneğin, patronum soruları sorduğum satırda yanıtlar), böylece ne yaparsanız yapın, saklamak isteyeceğiniz bazı bilgileri kaybedebilirsiniz.


gmail bunu yapıyor ... en azından yapıyor gibi görünüyor. Hatırladığım kadarıyla, orjinal ve cevaplar arasında değişmeyen bir iplik kimliği var ...
kenny

gmail, diğer e-posta istemcilerinde olduğu gibi '>' ekleyebilir, ancak bu bir e-posta standardı değildir ve güvenebileceğiniz bir şey değildir
3Doubloons

6

İşte @ hurshagrawal'ın Ruby kodunun C # sürümü. Ruby'yi gerçekten iyi tanımıyorum, bu yüzden kapalı olabilir, ama sanırım doğru anladım.

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

Orijinal mesajı kontrol ediyorsanız (örneğin, bir web uygulamasından gelen bildirimler), farklı, tanımlanabilir bir başlık koyabilir ve bunu orijinal gönderi için sınırlayıcı olarak kullanabilirsiniz.


0

Bu iyi bir çözüm. Uzun süre aradıktan sonra buldum.

Bir ek, yukarıda belirtildiği gibi, bu durum açısından önemlidir, bu nedenle yukarıdaki ifadeler gmail ve outlook (2010) yanıtlarımı doğru şekilde ayrıştırmadı, bunun için aşağıdaki iki Regex'i ekledim. Herhangi bir sorun için bana haber verin.

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

Şerefe


Biri onun php sürümü için yardım edebilir mi?
user4271704


-1

Bu eski bir gönderidir, ancak github'ın yanıtı çıkaran bir Ruby kütüphanesine sahip olup olmadığınızdan emin değilsiniz . .NET kullanıyorsanız, https://github.com/EricJWHuang/EmailReplyParser adresinde bir .NET var.


1
Dış kaynaklara bağlantılar teşvik edilmektedir, ancak lütfen bağlantının etrafına bağlam ekleyin, böylece diğer kullanıcılarınız bunun ne olduğu ve neden orada olduğu konusunda fikir sahibi olur. Hedef sitenin erişilemez olması veya kalıcı olarak çevrimdışı olması durumunda, her zaman önemli bir bağlantının en alakalı kısmını alıntılayın.
pableiros

o kütüphaneyi güncel tutuyor musunuz? Aramaya geldim çünkü C # kitaplığı Outlook'tan Office 365'ten gelen basit bir e-postayı uygun şekilde ayrıştırmıyor. Sonra Ruby kaynak koduna baktım ve test senaryolarında aynı test vakası olduğunu buldum, o kadar net bir şekilde ayrıştırmaları gerektiğini düşünüyorlar o.
Greg Veres

-2

Eğer kullanırsanız SigParser.com 'ın API, size tek bir e-posta metin dizesinden bir cevap zincirindeki tüm patlak e-postaların bir dizi verecektir. Yani 10 e-posta varsa, 10 e-postanın tümü için metin alırsınız.

görüntü açıklamasını buraya girin

Ayrıntılı API spesifikasyonunu burada görüntüleyebilirsiniz.

https://api.sigparser.com/

görüntü açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.