网络爬虫:基于对象持久化实现爬虫现场快速还原
前言:
? 因為中間有一些其他的任務工作,所以有一些時日沒有再關心爬蟲的程序了。今天想到了另一個優化爬蟲的思路。
? 在上篇中,我們說到可以使用布隆過濾器可以很好地實現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
總結
以上是生活随笔為你收集整理的网络爬虫:基于对象持久化实现爬虫现场快速还原的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构:关于重建二叉树的三种思路
- 下一篇: 数据结构:二叉搜索树(BST)的基本操作