简单爬虫设计(五)——重构爬虫控制流程

前言

关于本文的一些背景知识,请移步该系列的前序文章。

简单爬虫设计(一)——基本模型

简单爬虫设计(二)——爬取范围

简单爬虫设计(三)——需要处理的网页范围

简单爬虫设计(四)——管理爬虫内部状态

待重构的代码和坏味道

在本系列第一篇文章中,展示了爬虫的控制逻辑代码,现在重新把它放在下面。这部分代码虽然利用了领域模型基本模块,但是职责不清,层次不够清晰,很多语句并不在相同的抽象层次上。

包含的代码坏味道有:

  • 同一个函数有多个职责:fetchAndProcess方法
  • 同一个函数中的语句不在一个抽象层次:crawl方法
  • 没有一个地方可以清楚地展现完整处理流程
//示意代码,忽略了部分实现细节
public class CrawlerImpl implements Crawler {
     
    public void crawl() {
        Link target = null;
        while (null != (target = targetLinks.next())) {
            try {
                fetchAndProcess(target);
                if (this.fetchedLinks.total() >= crawlingScope.maxToCrawl()) {
                    return;
                }
                TimeUnit.MILLISECONDS.sleep(this.crawlDelay);
            } catch (Exception e) {
                //记录错误信息
            }
        }
    }
}

private void fetchAndProcess(Link target) {
        //不在爬取范围内,略过
        if (!this.crawlingScope.contains(target)) {
            return;
        }
        //已经爬取过,略过
        if (target.getDepth() > 0 && this.fetchedLinks.contains(target)) {
            return;
        }
        
        Webpage webpage = fetch(target);

        if (processingScope.contains(webpage)) {
            webpageRepository.add(webpage);
        }

        Links allLinks = webpage.links();
        for (Link link : allLinks) {
            
            if (this.crawlingScope.contains(link) && 
                !this.fetchedLinks.contains(link)) {
                targetLinks.add(link);
            }
        }
        //当前链接为放入已采集集合
        this.fetchedLinks.add(target);
    }

重构过程

第一步,拆分fetchAndProcess方法。

这个方法中包含了太多职责,有判断是否需要爬取的逻辑,有下载网页的逻辑,还有处理网页的逻辑和抽取链接的逻辑,需要依次提取成独立的方法。

  1. 提取fetch方法,单纯的下载网页,并返回Webpage对象。
//单纯的下载网页,返回Webpage对象
private Webpage fetch(Link target) {
    Webpage webpage = downloader.download(target);
    return webpage;
}
  1. 提取shouldFetch方法,判断一个目标是否需要采集,返回布尔值。
//判断一个目标是否需要采集
private boolean shouldFetch(Link target) {
      //不在爬取范围内,略过
      if (!this.crawlingScope.contains(target)) {
          return false;
      }
      //已经爬取过,略过
      if (target.getDepth() > 0 && this.fetchedLinks.contains(target)) {
          return false;
      }
      return true;
  }
  1. 提取process方法,仅处理网页,保存或者抽取详细信息。
//处理网页,保存或者抽取详细信息
private void process(Webpage webpage) {

    if (processingScope.contains(webpage)) {
        webpageRepository.add(webpage);
        logger.info("Saved html {}", webpage.getUrl());
    }

    if (this.webExtractor != null) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        System.out.println(gson.toJson(extract(webpage)));
    }
}
  1. 提取harvestLinks方法,从网页中收集需要继续爬取的链接。
//从网页中收集链接
private Collection<Link> harvestLinks(Links allLinks) {
    List<Link> result = new LinkedList<>();
    for (Link link : allLinks) {
        if (this.crawlingScope.contains(link) && 
           !this.fetchedLinks.contains(link)) {
            result.add(link);
        }
    }
    return result;
}
  1. 提取shouldStop方法,判断是否应该停止采集。
private boolean shouldStop() {
    if (this.fetchedLinks.total() >= crawlingScope.maxToCrawl()) {
        return true;
     }
    return false;
}

第二步,重新整理crawl方法。

可以更加清晰展现爬虫的控制逻辑,尽量让每个语句在同一个抽象层次。

public void crawl() {     
    Link target;
    while (null != (target = this.targetLinks.next())) {
        try {
            if (!shouldFetch(target)) {  //是否需要采集
                continue;
            }
            //爬取网页
            Webpage webpage = fetch(target);   
            //处理网页
            process(webpage);                  
            //收集链接
            this.targetLinks.addAll(this.harvestLinks(webpage.links())); 
            //设置已采集
            this.fetchedLinks.add(target);

            if (shouldStop()) {  //是否停止
                break;
            }
            TimeUnit.MILLISECONDS.sleep(this.crawlDelay);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结

通过重构,学到了两点:

一是要通过深入理解领域逻辑,抽象好的领域模型;

二是用好领域模型,需要重构加深对应用逻辑的理解。

有了这两点,才能让应用逻辑更加清晰,方便后续扩展功能。

如果喜欢这系列文章,欢迎点赞+关注+收藏!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值