网络爬虫:基于对象持久化实现爬虫现场快速还原

前言:

因为中间有一些其他的任务工作,所以有一些时日没有再关心爬虫的程序了。今天想到了另一个优化爬虫的思路。

在上篇中,我们说到可以使用布隆过滤器可以很好地实现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


分享到:


相關文章: