文档章节

Java 动手写爬虫: 四、日志埋点输出 & 动态配置支持

小灰灰Blog
 小灰灰Blog
发布于 2017/07/27 12:34
字数 1256
阅读 1365
收藏 62

第四篇, 日志埋点输出 & 动态配置支持

前面基本上实现了一个非常简陋的爬虫框架模型,很多关键链路都没有日志,在分析问题时,就比较麻烦了,因此就有了这一篇博文

其次就是解决前几篇遗留的容易解决的问题

实际上,日志的输出应该贯穿在实际的开发过程中的,由于之前写得比较随意,直接System.out了, 所以现在就来填坑了

1.日志埋点设计

采用 logback 左右日志输出, 这里有一篇博文可供参考 《Logback 简明使用手册

埋点的关键链路

  1. 当前爬取的任务信息
  2. 爬取任务的耗时
  3. 应用的状态(如爬取了多少个,还剩下多少个待爬取等)
  4. 爬取结果输出
  5. 其他一些信息

实现比较简单,在pom中添加依赖

<!--日志-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>

添加配置文件

logback-test.xml

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.quick.hui.crawler" level="DEBUG"/>


    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

代码中埋点

.... (直接参考源码即可)

2. 爬取频率控制

很多网站会对访问的频率进行限制,这是一个最基础的防爬手段了,所以我们的爬取需要一个可以设置爬取任务的频率控制

1. 设计

目的

  • 采用一个比较简单的方案,每次从队列中获取爬取任务时,sleep指定的时间,来实现爬取频率的限制
  • 对此我们设计得稍微高级一点,这个sleep时间,我们希望是可以动态配置的

方案

采用配置项来解决这个,(为了后续的拓展,读取配置搞成面向接口的编程方式),我们先提供一个基础的,根据本地配置文件来读取频率控制参数

实现

因为采用配置文件的方式,所以一个用于读取配置文件的辅助工具类是必须的

1. 配置文件读取 FileConfRead

@Slf4j
public class FileConfRead implements IConfRead {


    public Config initConf(String path) {
        try {
            Properties properties = read(path);

            Config config = new Config();
            config.setSleep(properties.getProperty("sleep"), 0);
            config.setEmptyQueueWaitTime(properties.getProperty("emptyQueueWaitTime"), 200);

            return config;
        } catch (Exception e) {
            log.error("init config from file: {} error! e: {}", path, e);
            return new Config();
        }
    }


    private Properties read(String fileName) throws IOException {
        try (InputStream inputStream = FileReadUtil.getStreamByFileName(fileName)) {
            Properties pro = new Properties();
            pro.load(inputStream);
            return pro;
        }
    }


    private File file;
    private long lastTime;

    public void registerCheckTask(final String path) {
        try {
            file = FileReadUtil.getFile(path);
            lastTime = file.lastModified();


            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                        if (file.lastModified() > lastTime) {
                            lastTime = file.lastModified();
                            ConfigWrapper.getInstance().post(new ConfigWrapper.UpdateConfEvent());
                        }
                    },
                    1,
                    1,
                    TimeUnit.MINUTES);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

实现类主要继承接口 IConfRead, 接口中定义了两个方法,一个用于获取配置信息,一个用于注册配置信息的变动监听事件

public interface IConfRead {

    /**
     * 初始化配置信息
     *
     * @param var
     * @return
     */
    Config initConf(String var);


    /**
     * 注册配置信息更新检测任务
     *
     * @param path
     */
    void registerCheckTask(final String path);
}

回到具体的实现,读取配置文件信息比较简单,直接使用jdk的Properties文件的读写方式,接下来则是注册监听事件的实现上,我们的设计思路如下:

  • 获取配置文件的更新时间
  • 每隔一段时间主动去查看下配置文件的更新时间,判断是否更新过
    • 若更新,则重新加载配置文件,覆盖之前的
    • 若无更新,直接放过

2. 配置类 Config

这里定义所有的配置信息,方便后续的维护和查阅

@Getter
@Setter
@ToString
public class Config {

    /**
     * 爬取任务的间隔时间
     */
    private long sleep;


    /**
     * 从队列中获取任务,返回空时,等待时间之后再进行重试
     */
    private long emptyQueueWaitTime;


    public void setSleep(String str, long sleep) {
        this.sleep = NumUtils.str2long(str, sleep);
    }

    public void setEmptyQueueWaitTime(String str, long emptyQueueWaitTime) {
        this.emptyQueueWaitTime = NumUtils.str2long(str, emptyQueueWaitTime);
    }
}

3. 配置信息获取封装类 ConfigWrapper

这里封装了获取配置信息的接口,内部维护配置信息的变更事件,我们采用EventBus来实现事件的监听

@Slf4j
public class ConfigWrapper {
    private static final String CONFIG_PATH = "conf/crawler.properties";

    private EventBus eventBus;


    private IConfRead confRead;

    private Config config;

    private static volatile ConfigWrapper instance;

    private ConfigWrapper() {
        confRead = new FileConfRead();
        confRead.registerCheckTask(CONFIG_PATH);
        config = confRead.initConf(CONFIG_PATH);


        // 注册监听器
        eventBus = new EventBus();
        eventBus.register(this);
    }


    public static ConfigWrapper getInstance() {
        if (instance == null) {
            synchronized (ConfigWrapper.class) {
                if (instance == null) {
                    instance = new ConfigWrapper();
                }
            }
        }

        return instance;
    }


    @Subscribe
    public void init(UpdateConfEvent event) {
        config = confRead.initConf(event.conf);

        if (log.isDebugEnabled()) {
            log.debug("time:{} processor:{} update config! new config is: {}",
                    event.now, event.operator, config);
        }
    }


    public Config getConfig() {
        return config;
    }


    public void post(Object event) {
        eventBus.post(event);
    }

    @Getter
    @Setter
    public static class UpdateConfEvent {
        private long now = System.currentTimeMillis();

        private String operator = "System";

        private String conf = CONFIG_PATH;
    }
}

3. 源码地址

项目地址: https://github.com/liuyueyi/quick-crawler

日志埋点对应的tag: v0.006

动态配置对应的tag: v0.007

相关链接

欢迎关注我的微信公众号

小灰灰的公众号

© 著作权归作者所有

小灰灰Blog
粉丝 203
博文 227
码字总数 405059
作品 0
武汉
程序员
私信 提问
加载中

评论(3)

walykyy
walykyy
支持,赞一个,Mark
小灰灰Blog
小灰灰Blog 博主

引用来自“思静谦”的评论

恩,这个逻辑是不是借鉴webmagic这个项目,很感谢讲的这么清晰,有当讲师潜力
是的,主要参考 webcollector + crawler4j + webmagic ; 具体的实现没有他们优雅,主要是为了分解一下设计一个爬虫框架的思路
发票小哥
发票小哥
恩,这个逻辑是不是借鉴webmagic这个项目,很感谢讲的这么清晰,有当讲师潜力
MyBatis源码窥探:MyBatis整体架构解析

Mybatis的使用这里就不介绍了,不知道怎么使用的朋友可以点击 http://www.mybatis.org/mybatis-3/zh/index.html 这里面的教程很详细,包括xml的配置、映射、动态sql都有介绍,可以学习和使用...

java邵先生
01/15
0
0
你必须掌握的 21 个 Java 核心技术!(干货)

点击上方“java进阶架构师”,选择右上角“置顶公众号” 20大进阶架构专题每日送达 51闲来无事,师长一向不(没)喜(有)欢(钱)凑热闹,倒不如趁着这时候复盘复盘。而写这篇文章的目的是想...

Java进阶架构师
05/03
0
0
[jvm]九java gc分析

Java GC就是JVM记录仪,书画了JVM各个分区的表演。 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门...

菜鸟腾飞
2018/12/02
0
0
Elasticsearch的基友Logstash

Logstash 是一款强大的数据处理工具,它可以实现数据传输,格式处理,格式化输出,还有强大的插件功能,常用于日志处理。 一、原理 Input 可以从文件中、存储中、数据库中抽取数据,Input有两...

甩锅侠
2017/12/11
0
0
Java开发者不会这些永远都只能是三流程序员,细数一下你是不是?

源码系列 手写spring mvc框架 基于Spring JDBC手写ORM框架 实现自己的MyBatis Spring AOP实战之源码分析 Spring IOC高级特性应用分析 ORM框架底层实现原理剖析 手写Spring MVC框架实现 手把手...

美的让人心动
2018/04/16
122
5

没有更多内容

加载失败,请刷新页面

加载更多

skywalking(容器部署)

skywalking(容器部署) 标签(空格分隔): APM [toc] 1. Elasticsearch SkywalkingElasticsearch 5.X(部分功能报错、拓扑图不显示) Skywalking需要Elasticsearch 6.X docker network create......

JUKE
2分钟前
0
0
解决Unable to find a single main class from the following candidates [xxx,xxx]

一、问题描述 1.1 开发环境配置 pom.xml <plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!--一定要对上springboot版本号,因......

TeddyIH
3分钟前
0
0
Dubbo服务限制大数据传输抛Data length too large: 13055248, max payload: 8388608解决方案

当dubbo服务提供者向消费层传输大数据容量数据时,会受到Dubbo的限制,报类似如下异常: 2019-08-23 11:04:31.711 [ DubboServerHandler-XX.XX.XX.XXX:20880-thread-87] - [ ERROR ] [com.al...

huangkejie
6分钟前
0
0
HashMap和ConcurrentHashMap的区别

为了线程安全,ConcurrentHashMap 引入了一个 “分段锁” 的概念。具体可以理解把一个大的 map 拆分成 N 个小的 Map 。最后再根据 key.hashcode( )来决定放到哪一个 hashmap 中去。 hashmap ...

Garphy
7分钟前
0
0
购买SSL证书需要注意哪些问题

为了保障网站的基本安全,为网站部署SSL证书,已经是一种常态了。各大浏览器对于安装了SSL证书的网站会更友好,并且不会发出“不安全”的提示。部署SSL证书之前首先得去给网站购买一个SSL证书...

安信证书
36分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部