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

目录

  • 什么是爬虫程序?
  • 爬虫软件设计
    • 起始网址(Start URLs)
    • 链接(Link)
    • 网页(Webpage)
    • 遍历规则(Crawling Rule)
    • 爬取范围(Crawling Scope)
    • 处理范围(Processing Scope)
    • 爬取任务(Crawling Task)
    • 爬虫(Crawler)
    • 待采集链接集合(Target Links)
    • 已采集链接集合(Fetched Links)
    • 下载器(Downloader)
  • 代码示例
    • 基本构造模块
    • 爬虫构建过程
    • 爬虫控制过程
  • 小结
  • 继续阅读

本系列文章记录了一个简单的网页爬虫的设计过程,设计过程主要采用面向对象设计思想。下面开始正式内容。

一个简单的爬虫一般由网页爬取(Crawler)和网页解析(Parser)两部分组成。这个系列,主要讨论网页爬取部分的设计。

什么是爬虫程序?

简单来说,爬虫程序就是从起始网址开始,按照某种规则遍历目标网站,并处理特定网页的程序。

爬虫软件设计

从爬虫程序的工作内容,大概可以梳理出以下概念。

起始网址(Start URLs)

起始网址是爬虫程序最先爬取的网址,是一次爬取任务的入口,入口可以有多个,一般会选择网站主页或者链接列表页作为入口。

链接(Link)

包括链接文字,URL,链接深度,父URL,起始网址是深度为0链接。

网页(Webpage)

对于简单爬虫,网页可以抽象为链接的集合和HTML文件,因为简单爬虫不对HTML包含的媒体内容进行信息抽取。

对于需要抽取特定网页信息的爬虫,根据抽取内容不同,需要对网页进行不同的抽象。比如抽取网页元数据的爬虫,需要解析HTML文件的所有meta标签信息;抽取关联数据(Linked Data)的爬虫,可能需要解析网页内部的json-ld;抽取视频内容的爬虫,需要关注视频标题、作者、视频链接、视频长度,还要下载视频文件。

网页是一个HTML文件,HTML的英文全称是 Hyper Text Markup Language,即超文本标记语言,超文本的含义是除了文本,还可以包含多种类型媒体内容。

遍历规则(Crawling Rule)

遍历规则由用户设置,这些规则决定了爬虫访问哪些链接。

例如,如果要采集某个网站的所有网页,那么遍历规则就是该网站域名下的所有链接。如果要便利某个网站的特定频道,遍历规则就要限定为某个字域名或者特定的父路径。如果只采集某个页面内的链接,那么遍历规则就要控制链接深度(如果规定起始URL深度为0,那么这里就要设置链接深度为1)。有时候,还需要采集并保存包含特定特征的网页,比如包含正文详情的页面。

从上面的分析,发现遍历规则这个概念可以细分为爬取范围和处理范围,爬取范围还包含了起始网址的概念。

爬取范围(Crawling Scope)

起始网址和遍历规则实际上约束了爬虫的爬取范围,可以对这个概念显示建模。爬取范围包括:

  • 起始网址
  • 要遍历的链接深度
  • 要遍历的链接特征
  • 最大遍历链接数量
  • 是否只采集域名内链接

处理范围(Processing Scope)

需要后续处理的链接和网页规则,包括:

  • URL特征
  • 网页内部特征
  • 最多处理网页数量

有了上面这些基础要素,就很容易定义一个爬取任务了。

爬取任务(Crawling Task)

爬取任务应该至少包含如下信息:

  • 任务名称和ID
  • 爬取范围
  • 处理范围
  • 任务调度约束
  • 网页保存位置
  • 其他所需的信息

爬虫(Crawler)

爬虫负责采集过程的控制。

  • 输入,是一个爬取任务,包含了爬虫运行所需的基本信息和控制信息。
  • 输出,是网页集合,这些网页可能需要保存到文件系统或者数据库中。
  • 在采集过程中,需要根据爬取任务下载网页,同时满足采集间隔、采集总网页数等限制条件。因为网页之间链接关系构成了一张图,为了不重复地遍历这些网页,爬虫内部要维护待采集链接和已采集链接的集合,还要选择一种遍历方式:深度优先或者广度优先。

待采集链接集合(Target Links)

爬虫从每个网页内收集链接,把需要采集的链接放入待采集集合中。

已采集链接集合(Fetched Links)

爬虫把已经遍历过的链接放入已采集链接的集合,这样可以避免对相同链接进行重复采集。

下载器(Downloader)

爬虫遍历网页时,需要使用网页下载器,通常是一个Http客户端,有些场景需要通过代理访问目标网站。

代码示例

有了上述这些基本要素,就可以开始组装一个简单的网页爬虫了。

基本构造模块


public class Link {
    String url;
    int depth;
    //getter setter
}

public class Webpage {
    Set<Links> links;
    String html;
    //getter setter
}

interface CrawlingScope {  //爬取范围
    //起始网址
    List<String> getStartUrls();
    //哪些URL会继续爬取
    boolean contains(Link link);
    //最多爬取多少个链接
    long maxToCrawl();
    //爬取的最大深度
    int getMaxHops();
}

interface ProcessingScope {  //处理范围
    //某个网页是否要被处理
    boolean contains(Webpage webpage);
    //最多处理多少个网页
    long maxToProcess();

    long maxToProcessPerSubDomain();
}

interface TargetLinks {  //待采集链接集合

    void add(Link link);

    long size();

    Link next();

    void clear();
}

interface FetchedLinks {  //已采集链接集合

    boolean contains(Link link);

    void add(Link link);

    long total();

    void clear();
}

class CrawlingTask {  //爬取任务
    String name;
    private boolean enable;
    CrawlingScope crawlingScope;
    ProcessingScope processingScope;

}

interface Crawler {  //爬虫
    void crawl();
}

爬虫构建过程

主要是把CrawlingTask中的约束传递给Crawler。

//示意代码,忽略了部分实现细节
public class CrawlerBuilder {

    public Crawler build(CrawlingTask task) {
        CrawlerImpl crawler = new CrawlerImpl();
        //其他信息略...
        crawler.setCrawlingScope(task.getCrawlingScope());
        crawler.setProcessingScope(task.getProcessingScope());

        TargetLinksImpl targets = new TargetLinksImpl();
        targets.addAll(task.startUrls());
        crawler.setTargetLinks(targets);

        FetchedLinksImpl fetched = new FetchedLinksImpl();
        crawler.setFetchedLinks(fetched);
        
        return crawler;
    }
}

爬虫控制过程

//示意代码,忽略了部分实现细节
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);
    }

更新:在后续文章中对上面的这段代码进行了重构,控制逻辑更加清晰。
简单爬虫设计(五)——重构控制流程

小结

通过这篇文章,大概描述了一个简单爬虫的建模过程。后续文章将对爬虫的各个组成部分的实现细节进行介绍。

继续阅读

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

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

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

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

你可能感兴趣的:(软件设计,设计模式,java,爬虫)