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

本文探讨了爬虫设计中的关键组件——TargetLinks和FetchedLinks,介绍了它们在爬取逻辑中的作用,以及如何通过Redis实现数据持久化。理解这些状态管理有助于优化爬虫性能和数据跟踪。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

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

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

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

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

设计说明

从这篇文章开始讨论爬虫crawler的具体实现,首先讨论爬虫内部维护的数据结构。

本系列的第一篇文章已经包含了Crawler控制过程的示例代码,通过下面的代码片段,可以先简单回顾一下处理逻辑。其中使用的TargetLinks和FetchedLinks就是这篇文章要讨论的内容。

Link target = null;
// 如果需要爬取的链接集合还有剩余
while (null != (target = targetLinks.next())) {
    try {
         fetchAndProcess(target);
         //如果已采集的链接数量不超过采集范围约束的最大值
         if (this.fetchedLinks.total() >= crawlingScope.maxToCrawl()) {
            break;
         }
         TimeUnit.MILLISECONDS.sleep(this.crawlDelay);
     } catch (Exception e) {
         e.printStackTrace();
}

TargetLinks保存需要采集的目标链接,FetchedLinks保存已经采集过的链接。每当crawler成功爬取完一个链接,就要从TargetLinks中删除这个链接,并保存到FetchedLinks中。这两个集合就是crawler的内部状态。

代码示例

接口设计

interface TargetLinks {  //待爬取网址集合

    void addAll(Collection<Link> links);
    //增加一个链接
    void add(Link link);
    //带爬取链接总数
    long total();
    //返回下一个要爬取的链接
    Link next();
    //清空,相当于重置爬取进度
    void clear();
}

interface FetchedLinks {  //已爬取网址集合
    //某个链接是否已经爬取过
    boolean contains(Link link);
    //增加一个链接
    void add(Link link);
    //总链接数
    long total();
    //清空,相当于重置爬取进度
    void clear();
}

具体实现

具体实现的时候,这两个集合的持久化方式有很多选择。

(1)可以保存在内存中,这种方式实现最简单,但程序重启后,采集进度就丢失了,只能从头再来,测试环境可用。

(2)也可以保存在文件中,实现上复杂一些,要自己实现缓存,好处是进度不丢失。

(3)还可以保存到数据库中,常用的包括MySQL,Redis等,由于这两个集合变化速度很快,用Redis更适合,但可能需要单独安装一个数据库。

下面的代码示例是基于Redis实现的TargetLinks,只能用在实验环境,如果在生产环境使用,还需要继续完善。

需要说明的是,这个简单爬虫在整体设计上,让每个crawler实例执行一个采集任务,并且拥有独立的TargetLinks集合对象以及FetchedLinks集合对象。TargetLinks和FetchedLinks集合的名字包含了采集任务ID(taskId),以便和其他采集任务状态隔离。如果要同时执行多个采集任务,要使用多线程方式,让每个crawler实例在独立线程中运行。

细心的读者会发现,代码中使用了Redis集合(Set)作为持久化方案,这样就不能控制网站的遍历顺序了,的确如此,如果对这方面有要求,请自行切换为Redis列表。

public class TargetLinksRedisImpl implements TargetLinks {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private String setName;
    private String taskId;  // 每个任务对应一个集合,任务之间隔离
    private Gson gson = new GsonBuilder().create()

    public void setTaskId(String taskId) {
        this.taskId = taskId;
        setName = taskId + "-" + "targets";
    }

    @Override
    public void addAll(Collection<Link> links) {
        SetOperations<String, String> setOps = redisTemplate.opsForSet();
        for (Link each : links) {
            setOps.add(SET_NAME, gson.toJson(each));
        }
    }

    @Override
    public void add(Link link) {
        SetOperations<String, String> setOps = redisTemplate.opsForSet();
        setOps.add(setName, gson.toJson(link));
    }

    @Override
    public Link next() {
        SetOperations<String, String> setOps = redisTemplate.opsForSet();
        String linkJson = setOps.pop(setName);
        Link link = gson.fromJson(linkJson, Link.class);
        return link;
    }

    @Override
    public long total() {
        SetOperations<String, String> setOperations = redisTemplate.opsForSet();
        long size = setOperations.size(SET_NAME);
        return size;
    }
}

小结

这篇文章讨论了爬虫内部状态的管理,包括待爬取网址集合TargetLinks和已爬取网址集合FetchedLinks,并分别展示了这两个集合的接口设计和具体实现。

通过这两个基础概念的引入,不仅可以让爬取逻辑更加清晰,还可以方便地观察爬虫在爬取网页过程中状态的变化,并随时展示当前的爬取进度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值