框架升級 -- 增量爬蟲設計原理及其實現
阿新 • • 發佈:2018-12-27
目標
- 理解增量式爬蟲的原理
- 完成增量式爬蟲的實現
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
- 修改排程器,進行判斷
# 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 實現無限發起請求:
新增爬蟲抓取:新浪滾動新聞
- 在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() #