Veiking百草园


老狗啃爬虫-去重自定义之Scheduler

老狗啃骨头   @Veiking   2020-12-25

老狗啃爬虫-去重自定义之Scheduler

摘要:

经过对WebMagic的源码进行了走读,知道了Spider默认设置了QueueScheduler,用以处理链接重复的问题。本次学习我们再次对WebMagic的源码进行了走读,并实现了一个判定重复的简单逻辑。至此,WebMagic框架的Scheduler组件是如何实现对URL的判定重复,实际应用中,会有更加具体、更加复杂的业务需要,我们在此思路下进行实现即可

  接上回,我们读了读爬虫框架WebMagic的源码,我们已经知道了是Spider帮我们默认设置了QueueScheduler,用以协助处理链接重复的问题。
  然而实际的使用中,有时我们抓取的目标网站连接规则上不是那么规范,亦或根据不同的参数组合,不同的URL很有可能指向的是相同内容的页面,这时候在逻辑上可预先判断的情况下,去重就不是简单的URL字符串对比了,我们就需要一些额外的规则考量,来设计实现满足我们具体需求的Scheduler。

源码走读

  我们来仔细看看源代码,WebMagic框架的Scheduler组件,在URL去重复的过程中,是如何工作的。
  我们从中枢类Spider进入QueueScheduler类,可以看到,很明显此类继承的父类DuplicateRemovedScheduler是用来处理重复问题的,那我们进去看看:



  从代码可以发现,在push方法里,实现了URL是否重的判定业务,其中,shouldReserved()、noNeedToRemoveDuplicate ()分别表示保留和不删除的特定设置验证,这个我们暂且不究,不难看出,具体判定重复的业务逻辑,在HashSetDuplicateRemover类的isDuplicate()方法中,于是我们跟过去看看:



  可以看到,isDuplicate()方法中利用一个巧妙的逻辑,即Set在添加相同元素时会返回false,完成了URL是否重复的对比判断。
  好了,源代码已走读完毕,实现功能的逻辑也非常清晰了,我们就在此代码的基础上,来实现去重复的自定义。

功能实现

  经过代码走读,东西已经很清晰了,我们直接比葫芦画瓢,照着HashSetDuplicateRemover的模样,创建我们的DuplicateRemover:

package cn.veiking.scheduler.component;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import us.codecraft.webmagic.Request;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.scheduler.component.DuplicateRemover;

/**
* @author    :Veiking
* @version    :2020年12月12日 
* 说明        :处理URL重复问题
*/
public class VDuplicateRemover implements DuplicateRemover {

    private Set urls = Collections.newSetFromMap(new ConcurrentHashMap());

    @Override
    public boolean isDuplicate(Request request, Task task) {
        String url = getUrl(request);
        // 设置规则,我们假设这个编码的人物信息页面已经获取,故在抓取的时候,当作重复
        // 1000058-刘昶
        String celebrityCode = "1000058";
        if(url.contains(celebrityCode)) {
            return true;
        }
        // 如原Set没有则非重复
        if(urls.add(url)) {
            return false;
        }
        return true;
    }

    protected String getUrl(Request request) {
        return request.getUrl();
    }

    @Override
    public void resetDuplicateCheck(Task task) {
        urls.clear();
    }

    @Override
    public int getTotalRequestsCount(Task task) {
        return urls.size();
    }
}

  这里我们设置一个变量参照,即假定编码为1000058,名为刘昶的人物信息页面,我们已经抓取过了,根据本次抓取网站URL的组成特点,URL字符串中包含此编码,即认为重复。
  由于我们是将编码1000056-屈原的页面作为种子URL,顺着前后翻页的链接顺藤摸瓜进行数据抓取的,这是一个前继后续的线性爬取链,如若此处去重逻辑设置能满足预期,爬取过程必定会在编码为1000058的URL处向后隔断,只能爬取此编码页面之前的数据,我们接下来赶紧完成代码,验证下。
  写好了VduplicateRemover类之后,我们继续,参照QueueScheduler,实现我们要用的Scheduler:

package cn.veiking.scheduler;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import cn.veiking.base.common.logs.SimLogger;
import cn.veiking.scheduler.component.VDuplicateRemover;
import us.codecraft.webmagic.Request;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.scheduler.MonitorableScheduler;
import us.codecraft.webmagic.scheduler.component.DuplicateRemover;
import us.codecraft.webmagic.utils.HttpConstant;

/**
* @author    :Veiking
* @version    :2020年12月12日
* 说明        :处理待抓取URL,管理URL队列
*/
@Service
public class VQueueScheduler implements MonitorableScheduler{
    SimLogger logger = new SimLogger(this.getClass());
    private DuplicateRemover duplicatedRemover = new VDuplicateRemover(); // 链接重复规则自定义设置
    private BlockingQueue queue = new LinkedBlockingQueue();

    @Override
    public void push(Request request, Task task) {
        String url = request.getUrl();
        logger.info("VQueueScheduler get [url:{}] ", url);
        boolean isDuplicate = duplicatedRemover.isDuplicate(request, task); // 重复 // 
        boolean shouldReserved = this.shouldReserved(request);    // 应保留的
        boolean noNeedToRemoveDuplicate = this.noNeedToRemoveDuplicate(request);    // 不需要删除的
        if (shouldReserved || noNeedToRemoveDuplicate || !isDuplicate) {
            logger.info("VQueueScheduler push to queue [url:{}]", url);
            queue.add(request);
        }
    }

    @Override
    public Request poll(Task task) {
        return queue.poll();
    }

    @Override
    public int getLeftRequestsCount(Task task) {
        return queue.size();
    }

    @Override
    public int getTotalRequestsCount(Task task) {
        return getDuplicateRemover().getTotalRequestsCount(task);
    }

    public DuplicateRemover getDuplicateRemover() {
        return duplicatedRemover;
    }

    public VQueueScheduler setDuplicateRemover(DuplicateRemover duplicatedRemover) {
        this.duplicatedRemover = duplicatedRemover;
        return this;
    }

    protected boolean shouldReserved(Request request) {
        return request.getExtra(Request.CYCLE_TRIED_TIMES) != null;
    }

    protected boolean noNeedToRemoveDuplicate(Request request) {
        return HttpConstant.Method.POST.equalsIgnoreCase(request.getMethod());
    }
}

  此处我们为了强调我们这次编程学习的重点,减少了源码中的一层继承关系,即QueueScheduler、DuplicateRemovedScheduler的功能都在我们的VQueueScheduler中实现了。
  我们可以看到,在我们写的VqueueScheduler中,VduplicateRemover也已替换了原来的HashSetDuplicateRemover,接下来我们就测试一下,看看预想的效果能否如愿实现。

程序测试

  完成了上面的代码,我们就可以进行测试了。
  测试入口在原来的基础上,稍作变动:

package cn.veiking.aigufeng;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import cn.veiking.StartTest;
import cn.veiking.base.common.logs.SimLogger;
import cn.veiking.scheduler.VQueueScheduler;
import cn.veiking.spider.AigufengCelebrityImgPipeline;
import cn.veiking.spider.AigufengCelebrityPipeline;
import cn.veiking.spider.AigufengCelebrityProcessor;
import us.codecraft.webmagic.Spider;

/** 
* @author    :Veiking
* @version    :2020年12月8日
* 说明        :aigufengCelebrityPipeline、aigufengCelebrityImgPipeline、aigufengCelebrityProcessor 测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartTest.class)
public class AigufengCelebrityTest {
    SimLogger logger = new SimLogger(this.getClass());

    @Autowired
    private AigufengCelebrityPipeline aigufengCelebrityPipeline;
    @Autowired
    private AigufengCelebrityImgPipeline aigufengCelebrityImgPipeline;


    @Autowired
    private VQueueScheduler vQueueScheduler;

    @Autowired
    private AigufengCelebrityProcessor aigufengCelebrityProcessor;

    private static final String StartUrl = "http://www.aigufeng.com/celebrity/article-1000056/page.html";

    @Test
    public void testSpider() {
        long startTime, endTime;
        logger.info("AigufengCelebrityTest testSpider [start={}] ", "开始爬取数据");
        startTime = System.currentTimeMillis();
        Spider.create(aigufengCelebrityProcessor)
        .addUrl(StartUrl)
        .addPipeline(aigufengCelebrityPipeline)
        .addPipeline(aigufengCelebrityImgPipeline) // 添加图片下载管道
        .setScheduler(vQueueScheduler) // 设置自定义Scheduler
        .thread(3)
        .run();

        endTime = System.currentTimeMillis();
        logger.info("AigufengCelebrityTest testSpider [end={}] ", "爬取结束,耗时约" + ((endTime - startTime) / 1000) + "秒");
    }
}

  变化就是增添这一行代码:.setScheduler(vQueueScheduler),即给Spider设置成我们要用的VqueueScheduler。
  一切OK,运行程序,进行测试验证…



  通过窗口日志我们可以看到,没有找到包含编码1000058 URL 的push to 操作,也没有看到编码序列比1000058大的get或push操作,即预期设想已实现。

结语

  本次学习我们再次对WebMagic的源码进行了走读,并实现了一个判定重复的简单逻辑。至此,WebMagic框架的Scheduler组件是如何实现对URL的重复剔除,已然十分清楚了,当然实际应用中,会有更加具体、更加复杂的业务需要,在此思路下进行补充实现即可。
  在我们剖析URL去重的过程中,对Scheduler的工作机制已经有了比较完整的理解,去重是没问题了,但爬虫应用在实际的使用场景中,很容易发生程序中断这种情况,如何将抓取工作过程的数据持久化也是个很迫切的需求。
  抓取工作过程的数据,即待抓取URL队列、已抓取URL队列。接下来,我们将试着实现如何保存和使用这些数据,实现增量爬取的功能。


老狗啃骨头



慷慨发言

(您提供的信息将用于后续必要的反馈联系,本站会恪守隐私)

潜影拾光

羊卓雍错

美丽羊湖,藏南明珠。

扫码转发

二维码
二维码
二维码
二维码
二维码
二维码

博文标签