1. 程式人生 > >框架升級 -- 增量爬蟲設計原理及其實現

框架升級 -- 增量爬蟲設計原理及其實現

目標

  1. 理解增量式爬蟲的原理
  2. 完成增量式爬蟲的實現

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("新增請求成功<disable去重>[%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()   #