### 8.防止垃圾郵件的一種方法
我認為過濾垃圾郵件是可以做到的,基于內容的過濾器將發揮作用。發送垃圾郵件的人有一個致命傷,那就是他們發送的郵件本身。他們有辦法逃脫你搭建的其他壁壘(至少目前是這樣),但是不管怎樣,他們都必須把垃圾郵件發出去。如果我們能夠寫出可以從內容上識別出垃圾郵件的軟件,那么他們就無法逃脫了^。
^「出版時,本文的一些內容經過改寫,但是從Lisp代碼翻譯過來的、計算垃圾郵件概率的數學公式沒有變。所以,公式里有些地方可能過時了,比如現在很少有垃圾郵件還含有click這個詞。但是,算法仍然是有效的。一個略加修改的版本可以過濾99.6%的垃圾郵件,更多信息參見paulgraham.com。」
收信人很容易識別哪些是垃圾郵件,哪些是正常郵件。如果你雇人用肉眼幫你清除垃圾郵件,這事情應該沒有太大難度。那么我們怎么用軟件自動模擬這個過程(假定不使用復雜的人工智能)?
我覺得只用一些很簡單的算法就可以做到這一點。事實上,我發現只要對單個詞語進行貝葉斯判斷,就能很好地過濾大部分垃圾郵件。設置好貝葉斯過濾器(詳見后文),1000封垃圾郵件能夠被過濾掉995封,并且沒有一個誤判。
開發垃圾郵件過濾器時,統計學方法往往不是程序員首先想到的方法。大多數黑客的直覺是寫出一個能夠識別垃圾郵件某種特征的軟件。你看著那些垃圾郵件,心想這些可惡至極的家伙膽敢向我發送以“親愛的朋友”開頭的郵件,或者主題行都是大寫字母且以八個驚嘆號作為結尾的郵件,我用一行代碼就能把它們全過濾掉。
你這樣做了以后,一開始效果還不錯。幾條簡單的規則就能攔截大部分垃圾郵件。僅僅搜索單詞Click就會捕捉到79.7%的垃圾郵件(以我的情況為例),其中只有1.2%是誤判。
在轉向統計學方法之前,大約整整有六個月,我一直使用這種特征過濾法,自己編寫軟件,識別垃圾郵件的特鉦。我發現,到后來要想把識別精度提高幾個百分點非常困難,如果我把過濾條件設置得很嚴格,誤判率就會上升。
所謂誤判,指的是正常的郵件被錯誤認定為垃圾郵件。對于大多數用戶來說,錯過一封正常的郵件后果要比收到垃圾郵件嚴重得多。所以,如果過濾器有誤判,就好像治療粉刺的藥物卻有致人死亡的危險一樣。
用戶收到的垃圾郵件越多,他就越不可能注意到被過濾掉的垃圾郵件中包含著一封正常郵件。這就導致了一個很奇怪的后果,如果你的過濾器效果越好,就越不能出現誤判,一旦誤判,后果就會變得很嚴重,因為過濾器工作得非常良好,所以用戶相信它,就不太可能去檢查被它過濾掉的郵件。
我不知道為什么我沒有早一點嘗試統計學方法。原因可能是我太過迷戀于發現垃圾郵件的特征,有一種與發送者斗智斗勇的感覺。(大多數黑客都是好勝心很強的人,一般人往往意識不到這點。)當我嘗試統計學方法以后,我立刻發現這是更聰明的選擇。它不僅能發現普通的垃圾郵件標志(比如,木馬和廣告性詞語),還能發現像 `per`、`FL`、`ff0000` 這種不太明顯的標志。事實上,`ff0000` (HTML語言中表示鮮紅色的代碼)被證明效果顯著,能很有效地識別垃圾郵件,就像色情詞匯一樣容易辨別。
下面我就簡單介紹一下我是如何開發統計學過濾器的。開始前,我先準備好一組垃圾郵件和一組非垃圾郵件,每組各有4000個樣本。我對每一封郵件的全部內容進行了掃描,包括郵件頭、內嵌的HTML代碼和JavaScript代碼。我把字母、阿拉伯數字、破折號、撇號、美元符號作為“實義標識”(token),所有其他字符則是“實義標識”的分隔符。(這個處理可能還可以進一步改善。)我忽略了完全由數字組成的字符串以及HTML注釋,也不把它們當作“實義標識”的分隔符看待。
我計算了每個實義標識在兩個郵件組出現的次數(忽略大小寫)。完成這步以后,我就得到了兩大張散列表,一個郵件組一張,表中每一欄就是一個鍵值對,“鍵”欄對應每一個實義標識,“值”欄則是這個標識出現的次數。
接著,我創建了第三張散列表,“鍵”欄還是每一個實義標識,“值”欄則是包含該標識的郵件是垃圾郵件的概率。我把這個概率記作*Pspam|w*,計算公式如下:

公式中的w就是我用來計算概率的那個實義標識,good和bad表示我在第一步創建的兩張散列表,G和B分別表示正常郵件和垃圾郵件的數量。
為了避免誤判,我稍微加大了某個實義標識不是垃圾郵件的概率。經過反復試錯,我發現將good表的次數值全部增大一倍可以很好地達到這個目的。這有助于區分那些偶爾出現在正常郵件中的詞以及那些幾乎從不出現的詞。我只把出現總次數超過5次的詞列入計算范圍(實際上,由于正常郵件會反復使用同樣的詞,所以出現總次數超過3次應該就夠了)。下一個問題就是,如果一個詞只出現在一組郵件中,它的概率應該怎么分配。我又通過試錯法選擇了0.01和0.99。這里可能還有改善的余地,但是隨著郵件數量的增加,計算結果應該會自動調整的。
那些善于觀察的人會注意到為了計算每個詞出現的次數,我把每一組郵件看成一整串文本流,但卻還是使用電子郵件的數量而不是文本流的總長度作為計算概率時的分母。這樣做也是為了加大不是垃圾郵件的概率,防止出現誤判。
當收到新郵件的時候,程序會自動掃描,讀出郵件中所有的實義標識,再找出其中15個最醒目標識(所謂“最醒目標識”,就是指概率偏離中性值0.5最遠的標識),用它們判斷整封郵件是垃圾郵件的概率。如果用*w1, …, w15*分別表示15個最醒目標識,那么計算整封郵件概率的公式如下:

實踐中遇到的問題是,如果出現一個以前從來沒見過的詞(即兩張散列表里都找不到這個詞),它的概率應該怎么計算。我發現(還是通過試錯法)將概率設為0.4效果很好。如果你從來沒見過這個詞,它多半是一個正常的詞,垃圾郵件用的詞都是很常見的。
如果上面的公式計算出來的概率大于0.9,我就把這封郵件當作垃圾郵件。但是在實踐中,把這個門檻值設為多少并不是很重要,因為計算出來的概率值大多數都分布在兩端,很少落在中間。
統計學方法的一大優點就是,你不需要一封封去看垃圾郵件。在使用它之前的六個月,我大概看了足足幾千封垃圾郵件,這真是很苦惱的一件事。數學家Norbert Wiener說,如果你與奴隸比賽,你也會變成一個奴隸。與垃圾郵件搏斗就有這種令人退化的效果。為了識別垃圾郵件的每一個特征,你不得不鉆進發送者的腦袋,搞清楚他們怎么想。說實話,我一刻都不想待在那里。
但是,貝葉斯方法的真正優點在于你知道你正在計算的是什么東西。識別垃圾郵件特征的過濾器(比如SpamAssassin)為每封郵件計算一個“得分”,而貝葉斯方法為每封郵件算出一個概率。“得分”方法的缺點在于沒人知道這個分數到底是什么意思,用戶不知道,更糟的是,就連過濾器的開發者也不知道。如果郵件中有sex(性)這個詞,請問得分是多少?計算概率當然也會出錯,但是至少意義上很清楚,一點也不模糊,而且用來計算它的那些依據也很清楚。根據我的郵件庫,一封郵件中含有sex這個詞,那么它有0.97的概率是一封垃圾郵件;要是含有sexy這個詞,垃圾郵件的概率更是上升到0.99。貝葉斯規則同樣毫不含糊地表明,如果一封郵件同時含有這兩個詞,即使沒有其他證據(事實上,這是不可能的),垃圾郵件的概率也將達到99.97%。
因為貝葉斯方法計算的是概率,所以它必須考慮郵件中所有的線索,不管是肯定性線索還是否定性線索。有些詞(比如though、tonight、apparently)極少出現在垃圾郵件中,所以它們會大大降低這封郵件屬于垃圾郵件的概率;同樣,還有一些詞(比如unsubscribe、opt-in)幾乎是垃圾郵件專用,它們會大大增加概率。因此,如果一封郵件的其他方面都合格,只是碰巧包含了sex這個詞,這封郵件是不會被歸入垃圾郵件的。
理想情況下,每個收信人應該都有自己單獨的概率分布表。以我為例,我收到的許多郵件中都含有Lisp這個詞,而迄今還沒有垃圾郵件包含這個詞。所以,一個這樣的詞實際上就像許可證一樣,保證了這封信是發送給我的正常郵件。在我以前寫的垃圾郵件過濾器中,用戶可以自己開出一張清單,列出一系列這樣的詞。然后,收到的郵件之中如果包含這些詞,就將自動通過過濾器。我自已的清單上除了Lisp這個詞,還有我的郵政編碼,所以網上購物的確認郵件就能安然通過過濾器(否則它們看上去很像垃圾郵件)。我當時覺得自己真是聰明絕頂,但是后來發現貝葉斯方法能夠自動做到這一點,而且它還能發現許多我以前根本沒意識到的這一類詞語。
我在文章的開頭說,我的過濾器現在可以在1000封垃圾郵件中正確識別出995封,并且沒有一個誤判。做到這一點的前提是必須有一個很大的郵件庫作為判斷依據。但是,我不想用這些數字誤導讀者,如果你想同樣做到這個水平,最好采用我提倡的方法,就是把自己收到的所有郵件分成垃圾郵件和非垃圾郵件兩大類。按照我的想法,每個用戶應該有兩個“刪除”按鈕,一個是“正常刪除”,還有一個是“垃圾郵件刪除”。任何被后一個按鈕刪除的郵件都進入垃圾郵件庫,而其他的所有郵件進入非垃圾郵件庫。
剛開始的時候可以有一個所有人共享的基本概率分布表,但是到了最后,每個用戶應該都分別有自己的概率分布表,這是根據他收到的郵件對每一個詞進行統計后得出的。這樣做可以:(a)使得過濾器更有效;(b)讓每個用戶自己定義,什么是他眼中的垃圾郵件;(c)使得垃圾郵件的發送者無法針對過濾器做出調整(這可能是最大的好處)。如果每個用戶的過濾器大部分都是基于獨立的數據庫,那么每個過濾器的過濾條件都不一樣,而且會更加富有成效。要是垃圾郵件的發送者僅僅針對基本概率分布表做出調整,并不能保證這封郵件會通過攔截。
統計學過濾器除了基于內容做出判斷以外,還可以有一張白名單,上面列出值得信任的、不會發送垃圾郵件的發信人,讓他們的郵件直接通過過濾器。建立這樣一張白名單有一個容易的方法,就是將所有你曾經去信的地址都保留下來。另外,凡是你使用“正常刪除”按鈕刪除的郵件(前提是郵箱軟件必須同時具備“Spam刪除”按鈕),它們的地址也可以加入白名單。
我提倡使用白名單,主要是為了節約計算,而不是認為這樣可以改進過濾器的效果。我曾經認為白名單會讓過濾器運作得更順利,因為你從此只需要掃描那些陌生人的郵件就行了。試想一下,如果某人是第一次發郵件給你,他一般囿于常規,只會說一些需要對你說的內容,不會一上來就跟你討論sex。相反,倒是你已經認識的熟人可能會這樣做。所以,白名單有助于避免這些郵件的誤判。但是問題是,人們一般都有好幾個Email地址,一封從陌生地址發來的郵件并不必然意味著來自一個你不認識的陌生人。一個老朋友突然用一個全新的地址寫信給你可不是罕見情況,對于黑客尤其如此。所以,白名單并不會降低誤判的風險。
不過,某種意義上,統計學過濾器其實內嵌了白名單(還有黑名單)。因為整封郵件都會被掃描,包括郵件頭在內,所以經過這一步,過濾器自己“知道”哪些郵箱地址可以信賴(甚至還知道哪些中轉的服務器可以信賴)。對于垃圾郵件,它也會“知道”得一清二楚,包括服務器名稱、發送郵件的軟件版本和郵件協議。
如果現在的過濾水平(1000封垃圾郵件識別出995封)可以保持下去,我會覺得問題已經解決了。但是,垃圾郵件永遠在進化,現在能夠過濾它們不等于永遠能夠過濾它們。說實話,如今的大多數垃圾郵件過濾器就像殺蟲劑一樣,唯一作用就是創造出殺不死的新品種害蟲。
我對貝葉斯方法寄予厚望,因為它的過濾能力可以隨著垃圾郵件一起進化。所以,假定垃圾郵件發送者開始用vlagra替代viagra^,以此逃避某些機械的、基于單個詞匯的過濾器的欄截,貝葉斯過濾器卻能夠自動注意到這種變化。實際上,vlagra是比viagra確定性高得多的線索,可以證實這封郵件為垃圾郵件,至于概率到底高出多少,貝葉斯過濾器將準確告訴我們。
^「中文名“萬艾可”(偉哥),一種治療陽瘺的藥物。——譯者注」
到目前為止還存在一個問題,所有垃圾郵件過濾器的開發者必須回答:如果發送人準確知道你的過濾機制,他們逃避攔截的可能性有多大?比如我猜想,如果“校驗碼”(checksum)方法^對垃圾郵件構成重大威脅,那么發送人就會耍花招,使用同義詞替代的技巧讓每一封郵件內容完全不同,從而逃避攔截。
^「“校驗碼”方法的原理是,一般來說,垃圾郵件都是大量群發的,除了個別詞語不同以外,信件的主體內容完全一樣,所以,只要去除那些不同的部分,對信件主體計算一個校驗碼,然后與數據庫中已經確認的垃圾郵件校驗碼進行比較,如果兩者相同,就可以認定是垃圾郵件了。——譯者注」
但是,要想騙過貝葉斯過濾器就沒那么容易了。你把每一封垃圾郵件都寫得獨一無二或者不使用某些特定的標志性詞匯,都不足以達到目的。只有讓垃圾郵件看上去與正常郵件毫無區別才能夠實現。我覺得要做到這一點真是夠難為他們的。垃圾郵件主要用于銷售目的,那么除非你正常往來的郵件都是銷售類郵件,否則垃圾郵件不可避免地將與其他郵件不一樣。此外,發送人還必須改變(并且不斷改變)他的郵件系統架構,否則貝葉斯過濾器會識別出他的郵件頭,而根本不用看郵件內容到底寫的是什么。我對郵件系統架構知道得不多,不太清楚讓郵件頭逃過攔截的難度有多高,但是我猜想它的難度要超過讓郵件正文逃過攔截的難度。
假定那些人連郵件頭的難題也解決了,那么未來的垃圾郵件可能就是下面這個樣子:
> 嗨,你好。請查看鏈接:
> www.27rneg.com/foo
這差不多就是統計學過濾器能夠允許通過的銷售類郵件的樣子,最多就到這樣了。(可是實際上,這段話更難逃過攔截,因為郵件的其他內容全部都是中性詞語,垃圾郵件可能不得不在URL上做文章,但是要讓一個URL看上去沒有可疑之處還是很傷腦筋的。)
發送垃圾郵件的人形形色色。有的是公司,經營著一個所謂的郵件列表,表面上說你可以選擇訂閱,但是實際上根本無法退訂,他們肆無忌憚地向你發送廣告,有的是個人,專門劫持郵件服務器,推廣色情網站。如果我們的過濾器迫使他們只能把垃圾郵件寫成上面那樣,應該會使得垃圾郵件業中合法經營的那部分人退出這個行業。因為他們很樂于遵守各州的法律規定,在郵件中附上正式聲明,解釋為什么自己不是垃圾郵件以及如何才能取消訂閱。這一類文字反而使得識別他們變得更容易了。
(我以前曾經認為,那些相信更嚴格的法律會遏制垃圾郵件的人真是太天真了。我現在認為,更嚴格的法律或許無法減少我們收到的垃圾郵件的數量,但是肯定有助于減少逃過過濾器攔截的垃圾郵件的數量。)
在垃圾郵件業中,如果發送銷售類垃圾郵件受到限制,那么整個行業將不可避免地受到重創。“行業”這個詞是很準確的,發送垃圾郵件的人其實都是商人,他們這么做只是因為這招很有效。雖然垃圾郵件的回應率低到不能再低了(不超過百萬分之15,相比之下,傳統的郵寄商品目錄的回應率是百萬分之3000),但是發送垃圾郵件的成本實際上為零,所以它還是有效的。但是對于收到垃圾郵件的人來說,成本卻很高昂,假定有100萬人分別收到一封垃圾郵件,每人花一秒鐘刪除,累計起來就相當于一個人5個星期的工作量,而發送人連一分錢也不用付出。
不過,雖然接近于零,發送垃圾郵件還是有成本的^。所以,只要我們把垃圾郵件的回應率降得很低(不管手段是直接過濾,還是讓垃圾郵件被迫掩蓋它們的銷售意圖),商家就會發現,發送垃圾郵件是一件經濟上不值得的事情。
^「2002年,發送100萬封垃圾郵件的最低成本好像是200美元。這個價格很便宜,相當于每封垃圾郵件成本為0.02美分。但是,假定過濾器可以攔截95%的垃圾郵件,那么要使得受眾數量保持不變,發送人的成本就必須增加20倍。而垃圾郵件推銷的那些生意,利潤幾乎肯定到不了這么高,無法抵消這筆成本。」
另一方面,垃圾郵件使用了那么多推銷語言就是為了增加回應率。如果有一天推銷語言突然不能用了,對他們就是重大打擊。為了說明這一點,讓我們把自己想象成一個回應垃圾郵件的人,看看這些人到底是怎么想的(這要比把自己想象成垃圾郵件發送者更讓人難受)。回應垃圾郵件的人要么是驚人地輕信,要么是表面上完全否認、但是私底下卻有著對性的強烈興趣。不管哪一種情況,也不管垃圾郵件在正常人看來是多么令人反感或愚蠢萬分,總是可以讓這些人興奮不已,因為郵件內容寫得實在太誘人了,畢竟如果不是這樣,商家也就不發送垃圾郵件了。要是郵件內容改成“請點擊下面的鏈接”,對于收信人來說就沒有太大的吸引力了,根本比不上現在的效果。結果就是,如果垃圾郵件不能使用誘人的推銷語言,它作為推銷工具的價值就會大大降低,使用它的商家數量也會減少。
最終,我們將取得全勝。我開始寫垃圾郵件過濾器只是因為不想再讓這些東西煩我了。但是,如果我們把過濾器做得足夠好,那么垃圾郵件將不再有效,商家最后將不再發送它。
在所有對抗垃圾郵件的方法之中(從軟件方法到法律方法),我認為單獨來看,“貝葉斯過濾”是最有效的工具。但是,我也認為,我們使用的不同方法越多,綜合效果就越好,因為任何對發送人構成限制的方法往往都會使得過濾器工作起來更順利。即使同樣是基于內容的過濾器,我也認為,如果有多種不同的軟件可以同時使用會比較好。過濾器的差異越大,垃圾郵件想要逃過攔截就越不可能。