1. 程式人生 > >廣告過濾演算法實現及優化

廣告過濾演算法實現及優化

一 廣告過濾綜述

網際網路已無處不在的今天,各網際網路公司通過各種方式都賺的盆滿鉢滿,其中很重要的一項收入來源就是頁面廣告,橫幅廣告,彈窗廣告以及視訊廣告等等,這些對大部分使用者來說,已經造成一定煩惱。因此,廣告過濾已成為瀏覽器的必備外掛之一,最為人熟知的廣告過濾外掛就是AdBlockhttps://adblockplus.org/),AdBlock針對FireFoxChrome等都有相應的外掛版本。但是在移動智慧裝置上,瀏覽器還不能很好地對AdBlock進行支援,其實這不是因為移動智慧裝置上瀏覽器的原因,而是市場和各瀏覽器廠商導致的,目前移動智慧終端系統被AndroidIOS兩家獨佔,IOS

上的safari以及IOS對開發者來說都是閉源,開發者很難對safari進行開發,好在Android平臺上的WebKit/Blink是完全開源的,很多開發者可以進行開發後釋出自己的瀏覽器,就國內而言,QQUC,百度瀏覽器等就都是這樣的,他們也都各自實現了廣告過濾,下面將對廣告過濾核心演算法的實現以優化作一些介紹。

廣告過濾最早的實現者是AdBlock,廣告過濾的實現原理其實非常簡單:

#1 針對各類廣告的實現方法,比如大幅廣告圖片,彈窗廣告,視訊廣告,它們在頁面中都是存在資源下載連結的,在瀏覽器進行這些資源下載時,發現它們的URL存在一個黑名單中,那就告訴瀏覽器別去下載這些廣告,從而達到廣告過濾的目的;

#2 對於頁面內鑲嵌的文字廣告,就不能通過以上方法過濾了,這時只要在頁面顯示時,通過設定文字廣告的CSS屬性,比如 display:none;就可以把這些廣告隱藏起來,給使用者的感覺就是這些廣告也被過濾了。就如AdBlock官網所展示的那樣,如圖1所示。

 

圖1 廣告過濾

二 廣告過濾演算法實現

  通過上面介紹可以瞭解到,廣告其本上可以分成兩大類,一是有明確的資源下載連結的廣告,AdBlock稱之為blocking的;二是內嵌在頁面內容中的文字類廣告,AdBlock稱之為Elem hide。針對這樣的每一個廣告,AdBlock都會有與之相對應的規則(filter),只要被filter所匹配的,都會被攔截或者被隱藏。比如

#1 這樣一條blocking規則http://example.com/ads/banner*.gif就會攔截下面這個gif廣告

#2 這樣一條Elem hide規則example.com##DIV[class=”textad”]就會把domainexample.com下的頁面中廣告隱藏起來,

<div class="textad">

Cheapest tofu, only here and now!

</div>

<div id="sponsorad">

Really cheap tofu, click here!

</div>

<textad>

Only here you get the best tofu!

</textad>

因此,對於AdBlock而言,最重要的是這個持續不斷更新的filter列表,規則列表並不只有AdBlock這一份,其實還有很多,比如EasylistFanboy, EasyList china等等。

  以上簡要說了一下AdBlock的規則,那麼接下來最重要的事就是如何進行這些規則的匹配,這裡就是純演算法問題了。AdBlock也提供了一些實現機制方面的文章,請看這裡(https://adblockplus.org/blog/investigating-filter-matching-algorithms)。先扮演一下搬運工,把AdBlock的演算法簡要說一下,這裡我們大概想一下,要做的事情是啥:有一大堆filter,而每個filter都是字串,給出一個URL需要判斷是否與這堆規則中的某個filter匹配上。

  於是一個非常直觀的演算法,肯定是這樣的,如圖2所示,用腳趾頭想了一下,這個演算法需要遍歷一下這堆filter,拿URL與每一個filter進行字串比較,即使我們使用最快的字串匹配演算法,它的時間複雜度也是O(n*f),當規則表非常大時,這個演算法的效率是不能接受的。

 

圖2 演算法1(圖片截圖自AdBlock官網)

接著,AdBlock對這個演算法進行了優化,同時也給出了演算法思路,如圖3所示,優化後的演算法是這樣的,先對每一個filter中找到一個keyword,儘量保證這個keyword與其他filterkeyword不同,這樣一來keywordfilter,就存在一對多的關係,理論上似乎可以做到一一對應,但是也沒有必要的。這樣就類似於雜湊表一樣,在匹配的時候,按照相同的方法將URL,分成一些keyword,然後通過keyword去找對應的filter,最後再匹配找到的filter。這樣就不需要遍歷規則表,大大地提高了匹配時間,文章也給出了keyword的經驗長度8個字元,字元匹配使用了BM的非回溯演算法,來儘量提高演算法的匹配時間,在本人的實現中,大約有2300條規則,其中約95%都是無衝突的,最多重複keyword4次,兩次及兩次以上衝突只佔到0.0072%,可見這個演算法是非常高效的,可以認為是O(f),即URLfilter匹配所用時間。

 

圖3 演算法3(圖片截圖自AdBlock官網)

三 廣告過濾演算法的優化

   通過前面瞭解到過濾規則分為兩類,blockingelem hide,如果都使用前面演算法很顯然對elem hide是不太適用的。首先需要分別來考慮blockingelem hide這兩類規則。

   針對演算法2的優化,演算法2中在字串匹配時,使用了的BMBoyer-Moore algorithmhttps://adblockplus.org/blog/investigating-filter-matching-algorithms),但是我們發現filter並不是簡單的字串,它也可以是正則表示式,看到正則表示式,我們就想到了Ken實現的DFA。所以,在不支援正則表示式,那就搞一個NFA,再把它變成DFA吧,儘管可能會有點難度,^_^ 

   我們來看看如何優雅地處理elem hide規則。Elem hide規則是形如這樣的:example.com##DIV[class=”textad”] or example.com#@#DIV[class=”textad”],簡單分析一下,它是由##或者#@#分割,前面都是一個或多個domain,後面是CSS選擇器。對Elem hide規則要做的就是匹配到當前頁面URLdomain是被elem hide規則中的domain包含的。下圖4AdBlockElem hide規則的解析說明:(https://adblockplus.org/en/filter-cheatsheet)

 

圖4 Elem hiding(圖片截圖自AdBlock官網)

#1 優化之hash_map

通過Elem hide規則特點是domain<-->CSS selectors,是一對多的對映廣西,因此,可以使用資料結構map,將domain作為keyselectors作為value。這個在實現上是非常簡單易懂的,另外,hash_map使用了hash table的特點在儲存和查詢都做到了線性時間內完成,缺點也是非常明顯的,使用了更多的記憶體空間;還有比如有這樣一個domainsubdomain.example.com,在hash_map中其實這個domainexample.com所對應的selectors也是適用於subdomain.example.com的,同時對~example.com這樣的exclude規則處理會更加複雜。

#2 優化之Trie

在優化#1中,如何在保證hash_map的查詢效率下,進一步節省記憶體空間呢?通過AdBlockElemhide規則的說明,一條example.com的規則,對subdomain.example.com也是生效的,這樣僅對domain進行字串匹配是不夠,還需要做更多其他處理。那麼如何做優化呢,其實只要把domain倒過來看,moc.elpmaxamoc.elpmaxe.niamodbus,看到這裡,是不是豁然明朗了,這就是字首匹配啊,立即想到Trie(字典樹)就可以很輕鬆地搞定。Trie樹如圖5所示。


5 Trie

   由Trie樹結構非常適合做字首匹配,現在就可以使用Trie樹來對處理所有Elem hide規則了,可以把所有Elem hide都放入同一顆Trie樹中,匹配的時候,只需要進行Trie樹查詢,Trie樹的查詢時間只跟樹的高度相關,時間效率是非常高的O(lgn)。通過對2000Elem hide規則建立Trie樹,平均一次匹配時間是0.059ms,同時記憶體消耗比AdBlock的實現節省約60%,效果非常明顯,當然在實現過程還有非常多的細節需要考慮和特別設計。

#3 優化之Double-array Trie

其實,在優化#2已經達到一個比較理想的優化結果,但是還有沒有更進一步的優化空間呢,答案是肯定,那就是使用更高效的資料結構:Double-array TrieDouble-array Trie可以保證與Trie相同效率下,更進一步節省記憶體空間消耗,Double-array Trie如圖6所示。


6 Double-array Trie

至此,廣告過濾相關演算法及其優化就介紹到這裡,如果有更好的想法歡迎隨時交流!

參考文獻: