您好,登錄后才能下訂單哦!
講師的博客:https://www.cnblogs.com/wupeiqi/p/6912807.html
scrapy-redis是一個(gè)基于redis的scrapy組件,通過(guò)它可以快速實(shí)現(xiàn)簡(jiǎn)單分布式爬蟲(chóng)程序,該組件本質(zhì)上提供了三大功能:
安裝模塊
pip install scrapy-redis
創(chuàng)建爬蟲(chóng)應(yīng)用
項(xiàng)目就不重新創(chuàng)建了,直接在之前Scrapy課程的項(xiàng)目里,再創(chuàng)建一個(gè)新的應(yīng)用:
> cd PeppaScrapy
> scrapy genspider [項(xiàng)目名稱(chēng)] [起始url]
通過(guò)環(huán)境變量指定配置文件
之前的課程上,已經(jīng)對(duì)配置文件做了一些設(shè)置了。這里既不想把之前的內(nèi)容覆蓋掉,也不想受到之前配置的影響。
可以通過(guò)命令行的-s參數(shù)或者是在類(lèi)里寫(xiě)一個(gè)名稱(chēng)為custom_settings的字典,一個(gè)一個(gè)參數(shù)的進(jìn)行設(shè)置。這兩個(gè)的優(yōu)先級(jí)都很高。但是都不是配置文件的形式,這里可以單獨(dú)再寫(xiě)一個(gè)配置文件,然后通過(guò)設(shè)置系統(tǒng)環(huán)境變量的方式來(lái)指定爬蟲(chóng)應(yīng)用加載的配置文件。
在原來(lái)的settings.py的同級(jí)目錄里創(chuàng)建一個(gè)mysettings.py的配置文件,文件可以在任意位置,只要能配python導(dǎo)入。然后設(shè)置一個(gè)系統(tǒng)環(huán)境變量即可:
import os
os.environ.setdefault('SCRAPY_SETTINGS_MODULE', 'PeppaScrapy.mysettings')
把設(shè)置都寫(xiě)在配置文件中即可:
# redis 配置文件
REDIS_HOST = 'localhost' # 主機(jī)名
REDIS_PORT = 6379 # 端口
# REDIS_URL = 'redis://user:pass@hostname:9001' # 連接URL,和上面2條一樣,也是連接redis的設(shè)置,優(yōu)先使用這個(gè)
# REDIS_PARAMS = {} # Redis連接參數(shù),這里有一些默認(rèn)值,可以去源碼里看
# REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定連接Redis的Python模塊,默認(rèn):redis.StrictRedis
# REDIS_ENCODING = "utf-8" # redis編碼類(lèi)型,默認(rèn):'utf-8'
連接參數(shù)
這里提供兩種方式連接,一種是前2個(gè)設(shè)置,指定HOST和PORT。如果還需要用戶(hù)名和密碼就沒(méi)辦法了。
另一種就是用一個(gè)REDIS_URL參數(shù),按照上面的格式把所有的信息填好。另外這里REDIS_URL參數(shù)的優(yōu)先級(jí)高,就是說(shuō)如果設(shè)置了REDIS_URL參數(shù),那么上面的2個(gè)參數(shù)就沒(méi)有效果了。這個(gè)邏輯可以在scrapy_redis.connection.py里的get_redis函數(shù)里找到。
其他連接參數(shù)
REDIS_PARAMS,是連接redis時(shí)使用的其他參數(shù)。所以其實(shí)用戶(hù)名,密碼也是可以寫(xiě)在這里面的。即使什么都不寫(xiě),scrapy-redis模塊本身也設(shè)置了一些默認(rèn)值,可以在scrapy_redis.defaults.py里找到:
REDIS_PARAMS = {
'socket_timeout': 30,
'socket_connect_timeout': 30,
'retry_on_timeout': True,
'encoding': REDIS_ENCODING, // 這個(gè)值默認(rèn)是'utf-8',也在這個(gè)文件里
}
具體還可以設(shè)置哪些參數(shù),就是看連接Redis的那個(gè)類(lèi)的構(gòu)造函數(shù)了,源碼里就是用這個(gè)字典直接**kwargs創(chuàng)建對(duì)象了。默認(rèn)的用來(lái)連接Redis的模塊是redis.StrictRedis,下面是這個(gè)類(lèi)的構(gòu)造函數(shù)的參數(shù)列表:
def __init__(self, host='localhost', port=6379,
db=0, password=None, socket_timeout=None,
socket_connect_timeout=None,
socket_keepalive=None, socket_keepalive_options=None,
connection_pool=None, unix_socket_path=None,
encoding='utf-8', encoding_errors='strict',
charset=None, errors=None,
decode_responses=False, retry_on_timeout=False,
ssl=False, ssl_keyfile=None, ssl_certfile=None,
ssl_cert_reqs='required', ssl_ca_certs=None,
max_connections=None):
REDIS_ENCODING,是指定編碼類(lèi)型,默認(rèn)utf-8沒(méi)太多要說(shuō)的。
用于連接redis的所有參數(shù),除了以下4個(gè)是單獨(dú)寫(xiě)的,其他的都寫(xiě)在REDIS_PARAMS這個(gè)字典里。按照上面構(gòu)造函數(shù)里的變量名稱(chēng)寫(xiě)。這4個(gè)可以單獨(dú)寫(xiě)的,其實(shí)也就是做了一步映射而已:
# 這個(gè)也是 scrapy_redis.connection.py 里的源碼
SETTINGS_PARAMS_MAP = {
'REDIS_URL': 'url',
'REDIS_HOST': 'host',
'REDIS_PORT': 'port',
'REDIS_ENCODING': 'encoding',
}
REDIS_PARAMS['redis_cls'],指定連接Redis的Python模塊。按上面說(shuō)的,處理那4個(gè)參數(shù),其他的都只能寫(xiě)在REDIS_PARAMS這個(gè)字典里。
源碼文件
上面這些主要是翻了3個(gè)文件里的源碼:
模塊提供了一個(gè)使用redis做去重的規(guī)則,只需要按之前在Scrapy課程里學(xué)的,設(shè)置自定的去重規(guī)則即可。具體就是加上一條配置:
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
url去重算法
這里的去重規(guī)則,會(huì)先把url轉(zhuǎn)換成唯一標(biāo)識(shí),然后再存到集合里。
源碼里會(huì)把url通過(guò)hashlib進(jìn)行哈希運(yùn)算,用的是SHA1·。把原本的字符串轉(zhuǎn)成數(shù)字簽名。這么做的好處就是,即使url會(huì)很長(zhǎng),但是生成的數(shù)字簽名的長(zhǎng)度是固定的。這里可以注意下這個(gè)技巧,把url存儲(chǔ)在redis里的時(shí)候,占用的長(zhǎng)度是固定的,關(guān)鍵是不會(huì)太長(zhǎng)。
另外這個(gè)轉(zhuǎn)換過(guò)程還有個(gè)特點(diǎn)。如果url里是帶get參數(shù)的,如果參數(shù)的值是一樣的,但是出現(xiàn)的順序不同,轉(zhuǎn)換后生成的唯一標(biāo)識(shí)也是一樣的。比如像下面這樣:
url1 = 'https://www.baidu.com/s?wd=sha1&ie=utf-8'
url2 = 'https://www.baidu.com/s?ie=utf-8&wd=sha1'
像這種,只是參數(shù)的先后順序不同,兩個(gè)請(qǐng)求應(yīng)該還是同一個(gè)請(qǐng)求,應(yīng)該要去重。這里在這種情況下生成的唯一標(biāo)識(shí)是一樣的,所以可以做到去重。
這個(gè)去重規(guī)則實(shí)現(xiàn)了去重的算法,是要被調(diào)度器使用的。在去重的這個(gè)類(lèi)里,通過(guò)request_seen這個(gè)方法來(lái)判斷是否有重復(fù)。下面的調(diào)度器里就會(huì)調(diào)用這個(gè)方法來(lái)判斷避免重復(fù)爬取。
在配置文件里,加上下面的配置來(lái)指定調(diào)度器,這個(gè)是scrapy讀取的配置:
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
接下來(lái)出一些在scrapy-redis模塊里要讀取的配置。其中部分在scrapy_redis.defaults.py里有默認(rèn)設(shè)置,還有的在代碼里也有默認(rèn)的邏輯,所以配置文件里可以什么都不寫(xiě),需要改變?cè)O(shè)置的話(huà)也可以自己在配置文件里指定。
使用的隊(duì)列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
指定調(diào)度器存取請(qǐng)求使用的隊(duì)列類(lèi)型,有3個(gè)可選的:
請(qǐng)求存放在redis中的key
SCHEDULER_QUEUE_KEY = '%(spider)s:requests'
這樣,不同的爬蟲(chóng)應(yīng)用在redis中可以使用不同的key存取請(qǐng)求。
對(duì)保存到redis中的數(shù)據(jù)進(jìn)行序列化
SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"
這個(gè)設(shè)置在defaults里沒(méi)有,如果沒(méi)有設(shè)置,就用 scrapy_redis.picklecompat 這個(gè)。而底層用的還是pickle模塊。這樣可以把對(duì)象序列化之后在redis中保存起來(lái)。
清空調(diào)度器和去重記錄
下面的2個(gè)參數(shù)都是布爾型的,如果不設(shè)置就是False,如果需要開(kāi)啟,就設(shè)置為T(mén)rue:
SCHEDULER_PERSIST = True # 是否在關(guān)閉時(shí)候保留,調(diào)度器和去重記錄
SCHEDULER_FLUSH_ON_START = True # 是否在開(kāi)始之前清空,調(diào)度器和去重記錄
上面兩個(gè)參數(shù)的效果都是判斷后決定是否要執(zhí)行調(diào)度器的一個(gè)flush方法。而這個(gè)flush方法里執(zhí)行的則是清空調(diào)度器和去重記錄。
注意這兩個(gè)參數(shù)的效果。默認(rèn)都是False,就是在開(kāi)始之前不清空調(diào)度器和去重記錄,在結(jié)束的時(shí)候也不保留調(diào)度器和去重記錄。測(cè)試的話(huà)一般不需要保留。如果是上線使用,一般需要保留調(diào)度器和去重記錄,那么就按下面來(lái)設(shè)置:
# 保留調(diào)度器和去重記錄的設(shè)置
SCHEDULER_PERSIST = True
SCHEDULER_FLUSH_ON_START = False
獲取請(qǐng)求時(shí)等待的時(shí)間
調(diào)度器獲取請(qǐng)求時(shí),如果數(shù)據(jù)為空,進(jìn)行等待的時(shí)間,默認(rèn)為0,單位秒:
SCHEDULER_IDLE_BEFORE_CLOSE = 0
這個(gè)實(shí)際就是redis的blpop和brpop操作時(shí)的timeout參數(shù),默認(rèn)為0。如果為0,就使用lpop和rpop這2個(gè)不阻塞的pop方法。如果大于0,就使用阻塞的pop方法。這里不會(huì)用到timeout為0的阻塞pop,所以不會(huì)一直阻塞,總會(huì)返回的。無(wú)論阻塞還是不阻塞,取不到值就返回None。
另外,調(diào)度器默認(rèn)用的是有序集合,redis的有序集合取值沒(méi)有阻塞也沒(méi)有timeout,所以這個(gè)值是無(wú)效的。
去重規(guī)則的參數(shù)
下面2個(gè)是去重規(guī)則使用的參數(shù),其中一個(gè)在去重的時(shí)候講過(guò)了。另一個(gè)就是在redis里使用的key:
SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重規(guī)則在redis中保存時(shí)對(duì)應(yīng)的key
SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 去重規(guī)則對(duì)應(yīng)處理的類(lèi)
模塊還提供了數(shù)據(jù)持久化,在scrapy的配置里指定好數(shù)據(jù)持久化使用的類(lèi):
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
}
# 下面是兩個(gè)持久化時(shí)可以定制的配置
# PIPELINE_KEY = '%(spider)s:items'
# REDIS_ITEMS_SERIALIZER = "scrapy.utils.serialize.ScrapyJSONEncoder"
先去看看這個(gè)類(lèi)里具體做了些什么:
from twisted.internet.threads import deferToThread
class RedisPipeline(object):
def process_item(self, item, spider):
return deferToThread(self._process_item, item, spider)
def _process_item(self, item, spider):
key = self.item_key(item, spider)
data = self.serialize(item)
self.server.rpush(key, data)
return item
先看process_item方法,返回一個(gè)deferToThread,在twisted里的相當(dāng)于拿一個(gè)線程來(lái)做一個(gè)事。具體twisted干了啥就忽略把,主要是處理_process_item這個(gè)方法。所以真正做的處理是在_process_item方法里。
這里先把item的數(shù)據(jù)通過(guò)self.serialize函數(shù)做序列化,然后就是下面的rpush存到redis里去了。這里有2個(gè)可以定制的參數(shù),一個(gè)是序列化的方法,一個(gè)是存儲(chǔ)的redis里使用的Key。
默認(rèn)使用下面的Key,當(dāng)然可以在配置文件里指定:
PIPELINE_KEY = '%(spider)s:items'
默認(rèn)使用的序列化的方法是一個(gè)做了一些定制的json方法,在下面這里:
from scrapy.utils.serialize import ScrapyJSONEncoder
也是可以通過(guò)參數(shù)來(lái)指定自己的序列化方法的:
REDIS_ITEMS_SERIALIZER = "scrapy.utils.serialize.ScrapyJSONEncoder"
之前在spiders文件夾下寫(xiě)爬蟲(chóng)的時(shí)候,都是繼承scrapy.Spider然后寫(xiě)自己的類(lèi)。
模塊也提供了一個(gè)它自己的類(lèi),可以直接繼承模塊提供的這個(gè)類(lèi)來(lái)寫(xiě)爬蟲(chóng):
from scrapy_redis.spiders import RedisSpider
class TestSpider(RedisSpider):
name = 'jandan'
allowed_domains = ['jandan.net']
用了模塊的爬蟲(chóng)后,起始url也可以直接從redis里獲取了,相關(guān)的配置有下面2個(gè):
START_URLS_KEY = '%(name)s:start_urls'
START_URLS_AS_SET = False
第一個(gè)設(shè)置,是在redis里用來(lái)存儲(chǔ)起始url的key,只有通過(guò)這個(gè)key才能從redis里獲取到起始的url。
第二個(gè)設(shè)置,是指定在redis里使用是否使用集合來(lái)存儲(chǔ)起始url,默認(rèn)不用集合,用的就是列表。
使用模塊提供的這個(gè)爬蟲(chóng),會(huì)一直運(yùn)行,永遠(yuǎn)不會(huì)停止。沒(méi)有任務(wù)的話(huà)應(yīng)該就是一直等著直到獲取到新的任務(wù)??梢酝鵵edis對(duì)應(yīng)的起始url的key里添加數(shù)據(jù),就會(huì)自動(dòng)的開(kāi)始爬蟲(chóng)。適合在線上使用。
而scrapy模塊提供的scrapy.Spider這個(gè)爬蟲(chóng)適合平時(shí)自己用,爬完就結(jié)束了。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。