| 導購 | 订阅 | 在线投稿
分享
 
 
當前位置: 王朝網路 >> perl >> 使用Perl處理電子郵件的方法的演化
 

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

2008-05-30 23:04:00  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
 
  譯者/作者:chunzi

  出處:中國Perl協會 FPC(Foundation of Perlchina)

  原名:The Evolution of Perl Email Handling

  作者:Simon Cozens

  原文:http://www.perl.com/pub/a/2004/06/10/email.html

  發表:June 10, 2004

  請保護作者的著作權,維護作者勞動的結晶。

  每天我都要花費大量的時間在電子郵件相關的工作上,或者通過郵件來和其他工作夥伴聯系,或者饒有興致地分析,索引,重新組織以及挖掘郵件內容。很自然的,Perl 協助我做這些事情。 在 CPAN 上有很多現成的模塊可以用來處理電子郵件,我們將介紹其中幾個主要的。同時我們也將關注由我和 Richard Clamp,Simon Wistow 以及其他夥伴所致力的 Perl 電子郵件項目(Perl Email Project),該項目的目標是提供一系列簡單的,有效的,精准的郵件處理模塊。

  郵件消息的處理我們從一些比較簡單的,用來描繪一封單獨郵件,提供對郵件頭和郵件體的訪問,甚至修改它們的信息的那些模塊開始介紹。 所有的這些模塊的曾祖父都是 Mail::Internet ,由 Graham Barr 創建,目前 Mark Overmeer 在維護。該模塊提供了通過數組(元素爲字符串行)或者文件句柄來讀取信件內容的構造器,並通過它返回一個描述該信件的 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.");

  到現在爲止我還沒有提到過任何關于 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::Internet 和 MIME::Entity 都不適合你,你可以試試 Mark Overmeer 自己的 Mail::Message 模塊,該模塊是令人印象深刻的 Mail::Box 模塊中的一部分。Mail::Message 是個極富特色的、功能全面的模塊,但這些優點並不總意味著褒揚。 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 對象來覆蓋現有的。 $obj-body(Mail::Message::Body-new(data = [split /\n/, $body]));

  Mail::Message 處理郵件的時候可能有點慢,也著實難用。它的體系也非常複雜,上面我們所看到的這些操作就已經用到了 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 多行的代碼。盡管它確實擁有很多功能,我還是傻傻的覺得郵件的分析處理應該更爲簡潔。所以我坐下來決定自己著手編寫盡可能簡潔的郵件處理函數庫,結果就有了 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 方法。 實際上,選擇哪一種郵件處理函數庫完全取決于你,最終用戶,不過並不總是這樣的。有許多輔助性的模塊,幫助你在更高的應用層上處理郵件信息的,可能要求你提供特定的郵件表達對象。比如最近的 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)」的方式來撰寫郵件處理函數庫。我很感謝 Michael Stevens 立即在 Mail::ListDetector 中使用了 Email::Abstract 。現在我可以將 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::MIME 和 Email::Simple 的運行速度差不多是對手的兩倍。然而,我們要強調一點,這裏所做的測試都是非常低級的,如果你要做任何比這裏看到的更加複雜的操作,你該考慮哪些老的 Mail:: 模塊。 郵箱的處理對于單獨信件的處理已經談了很多了,讓我們來看看對一組郵件或者存放郵件的文件夾該如何處理。我們提到過 Mail::Box ,它絕對是處理郵件文件夾的老大,它支持本地和遠程的文件夾處理,可以編輯文件夾,以及作相應的排序操作等等。要使用它,我們首先需要 Mail::Box::Manager 模塊,它是用來構建 Mail::Box 對象的工廠對象。 use Mail::Box::Manager my $mgr = Mail::Box::Manager-new;

  接下來,我們通過管理器來打開文件夾: my $folder = $mgr-open(folder = $folder_file);

  而現在,我們可以獲取各個獨立的郵件表達對象(Mail::Message): for ($folder-messages) { print $_-subject,"\n"; }

  與此最爲相近的,我喜歡用的郵箱管理器還是 Mail::Util 的 read_mbox 函數。把 Unix 中 mbox 文件路徑傳遞給它,然後返回一系列的匿名數組,每個匿名數組都表示一個郵件消息,其元素爲該消息的每一行。如此一來,它非常適合 Mail::Internet-new 或者相近的: for (read_mbox($folder_file)) { my $obj = Mail::Internet-new($_); print $_-head-get("Subject"),"\n"; }

  這兩種做法都非常容易,不過似乎在 Mail::Util 的簡潔性和 Mail::Box 的功能上還有些簡化的余地,于是電子郵件項目再次停滯下來,這次的焦點集中在 Email::Folder 和 Email::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 來更容易的改變對郵件消息的表達方式。 處理文件夾的另一方面就是如何寫數據了。或者說如何本地投遞。Email::LocalDelivery 模塊的出現是爲了輔助 Email::Filter 。問題比聽起來要更難些,因爲它必須處理鎖定,跳開郵件體,以及由 mailbox 和 maildir 等不同格式而引發的問題。而 LocalDelivery 則通過簡單的界面把所有這些都隱藏起來: Email::LocalDelivery-deliver($rfc2822, @mailboxes);

  Email::LocalDelivery 和 Email::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 加入我們並創造了 Email::Address 模塊。它和 Mail::Address 使用一致的交互界面,並且運行地更加快速,差不多兩到三倍。(譯注:上面的例子中,Email::Address 返回 "eBay, Inc." 。看來在作者眼裏,Mail::Address 的作者畫蛇添足了。) 還有一件我們經常需要做的事情就是校驗郵件地址是否合法。比如,某個用戶在站點上注冊,我們就需要對他所提供的郵件地址是否能夠接收郵件作檢查。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" }

  郵件數據轉換我們有了自己的信件,接下來會對它們做些什麽呢?我發現大多是對郵件進行文本化分析,這裏有三個模塊可以協助我們: 首先是 Text::Quoted ,它獲取郵件體的文本,實際上可以是任何其他文本,然後嘗試找出某些引用其他郵件的文本部分,然後將之分離並保存到嵌套的數據結構中。例如,如果我們有 $message = foo # Bar baz quux EOF

  然後運行 extract($message) 就會返回如下的數據結構: [ [ { text = 'foo', quoter = '', raw = ' foo' }, [ { text = 'Bar', quoter = ' #', raw = ' # Bar' } ], { text = 'baz', quoter = '', raw = ' baz' } ], { empty = 1 }, { text = 'quux', quoter = '', raw = 'quux' } ];

  當你顯示郵件消息的內容時,准備用不同的顔色來區分不同的引用文本,那麽這個模塊就幫到你大忙了。類似概念的還有 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 模塊實現了 Jamie Zawinski 的郵件線索化算法,該算法先是被 Mozilla 所用,繼而許多其他郵件客戶端也開始使用這種技術。當然 Mariachi 也使用了這項技術,最近它還作了更新,使用 Email::Abstract 來處理各種你扔過去的郵件表達對象: my $threader = Mail::Thread-new(@mails); $threader-thread; # 計算線索 for ($threader-rootset) { # 在一個線索內的原始郵件 dump_thread($_); }

  郵件過濾經典的 Perl 的郵件過濾工具莫不就是 Mail::Audit 了,我還在這裏寫過關于如何使用 Mail::Audit 模塊的文章(http://www.perl.com/pub/a/2001/07/17/mailfiltering.html),以及如何與 Mail::SpamAssassin (http://www.perl.com/pub/a/2002/03/06/spam.html)模塊相結合使用。 我們已經提到過 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 本身還有很長一段路要走,所以如果你新架設的系統的話,我們鼓勵您使用電子郵件項目的 Email::Filter 模塊作爲替代,它們的大部分操作界面是一致的,盡管功能並不完全相同。爲了追求簡潔和速度,它使用了新式的 Email::Simple 作爲郵件表達對象模塊。 郵件信息挖掘最後,我所做的比較高級的事情就是開發一個自動分類,組織,並索引郵件到數據庫的應用框架,並嘗試從中分析並提取有價值的信息。 我的第一個完成這個預期目標的模塊是 Mail::Miner ,它由三個主要部分組成。第一個部分獲取一封郵件後,去除各種附件,並分別存儲到數據庫。第二部分縱覽這封郵件並運行一系列的識別(Recogniser)模塊,如此搜尋郵件地址,電話號碼,一些關鍵字和短語等等,並把它們存儲到另一個獨立的數據庫表中。第三部分爲命令行工具,用來查詢數據庫中的郵件以及相關的信息。 舉個例子,如果我需要找 Tim O'Reilly 的郵政地址,我就會使用查詢工具 mm ,從他發來的信中找出該地址: % 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 寫到了磁盤上。 現在 Mail::Miner 已經非常不錯了,不過它把三種思想緊緊地捆綁在一個包中 -- 郵件的歸檔,郵件的數據挖掘以及查詢數據庫的命令行界面 -- 這使得很難單獨開發或者擴展每塊的功能。當然,它使用了老式的 Mail:: 名字空間。 這引領我們走到這次郵件模塊旅程的最後一站,最新發布的:Email::Store 模塊。這是個基于 Class::DBI 的應用框架,用來存儲郵件到數據庫並以各種方式索引: 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 的數據挖掘。它仍然在初步的開發階段,並在增強模塊的擴展性方面使用了一些新的思想。 在我們使用 Email::Store 寫出第一個郵件歸檔和搜索工具的時候,我會再次給大家作詳細介紹的。這也是爲了 perl.org 的新的 Perl 郵件列表處理接口而准備做的工作。 小結我們已經看過了 CPAN 上的幾個主要的郵件處理模塊,當然還有更多。很明顯的,我著實偏袒那些自己寫的模塊。特定的 Perl 電子郵件項目的模塊則使用 Email::* 的名字空間。我們特別設計了這些簡潔、高效的模塊,而它們並不總是老式的 Mail::* 模塊的優良替換方案,特別像 Mail::Box 之類。到此,我希望各位通過對本文的閱讀,了解和認識更多的郵件處理工具模塊,並在之後使用 Perl 來處理郵件時,胸中有丘壑。
 
 
 
上一篇《Mailing List (郵件列表)原理簡述及我的perl實現》
下一篇《用Perl來分析並生成中文Excel文件》
 
 
 
日版寵物情人插曲《Winding Road》歌詞

日版寵物情人2017的插曲,很帶節奏感,日語的,女生唱的。 最後聽見是在第8集的時候女主手割傷了,然後男主用嘴幫她吸了一下,插曲就出來了。 歌手:Def...

兄弟共妻,我成了他們夜裏的美食

老鍾家的兩個兒子很特別,就是跟其他的人不太一樣,魔一般的執著。兄弟倆都到了要結婚的年齡了,不管自家老爹怎麽磨破嘴皮子,兄弟倆說不娶就不娶,老父母爲兄弟兩操碎了心...

如何磨出破洞牛仔褲?牛仔褲怎麽剪破洞?

把牛仔褲磨出有線的破洞 1、具體工具就是磨腳石,下面墊一個硬物,然後用磨腳石一直磨一直磨,到把那塊磨薄了,用手撕開就好了。出來的洞啊很自然的。需要貓須的話調幾...

我就是掃描下圖得到了敬業福和愛國福

先來看下敬業福和愛國福 今年春節,支付寶再次推出了“五福紅包”活動,表示要“把欠大家的敬業福都還給大家”。 今天該活動正式啓動,和去年一樣,需要收集“五福”...

冰箱異味産生的原因和臭味去除的方法

有時候我們打開冰箱就會聞到一股異味,冰箱裏的這種異味是因爲一些物質發出的氣味的混合體,聞起來讓人惡心。 産生這些異味的主要原因有以下幾點。 1、很多人有這種習...

《極品家丁》1-31集大結局分集劇情介紹

簡介 《極品家丁》講述了現代白領林晚榮無意回到古代金陵,並追隨蕭二小姐化名“林三”進入蕭府,不料卻陰差陽錯上演了一出低級家丁拼搏上位的“林三升職記”。...

李溪芮《極品家丁》片尾曲《你就是我最愛的寶寶》歌詞

你就是我最愛的寶寶 - 李溪芮 (電視劇《極品家丁》片尾曲) 作詞:常馨內 作曲:常馨內 你的眉 又鬼馬的挑 你的嘴 又壞壞的笑 上一秒吵鬧 下...

烏梅的功效與作用以及烏梅的食用禁忌有哪些?

烏梅,又稱春梅,中醫認爲,烏梅味酸,性溫,無毒,具有安心、除熱、下氣、祛痰、止渴調中、殺蟲的功效,治肢體痛、肺痨病。烏梅泡水喝能治傷寒煩熱、止吐瀉,與幹姜一起制...

什麽是脂肪粒?如何消除臉部脂肪粒?

什麽是脂肪粒 在我們的臉上總會長一個個像脂肪的小顆粒,弄也弄不掉,而且顔色還是白白的。它既不是粉刺也不是其他的任何痘痘,它就是脂肪粒。 脂肪粒雖然也是由油脂...

網絡安全治理:國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰受害者

來源:中國青年報 新的攻擊方法不斷湧現,黑客幾乎永遠占據網絡攻擊的上風,我們不可能通過技術手段杜絕網絡攻擊。國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰...

 
 
 
譯者/作者:chunzi 出處:中國Perl協會 FPC(Foundation of Perlchina) 原名:The Evolution of Perl Email Handling 作者:Simon Cozens 原文:http://www.perl.com/pub/a/2004/06/10/email.html 發表:June 10, 2004 請保護作者的著作權,維護作者勞動的結晶。 每天我都要花費大量的時間在電子郵件相關的工作上,或者通過郵件來和其他工作夥伴聯系,或者饒有興致地分析,索引,重新組織以及挖掘郵件內容。很自然的,Perl 協助我做這些事情。 在 CPAN 上有很多現成的模塊可以用來處理電子郵件,我們將介紹其中幾個主要的。同時我們也將關注由我和 Richard Clamp,Simon Wistow 以及其他夥伴所致力的 Perl 電子郵件項目(Perl Email Project),該項目的目標是提供一系列簡單的,有效的,精准的郵件處理模塊。 郵件消息的處理我們從一些比較簡單的,用來描繪一封單獨郵件,提供對郵件頭和郵件體的訪問,甚至修改它們的信息的那些模塊開始介紹。 所有的這些模塊的曾祖父都是 Mail::Internet ,由 Graham Barr 創建,目前 Mark Overmeer 在維護。該模塊提供了通過數組(元素爲字符串行)或者文件句柄來讀取信件內容的構造器,並通過它返回一個描述該信件的 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."); 到現在爲止我還沒有提到過任何關于 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::Internet 和 MIME::Entity 都不適合你,你可以試試 Mark Overmeer 自己的 Mail::Message 模塊,該模塊是令人印象深刻的 Mail::Box 模塊中的一部分。Mail::Message 是個極富特色的、功能全面的模塊,但這些優點並不總意味著褒揚。 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 對象來覆蓋現有的。 $obj-body(Mail::Message::Body-new(data = [split /\n/, $body])); Mail::Message 處理郵件的時候可能有點慢,也著實難用。它的體系也非常複雜,上面我們所看到的這些操作就已經用到了 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 多行的代碼。盡管它確實擁有很多功能,我還是傻傻的覺得郵件的分析處理應該更爲簡潔。所以我坐下來決定自己著手編寫盡可能簡潔的郵件處理函數庫,結果就有了 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 方法。 實際上,選擇哪一種郵件處理函數庫完全取決于你,最終用戶,不過並不總是這樣的。有許多輔助性的模塊,幫助你在更高的應用層上處理郵件信息的,可能要求你提供特定的郵件表達對象。比如最近的 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)」的方式來撰寫郵件處理函數庫。我很感謝 Michael Stevens 立即在 Mail::ListDetector 中使用了 Email::Abstract 。現在我可以將 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::MIME 和 Email::Simple 的運行速度差不多是對手的兩倍。然而,我們要強調一點,這裏所做的測試都是非常低級的,如果你要做任何比這裏看到的更加複雜的操作,你該考慮哪些老的 Mail:: 模塊。 郵箱的處理對于單獨信件的處理已經談了很多了,讓我們來看看對一組郵件或者存放郵件的文件夾該如何處理。我們提到過 Mail::Box ,它絕對是處理郵件文件夾的老大,它支持本地和遠程的文件夾處理,可以編輯文件夾,以及作相應的排序操作等等。要使用它,我們首先需要 Mail::Box::Manager 模塊,它是用來構建 Mail::Box 對象的工廠對象。 use Mail::Box::Manager my $mgr = Mail::Box::Manager-new; 接下來,我們通過管理器來打開文件夾: my $folder = $mgr-open(folder = $folder_file); 而現在,我們可以獲取各個獨立的郵件表達對象(Mail::Message): for ($folder-messages) { print $_-subject,"\n"; } 與此最爲相近的,我喜歡用的郵箱管理器還是 Mail::Util 的 read_mbox 函數。把 Unix 中 mbox 文件路徑傳遞給它,然後返回一系列的匿名數組,每個匿名數組都表示一個郵件消息,其元素爲該消息的每一行。如此一來,它非常適合 Mail::Internet-new 或者相近的: for (read_mbox($folder_file)) { my $obj = Mail::Internet-new($_); print $_-head-get("Subject"),"\n"; } 這兩種做法都非常容易,不過似乎在 Mail::Util 的簡潔性和 Mail::Box 的功能上還有些簡化的余地,于是電子郵件項目再次停滯下來,這次的焦點集中在 Email::Folder 和 Email::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 來更容易的改變對郵件消息的表達方式。 處理文件夾的另一方面就是如何寫數據了。或者說如何本地投遞。Email::LocalDelivery 模塊的出現是爲了輔助 Email::Filter 。問題比聽起來要更難些,因爲它必須處理鎖定,跳開郵件體,以及由 mailbox 和 maildir 等不同格式而引發的問題。而 LocalDelivery 則通過簡單的界面把所有這些都隱藏起來: Email::LocalDelivery-deliver($rfc2822, @mailboxes); Email::LocalDelivery 和 Email::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 加入我們並創造了 Email::Address 模塊。它和 Mail::Address 使用一致的交互界面,並且運行地更加快速,差不多兩到三倍。(譯注:上面的例子中,Email::Address 返回 "eBay, Inc." 。看來在作者眼裏,Mail::Address 的作者畫蛇添足了。) 還有一件我們經常需要做的事情就是校驗郵件地址是否合法。比如,某個用戶在站點上注冊,我們就需要對他所提供的郵件地址是否能夠接收郵件作檢查。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" } 郵件數據轉換我們有了自己的信件,接下來會對它們做些什麽呢?我發現大多是對郵件進行文本化分析,這裏有三個模塊可以協助我們: 首先是 Text::Quoted ,它獲取郵件體的文本,實際上可以是任何其他文本,然後嘗試找出某些引用其他郵件的文本部分,然後將之分離並保存到嵌套的數據結構中。例如,如果我們有 $message = foo # Bar baz quux EOF 然後運行 extract($message) 就會返回如下的數據結構: [ [ { text = 'foo', quoter = '', raw = ' foo' }, [ { text = 'Bar', quoter = ' #', raw = ' # Bar' } ], { text = 'baz', quoter = '', raw = ' baz' } ], { empty = 1 }, { text = 'quux', quoter = '', raw = 'quux' } ]; 當你顯示郵件消息的內容時,准備用不同的顔色來區分不同的引用文本,那麽這個模塊就幫到你大忙了。類似概念的還有 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 模塊實現了 Jamie Zawinski 的郵件線索化算法,該算法先是被 Mozilla 所用,繼而許多其他郵件客戶端也開始使用這種技術。當然 Mariachi 也使用了這項技術,最近它還作了更新,使用 Email::Abstract 來處理各種你扔過去的郵件表達對象: my $threader = Mail::Thread-new(@mails); $threader-thread; # 計算線索 for ($threader-rootset) { # 在一個線索內的原始郵件 dump_thread($_); } 郵件過濾經典的 Perl 的郵件過濾工具莫不就是 Mail::Audit 了,我還在這裏寫過關于如何使用 Mail::Audit 模塊的文章(http://www.perl.com/pub/a/2001/07/17/mailfiltering.html),以及如何與 Mail::SpamAssassin (http://www.perl.com/pub/a/2002/03/06/spam.html)模塊相結合使用。 我們已經提到過 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 本身還有很長一段路要走,所以如果你新架設的系統的話,我們鼓勵您使用電子郵件項目的 Email::Filter 模塊作爲替代,它們的大部分操作界面是一致的,盡管功能並不完全相同。爲了追求簡潔和速度,它使用了新式的 Email::Simple 作爲郵件表達對象模塊。 郵件信息挖掘最後,我所做的比較高級的事情就是開發一個自動分類,組織,並索引郵件到數據庫的應用框架,並嘗試從中分析並提取有價值的信息。 我的第一個完成這個預期目標的模塊是 Mail::Miner ,它由三個主要部分組成。第一個部分獲取一封郵件後,去除各種附件,並分別存儲到數據庫。第二部分縱覽這封郵件並運行一系列的識別(Recogniser)模塊,如此搜尋郵件地址,電話號碼,一些關鍵字和短語等等,並把它們存儲到另一個獨立的數據庫表中。第三部分爲命令行工具,用來查詢數據庫中的郵件以及相關的信息。 舉個例子,如果我需要找 Tim O'Reilly 的郵政地址,我就會使用查詢工具 mm ,從他發來的信中找出該地址: % 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 寫到了磁盤上。 現在 Mail::Miner 已經非常不錯了,不過它把三種思想緊緊地捆綁在一個包中 -- 郵件的歸檔,郵件的數據挖掘以及查詢數據庫的命令行界面 -- 這使得很難單獨開發或者擴展每塊的功能。當然,它使用了老式的 Mail:: 名字空間。 這引領我們走到這次郵件模塊旅程的最後一站,最新發布的:Email::Store 模塊。這是個基于 Class::DBI 的應用框架,用來存儲郵件到數據庫並以各種方式索引: 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 的數據挖掘。它仍然在初步的開發階段,並在增強模塊的擴展性方面使用了一些新的思想。 在我們使用 Email::Store 寫出第一個郵件歸檔和搜索工具的時候,我會再次給大家作詳細介紹的。這也是爲了 perl.org 的新的 Perl 郵件列表處理接口而准備做的工作。 小結我們已經看過了 CPAN 上的幾個主要的郵件處理模塊,當然還有更多。很明顯的,我著實偏袒那些自己寫的模塊。特定的 Perl 電子郵件項目的模塊則使用 Email::* 的名字空間。我們特別設計了這些簡潔、高效的模塊,而它們並不總是老式的 Mail::* 模塊的優良替換方案,特別像 Mail::Box 之類。到此,我希望各位通過對本文的閱讀,了解和認識更多的郵件處理工具模塊,並在之後使用 Perl 來處理郵件時,胸中有丘壑。
󰈣󰈤
 
 
 
  免責聲明:本文僅代表作者個人觀點,與王朝網路無關。王朝網路登載此文出於傳遞更多信息之目的,並不意味著贊同其觀點或證實其描述,其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。
 
 
夏末午後的美麗女生
天生麗質_唯美動人
清新素雅的靓麗女生
完美絕倫_秀色可餐
芙蓉古鎮(一)
就是不一樣的街燈&#;
百態
荷一組(三張)
 
>>返回首頁<<
 
 熱帖排行
 
 
 
 
© 2005- 王朝網路 版權所有