分布式爬虫系统设计、实现与实战:爬取京东、苏宁易购全网手机商品数据+MySQL、HBase存储...

本文介绍了作者基于Java实现的分布式爬虫系统,用于爬取京东和苏宁易购的手机商品数据。系统包括爬虫、URL调度和监控报警三大部分,使用Redis作为URL仓库,利用Zookeeper进行节点监控,数据存储可选择MySQL或HBase。文章详细阐述了各个组件的设计与实现,如随机IP代理、URL调度策略、监控报警系统等,并分析了爬虫性能与反反爬虫策略。最后,作者分享了实战中的数据爬取与存储结果,并提供了项目源代码链接。

[TOC]


1 概述

在不用爬虫框架的情况,经过多方学习,尝试实现了一个分布式爬虫系统,并且可以将数据保存到不同地方,类似MySQL、HBase等。

基于面向接口的编码思想来开发,因此这个系统具有一定的扩展性,有兴趣的朋友直接看一下代码,就能理解其设计思想,虽然代码目前来说很多地方还是比较紧耦合,但只要花些时间和精力,很多都是可抽取出来并且可配置化的。

因为时间的关系,我只写了京东和苏宁易购两个网站的爬虫,但是完全可以实现不同网站爬虫的随机调度,基于其代码结构,再写国美、天猫等的商品爬取,难度不大,但是估计需要花很多时间和精力。因为在解析网页的数据时,实际上需要花很多时间,比如我在爬取苏宁易购商品的价格时,价格是异步获取的,并且其api是一长串的数字组合,我花了几个小时的时间才发现其规律,当然也承认,我的经验不足。

这个系统的设计,除了基本的数据爬取以外,更关注以下几个方面的问题:

  • 1.如何实现分布式,同一个程序打包后分发到不同的节点运行时,不影响整体的数据爬取
  • 2.如何实现url随机循环调度,核心是针对不同的顶级域名做随机
  • 3.如何定时向url仓库中添加种子url,达到不让爬虫系统停下来的目的
  • 4.如何实现对爬虫节点程序的监控,并能够发邮件报警
  • 5.如何实现一个随机IP代理库,目的跟第2点有点类似,都是为了反反爬虫

下面会针对这个系统来做一个整体的基本介绍,其实我在代码中都有非常详细的注释,有兴趣的朋友可以参考一下代码,最后我会给出一些我爬虫时的数据分析。

另外需要注意的是,这个爬虫系统是基于Java实现的,但是语言本身仍然不是最重要的,有兴趣的朋友可以尝试用Python实现。

2 分布式爬虫系统架构

整体系统架构如下:

分布式爬虫系统设计、实现与实战:爬取京东、苏宁易购全网手机商品数据+MySQL、HBase存储

所以从上面的架构可以看出,整个系统主要分为三个部分:

  • 爬虫系统
  • URL调度系统
  • 监控报警系统

爬虫系统就是用来爬取数据的,因为系统设计为分布式,因此,爬虫程序本身可以运行在不同的服务器节点上。

url调度系统核心在于url仓库,所谓的url仓库其实就是用Redis保存了需要爬取的url列表,并且在我们的url调度器中根据一定的策略来消费其中的url,从这个角度考虑,url仓库其实也是一个url队列。

监控报警系统主要是对爬虫节点进行监控,虽然并行执行的爬虫节点中的某一个挂掉了对整体数据爬取本身没有影响(只是降低了爬虫的速度),但是我们还是希望知道能够主动接收到节点挂掉的通知,而不是被动地发现。

下面将会针对以上三个方面并结合部分代码片段来对整个系统的设计思路做一些基本的介绍,对系统完整实现有浓厚兴趣的朋友可以直接参考源代码。

3 爬虫系统

分布式爬虫系统设计、实现与实战:爬取京东、苏宁易购全网手机商品数据+MySQL、HBase存储

(说明:zookeeper监控属于监控报警系统,url调度器属于URL调度系统)

爬虫系统是一个独立运行的进程,我们把我们的爬虫系统打包成jar包,然后分发到不同的节点上执行,这样并行爬取数据可以提高爬虫的效率。

3.1 随机IP代理器

加入随机IP代理主要是为了反反爬虫,因此如果有一个IP代理库,并且可以在构建http客户端时可以随机地使用不同的代理,那么对我们进行反反爬虫则会有很大的帮助。

在系统中使用IP代理库,需要先在文本文件中添加可用的代理地址信息:

# IPProxyRepository.txt
58.60.255.104:8118
219.135.164.245:3128
27.44.171.27:9999
219.135.164.245:3128
58.60.255.104:8118
58.252.6.165:9000
......

需要注意的是,上面的代理IP是我在西刺代理上拿到的一些代理IP,不一定可用,建议是自己花钱购买一批代理IP,这样可以节省很多时间和精力去寻找代理IP。

然后在构建http客户端的工具类中,当第一次使用工具类时,会把这些代理IP加载进内存中,加载到Java的一个HashMap:

// IP地址代理库Map
private static Map<String, Integer> IPProxyRepository = new HashMap<>();
private static String[] keysArray = null;   // keysArray是为了方便生成随机的代理对象

/**
     * 初次使用时使用静态代码块将IP代理库加载进set中
     */
static {
    InputStream in = HttpUtil.class.getClassLoader().getResourceAsStream("IPProxyRepository.txt");  // 加载包含代理IP的文本
    // 构建缓冲流对象
    InputStreamReader isr = new InputStreamReader(in);
    BufferedReader bfr = new BufferedReader(isr);
    String line = null;
    try {
        // 循环读每一行,添加进map中
        while ((line = bfr.readLine()) != null) {
            String[] split = line.split(":");   // 以:作为分隔符,即文本中的数据格式应为192.168.1.1:4893
            String host = split[0];
            int port = Integer.valueOf(split[1]);
            IPProxyRepository.put(host, port);
        }
        Set<String> keys = IPProxyRepository.keySet();
        keysArray = keys.toArray(new String[keys.size()]);  // keysArray是为了方便生成随机的代理对象
    } catch (IOException e) {
        e.printStackTrace();
    }

}

之后,在每次构建http客户端时,都会先到map中看是否有代理IP,有则使用,没有则不使用代理:

CloseableHttpClient httpClient = null;
HttpHost proxy = null;
if (IPProxyRepository.size() > 0) {  // 如果ip代理地址库不为空,则设置代理
    proxy = getRandomProxy();
    httpClient = HttpClients.custom().setProxy(proxy).build();  // 创建httpclient对象
} else {
    httpClient = HttpClients.custom().build();  // 创建httpclient对象
}
HttpGet request = new HttpGet(url); // 构建htttp get请求
......

随机代理对象则通过下面的方法生成:

/**
     * 随机返回一个代理对象
     *
     * @return
     */
public static HttpHost getRandomProxy() {
    // 随机获取host:port,并构建代理对象
    Random random = new Random();
    String host = keysArray[random.nextInt(keysArray.length)];
    int port = IPProxyRepository.get(host);
    HttpHost proxy = new HttpHost(host, port);  // 设置http代理
    return proxy;
}

这样,通过上面的设计,基本就实现了随机IP代理器的功能,当然,其中还有很多可以完善的地方,比如,当使用这个IP代理而请求失败时,是否可以把这一情况记录下来,当超过一定次数时,再将其从代理库中删除,同时生成日志供开发人员或运维人员参考,这是完全可以实现的,不过我就不做这一步功能了。

3.2 网页下载器

网页下载器就是用来下载网页中的数据,主要基于下面的接口开发:

/**
 * 网页数据下载
 */
public interface IDownload {
    /**
     * 下载给定url的网页数据
     * @param url
     * @return
     */
    public Page download(String url);
}

基于此,在系统中只实现了一个http get的下载器,但是也可以完成我们所需要的功能了:

/**
 * 数据下载实现类
 */
public class HttpGetDownloadImpl implements IDownload {

    @Override
    public Page download(String url) {
        Page page = new Page();
        String content = HttpUtil.getHttpContent(url);  // 获取网页数据
        page.setUrl(url);
        page.setContent(content);
        return page;
    }
}

3.3 网页解析器

网页解析器就是把下载的网页中我们感兴趣的数据解析出来,并保存到某个对象中,供数据存储器进一步处理以保存到不同的持久化仓库中,其基于下面的接口进行开发:

/**
 * 网页数据解析
 */
public interface IParser {
    public void parser(Page page);
}

网页解析器在整个系统的开发中也算是比较重头戏的一个组件,功能不复杂,主要是代码比较多,针对不同的商城不同的商品,对应的解析器可能就不一样了,因此需要针对特别的商城的商品进行开发,因为很显然,京东用的网页模板跟苏宁易购的肯定不一样,天猫用的跟京东用的也肯定不一样,所以这个完全是看自己的需要来进行开发了,只是说,在解析器开发的过程当中会发现有部分重复代码,这时就可以把这些代码抽象出来开发一个工具类了。

目前在系统中爬取的是京东和苏宁易购的手机商品数据,因此与就写了这两个实现类:

/**
 * 解析京东商品的实现类
 */
public class JDHtmlParserImpl implements IParser {
    ......
}

/**
 * 苏宁易购网页解析
 */
public class SNHtmlParserImpl implements IParser {
    ......
}

3.4 数据存储器

分布式爬虫系统设计、实现与实战:爬取京东、苏宁易购全网手机商品数据+MySQL、HBase存储

数据存储器主要是将网页解析器解析出来的数据对象保存到不同的,而对于本次爬取的手机商品,数据对象是下面一个Page对象:

/**
 * 网页对象,主要包含网页内容和商品数据
 */
public class Page {
    private String content;              
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值