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

前言:

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

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

原文地址:https://www.cnblogs.com/fengju/p/6336029.html