小小最近开始学习webmagic相关的内容,由于批量爬取的链接将会放入到内存中,将会产生OOM,所以,由此需要把数据放入redis中,确保redis实现其能爬取。

总体介绍

webmagic是一个开源的java垂直爬虫框架,目标简化爬虫的开发流程,让开发者专注于逻辑功能的开发。
采用模块化,覆盖爬虫的全部的生命周期,实现多线程抓去,分布式抓去,并支持自动重试,等功能。

架构简介

分为这四大组件 Downloader、PageProcessor、Scheduler、Pipeline 并进行相关的组织起来。

对这四个组件进行详细的解释。

Downloader

此组件负责从互联网上下载页面,进行后续的处理,采用了apache httpClient作为下载工具。

PageProcessor

此组件负责解析页面,抽取能用的信息,发现新的链接,使用jsoup作为html解析工具,基于xpath的工具,xsoup,作为页面进行相关的解析。

Scheduler

此组件负责,待抓取的URL,以及进行相关的去重,在这里,默认是使用JDK的内存队列实现,同时也支持使用Redis,实现分布式的管理。
其分布式和核心在于对Scheduler进行相关的改造。

Pipeline

此连接定义了结果保存的方式,需要对结果保存的时候,就定制pipeline

构建单机爬虫

这里构建单机的爬虫。
这个爬虫用于构建单机的爬虫。

添加相关依赖文件

<dependencies>
    <dependency>
        <groupId>us.codecraft</groupId>
        <artifactId>webmagic-core</artifactId>
        <version>0.7.3</version>
    </dependency>

    <dependency>
        <groupId>us.codecraft</groupId>
        <artifactId>webmagic-extension</artifactId>
        <version>0.7.3</version>
    </dependency>

    <dependency>
        <groupId>us.codecraft</groupId>
        <artifactId>webmagic-extension</artifactId>
        <version>0.7.3</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

</dependencies>

添加相关的日志文件

log4j.rootLogger=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

编写相关的爬虫程序

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;
import us.codecraft.webmagic.pipeline.JsonFilePipeline;
import us.codecraft.webmagic.processor.PageProcessor;

public class BaiduPageProcessor implements PageProcessor {

    private Site site = Site.me()
            .setRetryTimes(1)
            .setSleepTime(1000)
            .setCharset("utf-8");

    public void process(Page page) {
        page.putField("title", page.getHtml().css("title", "text").toString());
    }

    public Site getSite() {
        return site;
    }

    public static void main(String[] args) {
        Spider.create(new BaiduPageProcessor())
                .addUrl("http://www.baidu.com/")
                .addPipeline(new ConsolePipeline())
                .addPipeline(new JsonFilePipeline("/Users/qmp/myproject/WebMagicSpider"))
                .thread(1)
                .run();
    }
}

执行程序

控制台输出

get page: http://www.baidu.com/
title:  百度一下,你就知道

此时完成了一个单机版的爬虫程序的搭建。

分布式构建

由于当抓取的URL数量过多,会导致出现OOM,所以此时把数据保存进入Redis中,实现分布式构建。

设置Scheduler参数

这里设置Scheduler相关的参数为Redis相关的参数。

spider.setScheduler(new RedisScheduler(jedisPool));

这里需要传入相关的redis参数。即 JedisPool

如果是Spring Boot 相关,需要设置相关的RedisConfig配置类。

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;

    @Bean
    public JedisPool redisPoolFactory() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);

        return new JedisPool(jedisPoolConfig, host, port, timeout, password);
    }
}

实现分布式爬取

分布式爬取架构图如下
由原先的,变成如下的

查看源码

这里先查看一段关于run的源码

public void run() {
    checkRunningStat();
    initComponent();
    logger.info("Spider {} started!",getUUID());
    while (!Thread.currentThread().isInterrupted() && stat.get() == STAT_RUNNING) {
        final Request request = scheduler.poll(this);
        if (request == null) {
            if (threadPool.getThreadAlive() == 0 && exitWhenComplete) {
                break;
            }
            // wait until new url added
            waitNewUrl();
        } else {
            // ......
        }
    }
    // ......
}

这里更改代码如下

这样就完成了一次分布式的爬取。其中使用redis作为URL的爬取队列,实现分布式的爬取。