简易分布式爬虫系统汇报文档

779 阅读9分钟

一、项目介绍

本系统主要爬取豆瓣排名 TOP250 的电影,是一个借助Pycharm工具开发的简易分布式爬虫系统,分布式的实现主要依赖于scrapy-redis,主从分布式爬虫。redis数据库用作数据的持久化操作与消息队列,数据的存储采用MongoDB数据库,数据的可视化主要由Flask、Echarts、WordCloud等技术。

scrapy-redis: 是基于redis的scrapy组件,用于快速实现scrapy项目的分布式部署和数据爬取。

主从模式: 是由一台master服务器, 来提供url的分发, 维护待抓取url的list。由多台slave服务器执行网页抓取功能,slave所抽取的新url,一律由master来处理解析,而slave之间不需要做任何通信。

redis: 在内存中运行,它可以将抓取的网页内容存入到内存中,因此相对于从磁盘获取数据,redis可以大大提高爬虫爬取效率。

MongoDB: 基于分布式文件存储的数据库,支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。

flask: 由个Python实现的Web开发微框架。

echarts: 纯JavaScript的数据可视化图标库,兼容绝大部分的浏览器。

WordCloud: 绘制词云图。

二、项目分工

  • 前端:黄蓝、林佳妮
  • 后端:杨晨、祁欢、辛微
  • 爬虫:张玉东、韦景博、张朝阳

三、系统设计

3.1系统架构图及说明

python 分布式豆瓣电影数据抓取系统,为电影推荐系统做数据支持。分布式采用主从结构设置一个Master服务器和多个Slave服务器,Master端管理Redis数据库和分发下载任务,Slave部署Scrapy爬虫提取网页和解析提取数据,最后将解析的数据存储在同一个MongoDb数据库中。分布式爬虫架构如图所示。

架构图.png

  • master控制节点:负责管理所有slave,包括 slave 连接、任务调度、分发,维护爬取队列、 收取 salve 上传的数据,存储目标数据,新 URL 链接去重,新任务添加等、结果回收、汇总;
  • slave爬虫节点: 负责从 master 领取任务,并独自完成、本节点爬虫调度、数据抓取、HTML下载管理、数据处理、内容解析(解析包括目标数据和新的URL链接)、数据存储、上传结果;

3.2系统核心模块说明

1. 爬虫功能

  • 爬取策略的设计:由scrapy的结构分析可知,网络爬虫从初始地址开始,根据spider中定义的目标地址获的Xpath获得更多的网页链接,并加入到待下载队列当中,进行去重和排序之后,等待调度器的调度。在系统中,新的链接可以分为两类,一类是目录页链接,即下一页链接,一类是内容详情页链接,即需要解析网页提取字段的链接,指向的就是实际的电影信息页面。网络需从每一个目录页链接当中,提取到多个内容页链接,加入到待下载队列准备进一步爬取。

  • 去重与增量爬取:对服务器有重大意义,减少服务器的压力及保证数据的准确性。如果不采取去重处理,那么抓取的内容会抓取大量重复内容,让爬虫效率极大的下降。去重流程的核心就是,每次请求的时候,先判断这个请求是否在已经爬取的队列当中。如果已存在,则舍弃当前请求。

2. 中间件

访问一个网站网页时,会给网站带来一定负载,而爬虫程序则模拟了我们正常访问网页的过程,但大规模的爬虫会给网站增加大量的负载,影响正常用户的访问。因此,大多数网站都有相应的防爬虫策略。一旦访问行为被认定为爬虫,网站将会采取一定的措施,限制访问。中间件作为爬虫防屏蔽组件,可以实现反反爬虫。

  • 站内请求:本系统定向抓取网页数据的时候,将不间断的访问网站内容,如果不采取伪装措施,很容易被网站识别为爬虫行为而屏蔽掉。因此本系统在请求页面时,伪装成站内请求,用以反反爬虫。
  • IP代理:同一个IP针对一个网站短时间内大量的访问通常会导致IP被封,为解决这个问题,可以使用代理IP,解决IP被封的问题。
  • 动态User-Agent:设置随机 UA 来伪装请求头是一种常用的反反爬虫方式,这能够在一定程度上避免网站直接识别出爬虫,从而被封。

3. 数据存储

数据存储模块主要负责将slave端爬取解析的页面进行存储。使用Mongodb对数据进行存储。 Scrapy支持数据存储的格式有json,csv和xml等文本格式,用户可以在运行爬虫时设置,同时,Scrapy也支持数据库存储,如Monogdb,Redis等,当数据量大到一定程度时,可以做Mongodb或者Reids的集群来解决问题。

4. 数据可视化

数据的可视化即将数据库的数据转换成用户容易观察的形式,本系统使用Mongodb对数据进行存储,采用Flask、Echarts、WordCloud等技术实现可视化界面和可视化数据分析结果。

3.3系统各模块交互逻辑说明

爬虫模块获取数据,并对数据进行分析处理,中间件进行了反反爬虫的实现,确保爬虫过程的顺利进行。数据存储模块由数据库存储爬虫过程中获得的数据,在进行数据可视化时,从数据库中取出爬取到的数据进行分析。

3.4系统运行流程说明

1.爬虫过程说明

1.master将任务(未爬取的url)分发下去,slave通过master的URL管理器领取任务(url)并独自完成对应任务(url)的HTML内容下载、内容解析,解析出来的内容包含目标数据和新的url。

2.解析完成后slave将结果(目标数据及新的url)提交给master的数据提取进程(属于master的结果处理),该进程完成两个任务:

  • 提取出新的url交于url管理器
  • 提取目标数据交于数据存储进程

3.master的url管理进程收到url后进行验证(是否已爬取过)并处理(未爬取的添加进待爬url集合,爬过的添加进已爬url集合),然后slave循环从url管理器获取任务、执行任务、提交结果,不断循环

2.系统运行说明

1.打开redis数据库,运行slave下的spider,进行数据持久化操作。

2.运行master下的main.py,将数据存储进MongoDB。

3.运行FlaskProject下的app.py,进行数据处理,数据的可视化展示。

3.5分布式思想说明

为了解决Scrapy单机局限的问题,Scrapy结合Scrapy-Redis组件进行开发,Scrapy-Redis总体思路就是这个工程通过重写Scrapu框架中的scheduler和spider类,实现了调度、spider启动和redis的交互。因为每个主机上的爬虫进程都访问同一个redis数据库,所以调度和判重都统一进行统一管理,达到分布式爬虫的目的。

3.6核心代码实现说明

本系统采用 python 的 Scrapy 框架来开发,使用 Xpath 技术对下载的网页进行提取解析,运用 Redis 数据库做分布式,使用MongoDb 数据库做数据存储,利用 Flask、Echarts、WordCloud等技术对数据进行友好可视化处理。以下对核心代码进行说明(因篇幅限制,类似代码说明一部分)。

1、代码结构

  • FlaskProject:可视化界面实现代码
  • Master:主节点代码
  • proxy_pool:IP代理池
  • Slave:从节点代码

image.png

2、Slave

image.png

  • douban_redis.py
# 解析数据
def parse_info(self, response):
    pass
# 使用Xpath提取网页数据
page_url = response.url
title = response.xpath("//h1/span[@property='v:itemreviewed']/text()").extract_first()
year = response.xpath("//h1/span[@class='year']/text()").extract_first()
score = response.xpath("//strong[@class='ll rating_num']/text()").extract_first()
.......
# 返回数据
yield {
    "page_url":page_url,
    "title":title,
    "year":year,
    "score":score,
    "directedBy":directedBy,
    "actors":actors,
    ......
}
  • middlewares.py
# IP代理
class RandomProxyMiddleWare(object):

   # 创建中间件对象,默认代理启用
   @classmethod
   def from_crawler(cls, crawler):
       if not crawler.settings.getbool('HTTPPROXY_ENABLED'):
           raise NotConfigured
       return cls(crawler.settings)

    # 初始化配置及相关变量
    def __init__(self, settings):
        self.r = redis.Redis(host='localhost', port=6379, db=0)
        self.proxy_key = settings.get('PROXY_REDIS_KEY')
        self.max_failed = 1
    
    # 为每个request对象分配随机的ip代理
    def process_request(self, request, spider):
        if self.proxies and not request.meta.get('proxy'):
            proxies_list = self.proxies
            if proxies_list:
                request.meta['proxy'] = 'https://' + random.choice(proxies_list)
    # 请求成功处理   
    def process_response(self, request, response, spider):
        # proxy为空处理
        if not request.meta.get('proxy'):
            pass
        # ip是否被对方封禁处理
        if response.status in (400, 401, 403):
            pass
        # 某个IP的失败次数累积到一定的数量时的处理
        if int(filed_times) >= self.max_failed:
            pass
   
    # 请求失败处理
    def process_exception(self, request, exception, spider):
        cur_proxy = request.meta.get('proxy')
        # 请求使用代理,并且网络请求报错,认为该IP出错,删除,并重新->调度器
        if cur_proxy and isinstance(cur_proxy, (ConnectError, TimeoutError)):
            print('error (%s) occur when use proxy %s' % (exception, cur_proxy))
            self.remove_proxy(cur_proxy)
            del request.meta['proxy']
            return request
    ......
# 动态User-Agent
class UserAgentMiddleware(object):
    def process_request(self, request, spider):
        request.headers.setdefault(b'User-Agent', UserAgent().random)
    ......
#  反反爬虫
class MoviesDownloaderMiddleware:
    def process_request(self, request, spider):
        referer = request.url
        if referer:
            request.headers['referer'] = referer
        return None
    ......
  • pipelines.py
# 操作数据库
class MongoMoviesPipeline:
    pass
# 对图片数据进行处理
class ImagePipeline(ImagesPipeline):
    pass
  • settings.py
# url指纹过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 持久化存储
SCHEDULER_PERSIST = True
# 配置redis管道文件,权重数字相对最大
ITEM_PIPELINES = {
    # redis管道文件,自动把数据加载到redis
    'scrapy_redis.pipelines.RedisPipeline': 300,
}
# redis 连接配置
REDIS_HOST = '192.168.108.1'
REDIS_PORT = '6379'
# 激活下载器中间件组件
DOWNLOADER_MIDDLEWARES = {
   'movies.middlewares.MoviesDownloaderMiddleware': 543,
}

3、Master

image.png

  • main.py
def main():
    ......
    # 从MongoDB中取数据
    collection = db.douban
    while True:
        #  从redis中取数据
        source, data = r.blpop(["douban_redis:items"])
        item = json.loads(data)
        # 根据过滤条件替换数据
        collection.replace_one(filter={"rank":item["rank"]},replacement=item,upsert=True)

4、FlaskProject

image.png

  • app.py
# 从数据库获取数据
def myCollection():
    pass
# 首页
def index():
    pass
# 爬取电影数据列表展示页
def movie():
    pass
# 词频统计展示页
def word():
    pass
# 数据分析展示页
def score():
    pass

四、测试结果

4.1、pycharm代码运行结果

1.slave运行

pycharm:scrapy crawl douban_redis image.png redis-cli:lpush douban:start_urls movie.douban.com/top250?star…

image.png

2.master运行

image.png

4.2、MongoDB数据库数据

image.png

image.png

image.png

4.3、redis数据

image.png

4.4、数据展示结果

Running on http://127.0.0.1:5000

image.png

image.png

image.png

image.png

五、项目代码仓库地址

github.com/CoderDon/Cr…