框架升级 -- 增量爬虫设计原理及其实现

目标

  1. 理解增量式爬虫的原理
  2. 完成增量式爬虫的实现

1 增量爬虫设计原理

增量抓取,意即针对某个站点的数据抓取,当网站的新增数据或者该站点的数据发生了变化后,自动地抓取它新增的或者变化后的数据

设计原理:

框架升级 -- 增量爬虫设计原理及其实现_第1张图片

1.1 实现关闭请求去重

  1. 为Request对象增加属性filter
# scrapy/http/reqeust.py
'''封装Request对象'''


class Request(object):
    '''请求对象,设置请求信息'''

    def __init__(self, url, method='GET', headers=None, params=None, data=None, filter=True):
        self.url = url    # 请求地址
        self.method = method    # 请求方法
        self.headers = headers    # 请求头
        self.params = params    # 请求参数
        self.data = data    # 请求体
        self.filter = filter    # 是否进行去重,默认是True
  1. 修改调度器,进行判断
# scrapy_plus/core/scheduler.py
class Scheduler(object):

    ......

    def add_request(self, request):
        '''添加请求对象'''

        # 先判断是否要去重
        if request.filter is False:
            self.queue.put(request)
            logger.info("添加请求成功[%s %s]" % (request.method, request.url))
            self.total_request_number += 1  # 统计请求总数
            return # 必须return

        # 添加请求对象前,先进性去重判断
        fp = self._gen_fp(request)
        if not self.filter_request(fp, request):    # 如果指纹不存在,那么添加该请求
            self.queue.put(request)
            logger.info("添加请求成功[%s %s]"%(request.method, request.url))
            self._filter_container.add_fp(fp)     # 添加完请求后,将指纹也记录下来
            self.total_request_number += 1    # 统计请求总数
        else:
            logger.info("发现重复的请求 [%s %s]" % (request.method, request.url))
            self.repeat_request_number += 1

    ......

1.2 实现无限发起请求:

新增爬虫抓取:新浪滚动新闻

  1. 在start_reqeusts中改成无限循环,并设置对应请求为非去重模式。(注意)
# spiders/baidu.py
import time

from scrapy_plus.core.spider import Spider
from scrapy_plus.http.request import Request
from scrapy_plus.item import Item
import js2py


class SinaGunDong(Spider):

    name = "sina_gundong"

    def start_requests(self):
        while True:
            # 需要发起这个请求,才能获取到列表页数据,并且返回的是一个js语句
            url = "http://roll.news.sina.com.cn/interface/rollnews_ch_out_interface.php?col=89&spec=&type=&ch=&k=&offset_page=0&offset_num=0&num=120&asc=&page=1&r=0.5559616678192825"
            yield Request(url, parse='parse', filter=False)
            time.sleep(10)     # 每10秒发起一次请求

    def parse(self, response):
        '''响应体数据是js代码'''
        # 使用js2py模块,执行js代码,获取数据
        ret = js2py.eval_js(response.body.decode("gbk"))    # 对网站分析发现,数据编码格式是gbk的,因此需要先进行解码
        yield Item(ret.list)

但由于框架调用start_requests方法时同步,如果设置为死循环后,那么位于之后的爬虫的start_requests方法就不会被调用,因此需要在调用每个爬虫的start_reqeusts时设置为异步的

# scrapy_plus/core/engine.py
class Engine(object):

    ......

    def _start_requests(self):
        '''向调度器添加初始请求'''
        # 1. 爬虫模块发出初始请求
        # for spider_name, spider in self.spiders.items():
        #     for start_request in spider.start_requests():
        #         # 2. 把初始请求添加给调度器
        #         # 利用爬虫中间件预处理请求对象
        #         for spider_mid in self.spider_mids:
        #             start_request = spider_mid.process_request(start_request)
        #         start_request.spider_name = spider_name    #为请求对象绑定它所属的爬虫的名称
        #         self.scheduler.add_request(start_request)

        def _func(spider_name, spider):
            for start_request in spider.start_requests():
                # 2. 把初始请求添加给调度器
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    start_request = spider_mid.process_request(start_request)
                start_request.spider_name = spider_name    #为请求对象绑定它所属的爬虫的名称
                self.scheduler.add_request(start_request)
        # 1. 爬虫模块发出初始请求
        for spider_name, spider in self.spiders.items():
            self.pool.apply_async(_func, args=(spider_name, spider))    # 把执行每个爬虫的start_requests方法,设置为异步的

    ......

让程序的主线程在,多个start_reqeusts方法都没执行完毕前,不要进行退出判断,避免退出过早:

# scrapy_plus/core/engine.py
class Engine(object):
    '''
    负责驱动各大组件,通过调用各自对外提供的API接口,实现它们之间的交互和协作
    提供整个框架的启动入口
    '''
    def __init__(self):
        ......

        self.finshed_start_requests_number = 0

    ......

    def _callback_total_finshed_start_requests_number(self, temp):
        '''记录完成的start_requests的数量'''
        self.finshed_start_requests_number += 1

    def _start_requests(self):

        ......

        # 让主线程在这里阻塞
        while True:
            time.sleep(0.001)    # 节省cpu消耗
            # self.pool.apply_async(self._execute_request_response_item)    # 发起一个请求,处理一个响应
            # 设置退出循环的条件:
            # 当处理完的响应数等于总的请求数时,退出循环:
            if self.finshed_start_requests_number == len(self.spiders):    # 判断是否所有爬虫的start_requests是否都执行完毕,
                # 如果都执行完毕,才应该应该进行退出判断
                if self.total_response_number == self.scheduler.total_request_number and self.total_response_number != 0:
                    self.running = False    # 设为Flase, 让子线程满足判断条件,不再执行递归循环,然后退出
                    break
        logger.info("主线程循环已经退出")
        self.pool.close()   # 意味着无法再向pool添加任务,,无法在调用apply_async  apply
        self.pool.join()   #

你可能感兴趣的:(爬虫)