1. 程式人生 > >網路爬蟲:URL去重策略之布隆過濾器(BloomFilter)的使用

網路爬蟲:URL去重策略之布隆過濾器(BloomFilter)的使用

前言:

  最近被網路爬蟲中的去重策略所困擾。使用一些其他的“理想”的去重策略,不過在執行過程中總是會不太聽話。不過當我發現了BloomFilter這個東西的時候,的確,這裡是我目前找到的最靠譜的一種方法。

  如果,你說URL去重嘛,有什麼難的。那麼你可以看完下面的一些問題再說這句話。

關於BloomFilter:

  Bloom filter 是由 Howard Bloom 在 1970 年提出的二進位制向量資料結構,它具有很好的空間和時間效率,被用來檢測一個元素是不是集合中的一個成員。如果檢測結果為是,該元素不一定在集合中;但如果檢測結果為否,該元素一定不在集合中。因此Bloom filter具有100%的召回率。這樣每個檢測請求返回有“在集合內(可能錯誤)”和“不在集合內(絕對不在集合內)”兩種情況,可見 Bloom filter 是犧牲了正確率以節省空間。

以前的去重策略:

1.想到過的URL去重策略

  • 在資料庫中建立欄位的UNIQUE屬性
  • 在資料庫中建立一個唯一的索引,在插入資料之前檢查待插入的資料是否存在
  • 使用Set或HashSet儲存資料,確保唯一
  • 使用Map或是一個定長陣列記錄某一個URL是否被訪問過

2.以上去重策略存在的問題

  (1)對於在資料庫中建立欄位的UNIQUE屬性, 的確是可以避免一些重複性操作。不過在多次MySQL報錯之後,程式可能會直接崩潰,因此這種方式不可取

  (2)如果我們要在每一次插入資料之前都去檢查待插入的資料是否存在,這樣勢必會影響程式的效率

  (3)這種方式是我在第一次嘗試的時候使用的,放棄繼續使用的原因是:OOM。當然,這裡並不是程式的記憶體洩露,而程式中真的有這麼多記憶體需要被佔用(因為從待訪問佇列中解析出來的URL要遠比它本身要多得多)

  (4)在前幾篇部落格中,我就有提到使用Map物件來儲存URL的訪問資訊。不過,現在我要否定它。因為,在長時間執行之後,Map也是會佔用大量的記憶體。只不過,會比第3種方式要小一些。下面是使用Map<Integer, Integer>去重,在長時間執行中記憶體的使用情況:

  

BloomFilter的使用:

1.一般情況下BloomFilter使用記憶體的情況:

  

2.爬蟲程式中BloomFilter使用記憶體的情況(已執行4小時):


3.程式結構圖

  

4.BloomFilter的一般使用

  如果你看了上面的文章,相信你已經瞭解到布隆過濾器的空間複雜度是S(n)=O(n)。關於這一點,相信你已經從上面的記憶體使用情況中瞭解到了這一點。那麼以下會是一些相關的Java程式碼展示。而在查重過程也很有效率,時間複雜度是T(n)=O(1)。

BloomFilter.java

[java] view plaincopyprint?
  1. import java.util.BitSet;  
  2. publicclass BloomFilter {  
  3.     /* BitSet初始分配2^24個bit */
  4.     privatestaticfinalint DEFAULT_SIZE = 1 << 25;  
  5.     /* 不同雜湊函式的種子,一般應取質數 */
  6.     privatestaticfinalint[] seeds = newint[] { 571113313761 };  
  7.     private BitSet bits = new BitSet(DEFAULT_SIZE);  
  8.     /* 雜湊函式物件 */
  9.     private SimpleHash[] func = new SimpleHash[seeds.length];  
  10.     public BloomFilter() {  
  11.         for (int i = 0; i < seeds.length; i++) {  
  12.             func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);  
  13.         }  
  14.     }  
  15.     // 將字串標記到bits中
  16.     publicvoid add(String value) {  
  17.         for (SimpleHash f : func) {  
  18.             bits.set(f.hash(value), true);  
  19.         }  
  20.     }  
  21.     // 判斷字串是否已經被bits標記
  22.     publicboolean contains(String value) {  
  23.         if (value == null) {  
  24.             returnfalse;  
  25.         }  
  26.         boolean ret = true;  
  27.         for (SimpleHash f : func) {  
  28.             ret = ret && bits.get(f.hash(value));  
  29.         }  
  30.         return ret;  
  31.     }  
  32.     /* 雜湊函式類 */
  33.     publicstaticclass SimpleHash {  
  34.         privateint cap;  
  35.         privateint seed;  
  36.         public SimpleHash(int cap, int seed) {  
  37.             this.cap = cap;  
  38.             this.seed = seed;  
  39.         }  
  40.         // hash函式,採用簡單的加權和hash
  41.         publicint hash(String value) {  
  42.             int result = 0;  
  43.             int len = value.length();  
  44.             for (int i = 0; i < len; i++) {  
  45.                 result = seed * result + value.charAt(i);  
  46.             }  
  47.             return (cap - 1) & result;  
  48.         }  
  49.     }  
  50. }  
     
Test.java
[java] view plaincopyprint?
  1. publicclass Test {  
  2.     privatefinal String[] URLS = {  
  3.             "http://www.csdn.net/",  
  4.             "http://www.baidu.com/",  
  5.             "http://www.google.com.hk",  
  6.             "http://www.cnblogs.com/",  
  7.             "http://www.zhihu.com/",  
  8.             "https://www.shiyanlou.com/",  
  9.             "http://www.google.com.hk",  
  10.             "https://www.shiyanlou.com/",  
  11.             "http://www.csdn.net/"
  12.     };  
  13.     privatevoid testBloomFilter() {  
  14.         BloomFilter filter = new BloomFilter();  
  15.         for (int i = 0; i < URLS.length; i++) {  
  16.             if (filter.contains(URLS[i])) {  
  17.                 System.out.println("contain: " + URLS[i]);  
  18.                 continue;  
  19.             }  
  20.             filter.add(URLS[i]);  
  21.         }  
  22.     }  
  23.     publicstaticvoid main(String[] args) {  
  24.         Test t = new Test();  
  25.         t.testBloomFilter();  
  26.     }  
  27. }  

5.BloomFilter在爬蟲中過濾重複的URL

[java] view plaincopyprint?