一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

使用Perl處理電子郵件的方法的演化

 hj_18 2012-06-07

使用Perl處理電子郵件的方法的演化

作者: 來源:http://www. 時間:2007-5-10 15:07:31

譯者/作者:chunzi
出處:中國Perl協(xié)會 FPC(Foundation of Perlchina)
原名:The Evolution of Perl Email Handling
作者:Simon Cozens
原文:http://www./pub/a/2004/06/10/email.html
發(fā)表:June 10, 2004
請保護作者的著作權,維護作者勞動的結晶。

每天我都要花費大量的時間在電子郵件相關的工作上,或者通過郵件來和其他工作伙伴聯(lián)系,或者饒有興致地分析,索引,重新組織以及挖掘郵件內容。很自然的,Perl 協(xié)助我做這些事情。

在 CPAN 上有很多現(xiàn)成的模塊可以用來處理電子郵件,我們將介紹其中幾個主要的。同時我們也將關注由我和 Richard Clamp,Simon Wistow 以及其他伙伴所致力的 Perl 電子郵件項目(Perl Email Project),該項目的目標是提供一系列簡單的,有效的,精準的郵件處理模塊。

郵件消息的處理

我們從一些比較簡單的,用來描繪一封單獨郵件,提供對郵件頭和郵件體的訪問,甚至修改它們的信息的那些模塊開始介紹。

所有的這些模塊的曾祖父都是 Mail::Internet ,由 Graham Barr 創(chuàng)建,目前 Mark Overmeer 在維護。該模塊提供了通過數(shù)組(元素為字符串行)或者文件句柄來讀取信件內容的構造器,并通過它返回一個描述該信件的 Mail::Internet 對象。在下面的例子中,我們使用變量 $rfc2822 來表示字符串形式的郵件信息內容。

    my $obj = Mail::Internet->new( [ split /\n/, $rfc2822 ] );
Mail::Internet 從信件中提取構造出一個郵件頭對象,并連帶郵件體信息。郵件頭對象的類為 Mail::Header 。你可以通過該對象獲取或者設置郵件頭的信息:
    my $subject = $obj->head->get("Subject");    $obj->head->replace("Subject", "New subject");
而讀取或者編輯郵件體內容的操作,則可以使用 body 方法:
    my $old_body = $obj->body;    $obj->body("Wasn't worth reading anyway.");
到現(xiàn)在為止我還沒有提到過任何關于 MIME 的東西。對于簡單的任務來說,Mail::Internet 確實非常方便,不過它并不完全支持對 MIME 的處理。謝天謝地,MIME::Entity 作為一個為 MIME 而考慮設計的 Mail::Internet 子類,允許你讀取 MIME 消息的每一個獨立的部分(part):
    my $num_parts = $obj->parts;    for (0..$num_parts) {        my $part = $obj->parts($_);        ...    }
如果 Mail::InternetMIME::Entity 都不適合你,你可以試試 Mark Overmeer 自己的 Mail::Message 模塊,該模塊是令人印象深刻的 Mail::Box 模塊中的一部分。Mail::Message 是個極富特色的、功能全面的模塊,但這些優(yōu)點并不總意味著褒揚。

Mail::Message 對象通常都是在 Mail::Box 讀取一個電子郵件文件夾的時候,在內部構建的。當然它也可以通過 read 方法來讀取一封信件:

    $obj = Mail::Message->read($rfc2822);
就像 Mail::Internet 一樣,郵件消息被分割為郵件頭和郵件體,而與 Mail::Internet 不同的是,郵件體也是一個對象。我們如此讀取郵件頭:
    $obj->head->get("Subject");
或者,如果是 Subject 頭信息以及其他常見的郵件頭信息,可以如此讀?。?
    $obj->subject;
我找不到直接設置頭信息的方法,所以最終可能需要這樣做:
    $obj->head->delete($header);    $obj->head->add($header, $_) for @data;
讀取郵件體內容作為字符串形式表達也僅有一點麻煩:
    $obj->decoded->string
而設置郵件體內容的操作則絕對是惡夢 -- 我們不得不構建一個 Mail::Message::Body 對象來覆蓋現(xiàn)有的。
    $obj->body(Mail::Message::Body->new(data => [split /\n/, $body]));
Mail::Message 處理郵件的時候可能有點慢,也著實難用。它的體系也非常復雜,上面我們所看到的這些操作就已經(jīng)用到了 16 種類 (Mail::Address, Mail::Box::Parser, Mail::Box::Parser::Perl, Mail::Message, Mail::Message::Body, Mail::Message::Body::File, Mail::Message::Body::Lines, Mail::Message::Body::Multipart, Mail::Message::Body::Nested, Mail::Message::Construct, Mail::Message::Field, Mail::Message::Field::Fast, Mail::Message::Head, Mail::Message::Head::Complete, Mail::Message::Part, 以及 Mail::Reporter)和 4400 多行的代碼。盡管它確實擁有很多功能,我還是傻傻的覺得郵件的分析處理應該更為簡潔。所以我坐下來決定自己著手編寫盡可能簡潔的郵件處理函數(shù)庫,結果就有了 Email::Simple 模塊,它的交互界面如下所示:
    my $obj = Email::Simple->new($rfc2822);    my $subject = $obj->header("Subject");    $obj->header_set("Subject", "A new subject");    my $old_body = $obj->body;    $obj->body_set("A new body\n");    print $obj->as_string;
它做的事情并不多,但卻非常簡單和高效。如果你需要 MIME 處理,可以使用它的子類 Email::MIME, 該類增加了 parts 方法。

實際上,選擇哪一種郵件處理函數(shù)庫完全取決于你,最終用戶,不過并不總是這樣的。有許多輔助性的模塊,幫助你在更高的應用層上處理郵件信息的,可能要求你提供特定的郵件表達對象。比如最近的 Mail::ListDetector 模塊(稍后我們將解析),需要傳給它的郵件為 Mail::Internet 對象,因為該對象的操作界面(API)是已知的。而我不想用 Mail::Internet 對象,但我又需要 Mail::ListDetector 的一些功能,那我可以做些什么呢?

為了讓用戶也能夠有這樣的選擇,我寫了一個用于表達上面各個模塊操作界面的抽象層,叫做 Email::Abstract 。給出上面任何一種類型的對象,我們都可以說:

     my $subject = Email::Abstract->get_header($obj, "Subject");     Email::Abstract->set_header($obj, "Subject", "My new subject");     my $body = Email::Abstract->get_body($obj);     Email::Abstract->set_body($message, "Hello\nTest message\n");     $rfc2822 = Email::Abstract->as_string($obj);
Email::Abstract 知道如何在這些主要的郵件表達對象上作相應的操作。它也抽象了構造郵件消息的過程,并允許你通過類方法 cast 來改變郵件消息對象的操作界面:
    my $obj = Email::Abstract->cast($rfc2822, "Mail::Internet");
my $mm = Email::Abstract->cast($obj, "Mail::Message"); 這樣使得模塊的作者得以使用“接口預先未知(interface-agnostic)”的方式來撰寫郵件處理函數(shù)庫。我很感謝 Michael Stevens 立即在 Mail::ListDetector 中使用了 Email::Abstract ?,F(xiàn)在我可以將 Email::Simple 對象傳遞給 Mail::ListDetector 了,而且它工作的非常好。

Email::Abstract 也給了我們對上面所有這些模塊作基準測試(benchmarks)的機會。這里是我使用的測試代碼:

    use Email::Abstract;    my $message = do { local $/; ; };    my @classes =        qw(Email::MIME Email::Simple MIME::Entity Mail::Internet Mail::Message);    eval "require $_" or die $@ for @classes;    use Benchmark;    my %h;    for my $class (@classes) {        $h{$class} = sub {            my $obj = Email::Abstract->cast($message, $class);            Email::Abstract->get_header($obj, "Subject");            Email::Abstract->get_body($obj);            Email::Abstract->set_header($obj, "Subject", "New Subject");            Email::Abstract->set_body($obj, "A completely new body");            Email::Abstract->as_string($obj);        }    }    timethese(1000, \%h);    __DATA__    ...
我把一封短小的郵件放到 DATA 部分中,并運行相同的操作一千次:構造一個新的消息對象,讀取郵件頭,讀取郵件體,并將消息內容作為字符串返回。
    Benchmark: timing 1000 iterations of Email::MIME, Email::Simple,     MIME::Entity, Mail::Internet, Mail::Message...    Email::MIME: 10 wallclock secs ( 7.97 usr +  0.24 sys =  8.21 CPU)         @ 121.80/s (n=1000)    Email::Simple:  9 wallclock secs ( 7.49 usr +  0.05 sys =  7.54 CPU)         @ 132.63/s (n=1000)    MIME::Entity: 33 wallclock secs (23.76 usr +  0.35 sys = 24.11 CPU)         @ 41.48/s (n=1000)    Mail::Internet: 24 wallclock secs (17.34 usr +  0.30 sys = 17.64 CPU)         @ 56.69/s (n=1000)    Mail::Message: 20 wallclock secs (17.12 usr +  0.27 sys = 17.39 CPU)         @ 57.50/s (n=1000)
Perl 電子郵件項目確實是成功的:Email::MIMEEmail::Simple 的運行速度差不多是對手的兩倍。然而,我們要強調一點,這里所做的測試都是非常低級的,如果你要做任何比這里看到的更加復雜的操作,你該考慮哪些老的 Mail:: 模塊。

郵箱的處理

對于單獨信件的處理已經(jīng)談了很多了,讓我們來看看對一組郵件或者存放郵件的文件夾該如何處理。我們提到過 Mail::Box ,它絕對是處理郵件文件夾的老大,它支持本地和遠程的文件夾處理,可以編輯文件夾,以及作相應的排序操作等等。要使用它,我們首先需要 Mail::Box::Manager 模塊,它是用來構建 Mail::Box 對象的工廠對象。
    use Mail::Box::Manager    my $mgr = Mail::Box::Manager->new;
接下來,我們通過管理器來打開文件夾:
    my $folder = $mgr->open(folder => $folder_file);
而現(xiàn)在,我們可以獲取各個獨立的郵件表達對象(Mail::Message):
    for ($folder->messages) {        print $_->subject,"\n";    }
與此最為相近的,我喜歡用的郵箱管理器還是 Mail::Utilread_mbox 函數(shù)。把 Unix 中 mbox 文件路徑傳遞給它,然后返回一系列的匿名數(shù)組,每個匿名數(shù)組都表示一個郵件消息,其元素為該消息的每一行。如此一來,它非常適合 Mail::Internet->new 或者相近的:
    for (read_mbox($folder_file)) {        my $obj = Mail::Internet->new($_);        print $_->head->get("Subject"),"\n";    }
這兩種做法都非常容易,不過似乎在 Mail::Util 的簡潔性和 Mail::Box 的功能上還有些簡化的余地,于是電子郵件項目再次停滯下來,這次的焦點集中在 Email::FolderEmail::LocalDelivery 上面。 Email::Folder 可以處理 mbox 和 maildir 格式的郵件文件夾,以及計劃中更多其他格式,并且它有非常簡潔的操作界面:
    my $folder = Email::Folder->new($folder_file);    for ($folder->messages) {        print $_->header("Subject"),"\n";    }
默認情況,它返回一系列 Email::Simple 對象用以表達每封郵件,不過這可以通過派生一個子類來改變。例如,如果我們想要原始的 RFC2822 格式的字符串,我們可以這樣做:
    package Email::Folder::Raw; use base 'Email::Folder';    sub bless_message { my ($self, $rfc2822) = @_; return $rfc2822; }
可能將來我們不用再派生一個子類,然后 bless_message ,而改用 Email::Abstract->cast 來更容易的改變對郵件消息的表達方式。

處理文件夾的另一方面就是如何寫數(shù)據(jù)了?;蛘哒f如何本地投遞。Email::LocalDelivery 模塊的出現(xiàn)是為了輔助 Email::Filter 。問題比聽起來要更難些,因為它必須處理鎖定,跳開郵件體,以及由 mailbox 和 maildir 等不同格式而引發(fā)的問題。而 LocalDelivery 則通過簡單的界面把所有這些都隱藏起來:

    Email::LocalDelivery->deliver($rfc2822, @mailboxes);
Email::LocalDeliveryEmail::Folder 都使用了 Email::FolderType 模塊來幫助確定是哪種類型的郵件文件夾(通過文件名來判斷)。

郵件地址的處理

我們再次從抽象層面回到低級的處理,有大量的模塊可用于對郵件地址的處理。我很喜歡老的 Mail::Address 模塊。郵件地址可以分割為各種字段,諸如:實際的郵件地址,名稱短語,注釋信息。例如:
    Example user  (Not a real user)
Mail::Address 解析這些郵件地址,并將名稱短語和注釋分離出來,以便獲取各個獨立的部分:
    for (Mail::Address->parse($from_line)) {        print $_->name, "\t", $_->address, "\n";    }
不幸的是,和其他很多郵件模塊一樣,并不真的那么有用。
    my ($addr) = Mail::Address->parse('"eBay, Inc." ');    print $addr->name # Inc. eBay
得到的結果仍然難以讓人接受,雖然它比之間的版本所返回的 "Inc Ebay" 要好些。于是 Casey West 加入我們并創(chuàng)造了 Email::Address 模塊。它和 Mail::Address 使用一致的交互界面,并且運行地更加快速,差不多兩到三倍。(譯注:上面的例子中,Email::Address 返回 "eBay, Inc." ??磥碓谧髡哐劾?,Mail::Address 的作者畫蛇添足了。)

還有一件我們經(jīng)常需要做的事情就是校驗郵件地址是否合法。比如,某個用戶在站點上注冊,我們就需要對他所提供的郵件地址是否能夠接收郵件作檢查。Email::Valid 模塊是在我們這幫叛逆的人沖進來之前,就已有的 Email:: 名字空間的原住民,這個模塊就是用來做這件事情的。在它最簡約的用法中,我們可以說:

    if (not Email::Valid->address('test@example.com')) {        die "Not a valid address"    }
你也可以打開其他檢查的選項,比如確定它的域名擁有一個合法的 MX 記錄,修正常見的 AOL 和 Compuserve 的郵件地址的一些錯誤,如下:
    if (not Email::Valid->address(-address => 'test@example.com',                                  -mxcheck => 1)) {        die "Not a valid address"    }

郵件數(shù)據(jù)轉換

我們有了自己的信件,接下來會對它們做些什么呢?我發(fā)現(xiàn)大多是對郵件進行文本化分析,這里有三個模塊可以協(xié)助我們:

首先是 Text::Quoted ,它獲取郵件體的文本,實際上可以是任何其他文本,然后嘗試找出某些引用其他郵件的文本部分,然后將之分離并保存到嵌套的數(shù)據(jù)結構中。例如,如果我們有

    $message = < foo    > # Bar    > baz    quux    EOF
然后運行 extract($message) 就會返回如下的數(shù)據(jù)結構:
    [      [        { text => 'foo', quoter => '>', raw => '> foo' },        [             { text => 'Bar', quoter => '> #', raw => '> # Bar' }         ],        { text => 'baz', quoter => '>', raw => '> baz' }      ],      { empty => 1 },      { text => 'quux', quoter => '', raw => 'quux' }    ];
當你顯示郵件消息的內容時,準備用不同的顏色來區(qū)分不同的引用文本,那么這個模塊就幫到你大忙了。類似概念的還有 Text::Original 模塊,用于搜尋以原始文件內容開頭,沒有被引用的部分。它知道如何識別各種類型的屬性行,所以有:
    $message = < Why are there so many different mail modules?    There's more than one way to do it! Different modules have different    focuses, and operate at different levels; some lower, some higher.    EOF
那么 first_sentence($message) 將返回 There's more than one way to do it!。Mariachi 郵件列表存檔程序就使用了這項技術,為一個線索中的郵件給出它的提白。

說到郵件的線索化,Mail::Thread 模塊實現(xiàn)了 Jamie Zawinski 的郵件線索化算法,該算法先是被 Mozilla 所用,繼而許多其他郵件客戶端也開始使用這種技術。當然 Mariachi 也使用了這項技術,最近它還作了更新,使用 Email::Abstract 來處理各種你扔過去的郵件表達對象:

    my $threader = Mail::Thread->new(@mails);    $threader->thread; # 計算線索    for ($threader->rootset) { # 在一個線索內的原始郵件        dump_thread($_);    }

郵件過濾

經(jīng)典的 Perl 的郵件過濾工具莫不就是 Mail::Audit 了,我還在這里寫過關于如何使用 Mail::Audit 模塊的文章(http://www./pub/a/2001/07/17/mailfiltering.html),以及如何與 Mail::SpamAssassinhttp://www./pub/a/2002/03/06/spam.html)模塊相結合使用。

我們已經(jīng)提到過 Mail::ListDetector 模塊好幾次了。我把它和 Mail::Audit 結合在一起使用,幫助自己做了大量的自動郵件過濾工作。Mail::Audit::List 的插件使用 ListDetector 來查找信件中的郵件列表頭信息,諸如 List-Id,X-Mailman-Version 等等類似的東西,這些頭信息可以幫助判別該郵件是否來自于郵件列表。這意味著我有能力過濾所有來自郵件列表的信件到各自的文件夾中,就像這樣:

    my $list = Mail::ListDetector->new($obj);    if ($list) {        my $name = $list->listname;        $item->accept("mail/$name.-$date");    }
然而,Mail::Audit 本身還有很長一段路要走,所以如果你新架設的系統(tǒng)的話,我們鼓勵您使用電子郵件項目的 Email::Filter 模塊作為替代,它們的大部分操作界面是一致的,盡管功能并不完全相同。為了追求簡潔和速度,它使用了新式的 Email::Simple 作為郵件表達對象模塊。

郵件信息挖掘

最后,我所做的比較高級的事情就是開發(fā)一個自動分類,組織,并索引郵件到數(shù)據(jù)庫的應用框架,并嘗試從中分析并提取有價值的信息。

我的第一個完成這個預期目標的模塊是 Mail::Miner ,它由三個主要部分組成。第一個部分獲取一封郵件后,去除各種附件,并分別存儲到數(shù)據(jù)庫。第二部分縱覽這封郵件并運行一系列的識別 (Recogniser)模塊,如此搜尋郵件地址,電話號碼,一些關鍵字和短語等等,并把它們存儲到另一個獨立的數(shù)據(jù)庫表中。第三部分為命令行工具,用來 查詢數(shù)據(jù)庫中的郵件以及相關的信息。

舉個例子,如果我需要找 Tim O'Reilly 的郵政地址,我就會使用查詢工具 mm ,從他發(fā)來的信中找出該地址:

 % mm --from "Tim O" --address               Address found in message 1835 from "Tim O'Reilly" : Tim O'Reilly @ O'Reilly %26amp; Associates, Inc. 1005 Gravenstein Highway North, Sebastopol, CA 95472
如果要獲取完整的郵件,我可以說
 % mm --id 1835
如果它原本包含一個附件,那么我們可能會看到類似下面的部分:
 [ text/xml attachment something.xml detached - use   mm --detach 208   to recover ]
我粘貼中間的那一行 mm --detach 208 到 shell 中,然后很快的,something.xml 寫到了磁盤上。

現(xiàn)在 Mail::Miner 已經(jīng)非常不錯了,不過它把三種思想緊緊地捆綁在一個包中 -- 郵件的歸檔,郵件的數(shù)據(jù)挖掘以及查詢數(shù)據(jù)庫的命令行界面 -- 這使得很難單獨開發(fā)或者擴展每塊的功能。當然,它使用了老式的 Mail:: 名字空間。

這引領我們走到這次郵件模塊旅程的最后一站,最新發(fā)布的:Email::Store 模塊。這是個基于 Class::DBI 的應用框架,用來存儲郵件到數(shù)據(jù)庫并以各種方式索引:

   use Email::Store 'dbi:SQLite:mail.db';   Email::Store->setup;   Email::Store::Mail->store($rfc2822);
緊接著...
   my ($name) = Email::Store::Name->search( name => "Simon Cozens" )   @mails_from_simon = $name->addressings( role => "From" )->mails;
它可以用來構建類似 Mariachi 的郵件列表歸檔工具,或者類似 Mail::Miner 的數(shù)據(jù)挖掘。它仍然在初步的開發(fā)階段,并在增強模塊的擴展性方面使用了一些新的思想。

在我們使用 Email::Store 寫出第一個郵件歸檔和搜索工具的時候,我會再次給大家作詳細介紹的。這也是為了 perl.org 的新的 Perl 郵件列表處理接口而準備做的工作。

小結

我們已經(jīng)看過了 CPAN 上的幾個主要的郵件處理模塊,當然還有更多。很明顯的,我著實偏袒那些自己寫的模塊。特定的 Perl 電子郵件項目的模塊則使用 Email::* 的名字空間。我們特別設計了這些簡潔、高效的模塊,而它們并不總是老式的 Mail::* 模塊的優(yōu)良替換方案,特別像 Mail::Box 之類。到此,我希望各位通過對本文的閱讀,了解和認識更多的郵件處理工具模塊,并在之后使用 Perl 來處理郵件時,胸中有丘壑。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国产麻豆精品福利在线| 日本加勒比在线观看不卡| 久久成人国产欧美精品一区二区 | 国产中文字幕久久黄色片| 一二区不卡不卡在线观看| 91精品国产av一区二区| 国产人妻熟女高跟丝袜| 国产午夜福利在线观看精品| 亚洲男人的天堂久久a| 成人欧美一区二区三区视频| 国产精品偷拍一区二区| 中文字幕人妻日本一区二区| 日本 一区二区 在线| 亚洲精品国产主播一区| 欧美日韩精品久久第一页| 日本午夜乱色视频在线观看| 久久精品国产熟女精品| 91亚洲精品亚洲国产| 日本不卡一本二本三区| 黄片免费在线观看日韩| 99一级特黄色性生活片| 国产免费成人激情视频| 手机在线观看亚洲中文字幕| 免费在线成人激情视频| 一级片黄色一区二区三区| 欧美日韩综合在线第一页| 国产亚洲精品岁国产微拍精品| 欧美日韩免费黄片观看| 激情中文字幕在线观看| 国产成人午夜av一区二区| 欧美不卡一区二区在线视频| 亚洲国产中文字幕在线观看| 午夜视频在线观看日韩| 冬爱琴音一区二区中文字幕| 亚洲中文字幕日韩在线| 又色又爽又无遮挡的视频| 欧美一级片日韩一级片| 美国欧洲日本韩国二本道| 91后入中出内射在线| 黄色国产一区二区三区| 日本高清二区视频久二区|