1. 程式人生 > 其它 >Zookeeper面試題(未完待續...)

Zookeeper面試題(未完待續...)

一 介紹

原來scrapy的Scheduler維護的是本機的任務佇列(存放Request物件及其回撥函式等資訊)+本機的去重佇列(存放訪問過的url地址)

所以實現分散式爬取的關鍵就是,找一臺專門的主機上執行一個共享的佇列比如Redis,
然後重寫Scrapy的Scheduler,讓新的Scheduler到共享佇列存取Request,並且去除重複的Request請求,所以總結下來,實現分散式的關鍵就是三點:

#1、共享佇列
#2、重寫Scheduler,讓其無論是去重還是任務都去訪問共享佇列
#3、為Scheduler定製去重規則(利用redis的集合型別)

以上三點便是scrapy-redis元件的核心功能

#安裝:
pip3 install scrapy-redis

#原始碼:
D:\python3.6\Lib\site-packages\scrapy_redis

二、scrapy-redis元件

1、只使用scrapy-redis的去重功能

#一、原始碼:D:\python3.6\Lib\site-packages\scrapy_redis\dupefilter.py



#二、配置scrapy使用redis提供的共享去重佇列

#2.1 在settings.py中配置連結Redis
REDIS_HOST = 'localhost'                            # 主機名
REDIS_PORT = 6379                                   # 埠
REDIS_URL = 'redis://user:pass@hostname:9001'       # 連線URL(優先於以上配置)
REDIS_PARAMS  = {}                                  # Redis連線引數
REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定連線Redis的Python模組
REDIS_ENCODING = "utf-8"                            # redis編碼型別  
# 預設配置:D:\python3.6\Lib\site-packages\scrapy_redis\defaults.py

#2.2 讓scrapy使用共享的去重佇列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
#使用scrapy-redis提供的去重功能,檢視原始碼會發現是基於Redis的集合實現的

#2.3、需要指定Redis中集合的key名,key=存放不重複Request字串的集合
DUPEFILTER_KEY = 'dupefilter:%(timestamp)s'
#原始碼:dupefilter.py內一行程式碼key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())}

#2.4、去重規則原始碼分析dupefilter.py
def request_seen(self, request):
    """Returns True if request was already seen.

```
Parameters
----------
request : scrapy.http.Request

Returns
-------
bool

"""
fp = self.request_fingerprint(request) 
# This returns the number of values added, zero if already exists.
added = self.server.sadd(self.key, fp)
return added == 0
```

#2.5、將request請求轉成一串字元後再存入集合

from scrapy.http import Request
from scrapy.utils.request import request_fingerprint

req = Request(url='http://www.baidu.com')
result=request_fingerprint(req)
print(result) #75d6587d87b3f4f3aa574b33dbd69ceeb9eafe7b

#2.6、注意:
    - URL引數位置不同時,計算結果一致;
    - 預設請求頭不在計算範圍,include_headers可以設定指定請求頭
    - 示範:
    from scrapy.utils import request
    from scrapy.http import Request
     

```
req = Request(url='http://www.baidu.com?name=8&id=1',callback=lambda x:print(x),cookies={'k1':'vvvvv'})
result1 = request.request_fingerprint(req,include_headers=['cookies',])
 
print(result)
 
req = Request(url='http://www.baidu.com?id=1&name=8',callback=lambda x:print(x),cookies={'k1':666})
 
result2 = request.request_fingerprint(req,include_headers=['cookies',])
 
print(result1 == result2) #True

2、使用scrapy-redis的去重+排程實現分散式爬取

#1、原始碼:D:\python3.6\Lib\site-packages\scrapy_redis\scheduler.py



#2、settings.py配置

# Enables scheduling storing requests queue in redis.
SCHEDULER = "scrapy_redis.scheduler.Scheduler"       

# 排程器將不重複的任務用pickle序列化後放入共享任務佇列,預設使用優先順序佇列(預設),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表)               
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'          

# 對儲存到redis中的request物件進行序列化,預設使用pickle
SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"   

# 排程器中請求任務序列化後存放在redis中的key               
SCHEDULER_QUEUE_KEY = '%(spider)s:requests'    

# 是否在關閉時候保留原來的排程器和去重記錄,True=保留,False=清空                     
SCHEDULER_PERSIST = True       

# 是否在開始之前清空 排程器和去重記錄,True=清空,False=不清空                                     
SCHEDULER_FLUSH_ON_START = False    

# 去排程器中獲取資料時,如果為空,最多等待時間(最後沒資料,未獲取到)。如果沒有則立刻返回會造成空迴圈次數過多,cpu佔用率飆升                                
SCHEDULER_IDLE_BEFORE_CLOSE = 10           

# 去重規則,在redis中儲存時對應的key                         
SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter'      

# 去重規則對應處理的類,將任務request_fingerprint(request)得到的字串放入去重佇列            
SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'

3、持久化

#從目標站點獲取並解析出資料後儲存成item物件,會由引擎交給pipeline進行持久化/儲存到資料庫,scrapy-redis提供了一個pipeline元件,可以幫我們把item存到redis中

#1、將item持久化到redis時,指定key和序列化函式 
REDIS_ITEMS_KEY = '%(spider)s:items'
REDIS_ITEMS_SERIALIZER = 'json.dumps'

#2、使用列表儲存item資料

4、從Redis中獲取起始URL

scrapy程式爬取目標站點,一旦爬取完畢後就結束了,如果目標站點更新內容了,我們想重新爬取,那麼只能再重新啟動scrapy,非常麻煩
scrapy-redis提供了一種供,讓scrapy從redis中獲取起始url,如果沒有scrapy則過一段時間再來取而不會關閉
這樣我們就只需要寫一個簡單的指令碼程式,定期往redis佇列裡放入一個起始url。

#具體配置如下

#1、編寫爬蟲時,起始URL從redis的Key中獲取
REDIS_START_URLS_KEY = '%(name)s:start_urls'
    
#2、獲取起始URL時,去集合中獲取還是去列表中獲取?True,集合;False,列表
REDIS_START_URLS_AS_SET = False    # 獲取起始URL時,如果為True,則使用self.server.spop;如果為False,則使用self.server.lpop