網絡爬蟲:基於對象持久化實現爬蟲現場快速還原

前言:

因為中間有一些其他的任務工作,所以有一些時日沒有再關心爬蟲的程序了。今天想到了另一個優化爬蟲的思路。

在上篇中,我們說到可以使用布隆過濾器可以很好地實現URL的去重操作。可是,如果在某一個時刻我們不小心中止了爬蟲的繼續運行。這個時候要怎麼辦呢?

本篇博客的重點正是解決這個問題。

本文鏈接:http://blog.csdn.net/lemon_tree12138/article/details/50069047 -- Coding-Naga

問題描述:

上面也有提到,現在假設我們的程序需要在中途暫停一下。這樣,會直接導致一個問題,我們的程序無法保留之前使用BloomFilter保存的URL信息。如果你對BloomFilter還不瞭解,歡迎移步到我的上一篇博客《網絡爬蟲:URL去重策略之布隆過濾器(BloomFilter)的使用》瞭解一下。

對於爬蟲程序中使用兩個隊列“對象”是好處理的,因為這部分數據是直接存放在數據庫中(磁盤裡)的。這個不用擔心。可是如果這個BloomFilter如果沒有得到一個很好的處理就是一個比較麻煩的事情了,這使得我們在後期程序執行的過程中無法很準確地判斷一個URL是否有訪問過,這樣程序的效率勢必會受到不了的影響。這裡我提供了兩種解決方案。當然一種是好的解決方法,一種是不那麼好的解決方法。

前一種處理方案:

這裡我想到的是如何通過現在有的數據(數據庫中的數據)信息,儘可能完整地構建原來的BloomFilter。我的做法是在程序重新啟動的時候去讀數據庫,把數據庫中的信息一個一個地往過濾器中填。試想一下,如果這個時候數據庫中有千萬級的數據,我們也要一個一個地往裡填,這樣勢必有點太耗時了。

因為這裡我們是需要先從數據庫中去獲得數據,再將數據添加到過濾器中。這兩步都是耗時的操作,所以,如果能不用這種方法就不用這種方法,這是下下策。

對象持久化方案:

1.格式化數據保存到文件

從之前的博客中,我們可以知道BloomFilter的核心是一個很長的數組,這個數組是保存在BitSet中。那麼這裡我們就可以把這麼多位的每一位保存到文件或是數據庫中。這樣在程序啟動的時候就可以直接讀入了。關於這個想法,我猜是可行的。之所以說是“猜”,因為我也沒有使用過這樣方法。感覺是Ok的,不過沒實踐過,如果讀者感興趣可以試試看。這裡就不多說了,說這個思路的目的,主要還是為了引出下面的這種方法。

2.基於Serializable的實現

思路分析:

說過了下下策和保存到文件這兩種,是不是這裡可以說一下上上策了?因為還不知道有沒有更好的方法,所以上上策還不敢斷言,不過這裡要說的可以說是上策。我們在學習可序列化類Serializable的時候,應該就已經知道了這個類可以讓一個對象固化到磁盤,也就是說這個對象我們可以把它保存到磁盤上。下次在需要用的時候再去讀一下就OK了。所以,這裡我們就可以這樣來做。

首先,我們需要讓BloomFilter及其相關類實現Serializable接口,因為這些對象需要被持久化。並且添加上serialVersionUID成員常量。

保存到磁盤:

/**

* 將一個對象寫入到磁盤

*

* @param s

* 待寫入的對象

* @param path

* 寫入的路徑

*/

public static void writeObject(Serializable s, String path) {

try {

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));

objectOutputStream.writeObject(s);

objectOutputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

從磁盤中讀取對象:

public static Object readObject(String path) {

Object object = null;

try {

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));

object = objectInputStream.readObject();

objectInputStream.close();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

return object;

}

測試過程:

測試的方法很簡單,我們先在過濾中添加一些數據。並將持有數據的過濾器對象寫到磁盤中。在我們需要的時候去讀取磁盤上保存的對應文件即可。代碼邏輯如下:

public class BloomFilterTest {

public static void main(String[] args) {

String path = "F:/Temp/bloom.obj";

BloomFilterTest test = new BloomFilterTest();

test.testWriteBloomFilter(path);

BloomFilter readFilter = test.testReadBloomFilter(path);

boolean b1 = readFilter.contains("baidu");

boolean b2 = readFilter.contains("google");

boolean b3 = readFilter.contains("naga");

boolean b4 = readFilter.contains("hello");

boolean b5 = readFilter.contains("world");

boolean b6 = readFilter.contains("java");

System.out.println(b1);

System.out.println(b2);

System.out.println(b3);

System.out.println(b4);

System.out.println(b5);

System.out.println(b6);

}

private void testWriteBloomFilter(String path) {

BloomFilter filter = new BloomFilter();

filter.add("baidu");

filter.add("google");

filter.add("naga");

filter.add("hello");

filter.add("world");

SerializationUtils.writeObject(filter, path);

}

private BloomFilter testReadBloomFilter(String path) {

Object object = SerializationUtils.readObject(path);

return (BloomFilter)object;

}

}

測試結果:

我們在過濾器中添加了"baidu", "google", "naga", "hello", "world"這些字符串值。在驗證的時候,我們多驗證了一個"java"字符串。如果方案可行,我們將獲得5個true和1個false的結果。以下是測試結果:

true

true

true

true

true

false

由此驗證此方法可行。

注意事項:

1.過濾器內部的SimpleHash內部類也需要實現Serializable接口。因為這個SimpleHash也有對象在過濾器中,在持久化的時候,SimpleHash對象也會被持久化到磁盤;

2.本文的測試實例,可以在下面GitHub工程的org.naga.demo.bloom包下獲得;

3.本方案的作用點是在於停止程序的後勤工作。所以,必須保證程序能夠完成這些後勤工作。也就是說,我們不能突然去停止程序的運行,這樣程序因為來不及保存數據而讓BloomFilter對象持久化失敗。如果想要規避這個問題,就必須要作出一些其他的犧牲——性能下降。我們可以通過定時給BloomFilter進行持久化,這樣如果程序被突然中止,也只是會損失一部分數據的記錄,不會造成很大的影響。因為,這樣會是程序的性能有所下降,所以如何取捨還是要看需求了。

測試源碼工程GitHub鏈接:

https://github.com/William-Hai/SimpleDemo

---------------------

本文來自 Q-WHai 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/lemon_tree12138/article/details/50069047?utm_source=copy


分享到:


相關文章: