高拓展性的Java多线程爬虫框架reptile(个人开源项目)

简介

Reptile是一个具有高拓展性的可支持单机与集群部署Java多线程爬虫框架,该框架可简化爬虫的开发流程。该框架各个组件高内聚松耦合的特性让用户可以对不同组件进行定制来满足不同的需求。

特性

  • 模块化设计,具有高度拓展性
  • 支持单机多线程部署
  • 支持简单集群部署
  • 配置简单清晰
  • 支持同步或异步运行
  • 单机部署时,请求爬取完毕并且无其他线程产生新请求时会自动停止爬虫并关闭所有可关闭的资源
  • 整合Jsoup,支持HTML页面解析
  • 请求调度器支持URL或请求的去重处理,提供布隆过滤器与集合等去重实现,默认使用布隆过滤器,可在配置类进行指定
  • 支持设置UserAgent池与Proxy池,并且可设置请求对UserAgent与Proxy的选择策略,如随机或循环顺序选择
  • 当爬取请求出现IO异常时,支持请求重试,可在配置类指定请求重试次数

架构

Reptile.png

Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:

  • Scheduler 执行请求调度,支持添加与拉取新的爬取请求,并支持去重处理
    • FIFOQueueScheduler 基于Java的ConcurrentLinkedQueue实现的先进先出无限队列调度器、线程安全、不支持持久化,适合作为小型爬虫的请求调度器
    • RedisFIFOQueueScheduler 基于Redis的列表实现的先进先出无限队列调度器、线程安全、阻塞添加与拉取请求、支持持久化,适合作为大型爬虫的请求调度器
  • Downloader 执行请求下载与解析响应
    • HttpClientDownloader基于apache的httpclient实现的下载器
  • ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取请求Request
  • Consumer 来对处理的结果Result进行消费,例如持久化存储,用户可自定义其具体实现
    • ConsoleConsumer 控制台数据消费者,默认使用System.out.println将数据输出到控制台
    • JsonFileConsumer Json文件消费者,将Result数据序列化为JSON字符串并按行输出到指定文件,这样读取数据时可直接按行反序列化JSON字符串
    • MongoDBConsumer MongoDB数据消费者,将Result数据存储到指定的MongoDB数据库中的表

四个组件之间的关系如架构图所示,它们之间的互相调用形成一个完整的工作流并在Workflow线程中运行,Reptile爬虫会根据配置的线程数量通过线程池创建指定数量的工作流线程并发执行工作流任务。

快速开始

使用Maven

  1. clone项目并构建发布到本地仓库
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
  1. 在项目中使用Maven引入对应的依赖
<dependency>
    <groupId>com.xiepuhuan</groupId>
    <artifactId>reptile</artifactId>
    <version>0.3</version>
</dependency>

使用方式

  1. 实现ResponseHandler接口,重写isSupporthandle方法。
    • isSupport方法根据reponseContext参数对象判断是否需要处理该响应,是则返回true,否则返回false
    • handle方法处理该响应,并将处理结果存储到result,如果从响应中有提取到要爬取的新请求则将其作为返回值返回。
    • 如果没有找到支持处理该响应的处理器则响应会被忽略。
  2. 实现Consumer接口,重写consume方法,执行对数据的消费,可在该方法中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer

推荐

  • 推荐使用MongoDBConsumer作为爬虫消费者, 因为其面向文档存储, 文档可嵌套文档、数组, 并且预先不需要建表, 这些特性非常适合爬虫爬取的不确定网络数据, JSON格式数据的存储。
  • 若是使用MongoDBConsumer作为数据消费者, 那么必须在ResponseHandler中的handle方法中调用resultsetExtendedField方法并使用ResultExtendedField.MONGODB_DATABASE_COLLECTION_NAME常量作为键设置数据存储的表名称。

示例

单机部署

public class ZhihuPageHandler implements ResponseHandler {

    private static final String[] URLS = new String[] {
            "https://www.zhihu.com/api/v4/search_v3?t=general&q=java"
    };


    @Override
    public List<Request> handle(Response response, Result result) {
        Content content = response.getContent();
        JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
        result.setResults(jsonObject.getInnerMap());

        JSONObject paging = jsonObject.getJSONObject("paging");

        if (!paging.getBoolean("is_end")) {
            List<Request> requests = new ArrayList<>();
            requests.add(new Request(paging.getString("next")));
            return requests;
        }
        return null;
    }

    @Override
    public boolean isSupport(Request request, Response response) {
        return true;
    }

    public static void main(String[] args) {

        // 构建Reptile爬虫配置类,
        ReptileConfig config = ReptileConfig.Builder.cutom()
                .setThreadCount(8)
                .appendResponseHandlers(new ZhihuPageHandler())
                .setDeploymentMode(DeploymentModeEnum.SINGLE)
                .setConsumer(new ConsoleConsumer())
                .build();
        // 根据reptile配置构建Reptile爬虫并添加爬去的URL
        Reptile reptile = Reptile.create(config).addUrls(URLS);
        // 启动爬虫
        reptile.start();
    }
}

分布式部署

分布式部署时,创建配置类时需要通过setDeploymentMode方法指定部署模式为DeploymentModeEnum.Distributed,并且需要通过setScheduler方法设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。

public class ZhihuPageHandler implements ResponseHandler {

    private static final String[] URLS = new String[] {
            "https://www.zhihu.com/api/v4/search_v3?t=general&q=java"
    };

    @Override
    public List<Request> handle(Response response, Result result) {
        Content content = response.getContent();
        JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
        result.setResults(jsonObject.getInnerMap());

        JSONObject paging = jsonObject.getJSONObject("paging");

        if (!paging.getBoolean("is_end")) {
            List<Request> requests = new ArrayList<>();
            requests.add(new Request(paging.getString("next")));
            return requests;
        }
        return null;
    }

    @Override
    public boolean isSupport(Request request, Response response) {
        return true;
    }

    public static void main(String[] args) {
            
            // 构建Redis队列调度器
            Scheduler scheduler = RedisFIFOQueueScheduler.Builder.custom()
                    .setRedisConfig(RedisConfig.DEFAULT_REDIS_CONFIG)
                    .setRequestFilter(RedisBloomRequestFilter.Builder.create())
                    .build();
            // 构建数据消费者
            Consumer consumer = new MongoDBConsumer(MongoDBConfig.DEFAULT_MONGODB_CONFIG);
    
            // 构建Reptile爬虫配置类
            ReptileConfig config = ReptileConfig.Builder.cutom()
                    .setThreadCount(8)
                    .appendResponseHandlers(new ZhihuPageHandler())
                    .setDeploymentMode(DeploymentModeEnum.Distributed)
                    .setScheduler(scheduler)
                    .setConsumer(consumer)
                    .build();
            // 根据reptile配置构建Reptile爬虫并添加爬去的URL
            Reptile reptile = Reptile.create(config).addUrls(URLS);
            // 启动爬虫
            reptile.start();
        }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349