python爬蟲(chóng)之Scrapy_Redis分布式爬蟲(chóng)

為甚要學(xué)習(xí)scrapy_redis吨岭??

Scrapy_redis在scrapy的基礎(chǔ)上實(shí)現(xiàn)了更多峦树,更強(qiáng)大的功能辣辫,具體體現(xiàn)在:reqeust去重,爬蟲(chóng)持久化魁巩,和輕松實(shí)現(xiàn)分布式

pip3 install scrapy-redis

Scrapy-redis提供了下面四種組件(components):(四種組件意味著這四個(gè)模塊都要做相應(yīng)的修改)

  • Scheduler
  • Duplication Filter
  • Item Pipeline
  • Base Spider

Scrapy_redis是工作流程

圖片1

Scheduler

Scrapy改造了python本來(lái)的collection.deque(雙向隊(duì)列)形成了自己的Scrapy queue(https://github.com/scrapy/queuelib/blob/master/queuelib/queue.py))急灭,但是Scrapy多個(gè)spider不能共享待爬取隊(duì)列Scrapy queue, 即Scrapy本身不支持爬蟲(chóng)分布式谷遂,scrapy-redis 的解決是把這個(gè)Scrapy queue換成redis數(shù)據(jù)庫(kù)(也是指redis隊(duì)列)葬馋,從同一個(gè)redis-server存放要爬取的request,便能讓多個(gè)spider去同一個(gè)數(shù)據(jù)庫(kù)里讀取。
Scrapy中跟“待爬隊(duì)列”直接相關(guān)的就是調(diào)度器Scheduler点楼,它負(fù)責(zé)對(duì)新的request進(jìn)行入列操作(加入Scrapy queue)扫尖,取出下一個(gè)要爬取的request(從Scrapy queue中取出)等操作。它把待爬隊(duì)列按照優(yōu)先級(jí)建立了一個(gè)字典結(jié)構(gòu)掠廓,比如:

    {
        優(yōu)先級(jí)0 : 隊(duì)列0
        優(yōu)先級(jí)1 : 隊(duì)列1
        優(yōu)先級(jí)2 : 隊(duì)列2
    }

然后根據(jù)request中的優(yōu)先級(jí)换怖,來(lái)決定該入哪個(gè)隊(duì)列,出列時(shí)則按優(yōu)先級(jí)較小的優(yōu)先出列蟀瞧。為了管理這個(gè)比較高級(jí)的隊(duì)列字典沉颂,Scheduler需要提供一系列的方法。但是原來(lái)的Scheduler已經(jīng)無(wú)法使用悦污,所以使用Scrapy-redis的scheduler組件铸屉。

Duplication Filter

Scrapy中用集合實(shí)現(xiàn)這個(gè)request去重功能,Scrapy中把已經(jīng)發(fā)送的request指紋放入到一個(gè)集合中切端,把下一個(gè)request的指紋拿到集合中比對(duì)彻坛,如果該指紋存在于集合中,說(shuō)明這個(gè)request發(fā)送過(guò)了踏枣,如果沒(méi)有則繼續(xù)操作昌屉。這個(gè)核心的判重功能是這樣實(shí)現(xiàn)的:

    def request_seen(self, request):
        # 把請(qǐng)求轉(zhuǎn)化為指紋  
        fp = self.request_fingerprint(request)
        # 這就是判重的核心操作  ,self.fingerprints就是指紋集合
        if fp in self.fingerprints:
            return True  #直接返回
        self.fingerprints.add(fp) #如果不在茵瀑,就添加進(jìn)去指紋集合
        if self.file:
            self.file.write(fp + os.linesep)

在scrapy-redis中去重是由Duplication Filter組件來(lái)實(shí)現(xiàn)的间驮,它通過(guò)redis的set 不重復(fù)的特性,巧妙的實(shí)現(xiàn)了Duplication Filter去重马昨。scrapy-redis調(diào)度器從引擎接受request竞帽,將request的指紋存?redis的set檢查是否重復(fù),并將不重復(fù)的request push寫(xiě)?redis的 request queue鸿捧。
引擎請(qǐng)求request(Spider發(fā)出的)時(shí)屹篓,調(diào)度器從redis的request queue隊(duì)列?里根據(jù)優(yōu)先級(jí)pop 出?個(gè)request 返回給引擎,引擎將此request發(fā)給spider處理匙奴。

Item Pipeline:

引擎將(Spider返回的)爬取到的Item給Item Pipeline堆巧,scrapy-redis 的Item Pipeline將爬取到的 Item 存?redis的 items queue。
修改過(guò)Item Pipeline可以很方便的根據(jù) key 從 items queue 提取item饥脑,從?實(shí)現(xiàn) items processes集群。

Base Spider

不在使用scrapy原有的Spider類(lèi)懦冰,重寫(xiě)的RedisSpider繼承了Spider和RedisMixin這兩個(gè)類(lèi)灶轰,RedisMixin是用來(lái)從redis讀取url的類(lèi)。
當(dāng)我們生成一個(gè)Spider繼承RedisSpider時(shí)刷钢,調(diào)用setup_redis函數(shù)笋颤,這個(gè)函數(shù)會(huì)去連接redis數(shù)據(jù)庫(kù),然后會(huì)設(shè)置signals(信號(hào)):
一個(gè)是當(dāng)spider空閑時(shí)候的signal,會(huì)調(diào)用spider_idle函數(shù)伴澄,這個(gè)函數(shù)調(diào)用schedule_next_request函數(shù)赋除,保證spider是一直活著的狀態(tài),并且拋出DontCloseSpider異常非凌。
一個(gè)是當(dāng)抓到一個(gè)item時(shí)的signal举农,會(huì)調(diào)用item_scraped函數(shù),這個(gè)函數(shù)會(huì)調(diào)用schedule_next_request函數(shù)敞嗡,獲取下一個(gè)request颁糟。

源碼自帶項(xiàng)目說(shuō)明:

使用scrapy-redis的example來(lái)修改 先從github上拿到scrapy-redis的示例,然后將里面的example-project目錄移到指定的地址:

clone github scrapy-redis源碼文件

git clone https://github.com/rolando/scrapy-redis.git

直接拿官方的項(xiàng)目范例喉悴,改名為自己的項(xiàng)目用(針對(duì)懶癌患者)

mv scrapy-redis/example-project ~/scrapyredis-project

我們clone到的 scrapy-redis 源碼中有自帶一個(gè)example-project項(xiàng)目棱貌,這個(gè)項(xiàng)目包含3個(gè)spider,分別是dmoz, myspider_redis箕肃,mycrawler_redis婚脱。
一、dmoz (class DmozSpider(CrawlSpider))
注意:這里只是用到Redis的去重和保存功能,并沒(méi)有實(shí)現(xiàn)分布式
這個(gè)爬蟲(chóng)繼承的是CrawlSpider勺像,它是用來(lái)說(shuō)明Redis的持續(xù)性障贸,當(dāng)我們第一次運(yùn)行dmoz爬蟲(chóng),然后Ctrl + C停掉之后咏删,再運(yùn)行dmoz爬蟲(chóng)惹想,之前的爬取記錄是保留在Redis里的。
分析起來(lái)督函,其實(shí)這就是一個(gè) scrapy-redis 版 CrawlSpider 類(lèi)嘀粱,需要設(shè)置Rule規(guī)則,以及callback不能寫(xiě)parse()方法辰狡。 執(zhí)行方式:

scrapy crawl dmoz
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class DmozSpider(CrawlSpider):
    """Follow categories and extract links."""
    name = 'dmoz'
    allowed_domains = ['dmoz.org']
    start_urls = ['http://www.dmoz.org/']
   #定義了一個(gè)url的提取規(guī)則锋叨,將滿(mǎn)足條件的交給callback函數(shù)處理
    rules = [
        Rule(LinkExtractor(
            restrict_css=('.top-cat', '.sub-cat', '.cat-item')
        ), callback='parse_directory', follow=True),
    ]
    def parse_directory(self, response):
        for div in response.css('.title-and-desc'):
          #這里將獲取到的內(nèi)容交給引擎
            yield {
                'name': div.css('.site-title::text').extract_first(),
                'description': div.css('.site-descr::text').extract_first().strip(),
                'link': div.css('a::attr(href)').extract_first(),
            }

二、myspider_redis (class MySpider(RedisSpider))
這個(gè)爬蟲(chóng)繼承了RedisSpider宛篇, 它能夠支持分布式的抓取娃磺,采用的是basic spider,需要寫(xiě)parse函數(shù)叫倍。 其次就是不再有start_urls了偷卧,取而代之的是redis_key,scrapy-redis將key從Redis里pop出來(lái)吆倦,成為請(qǐng)求的url地址听诸。

from scrapy_redis.spiders import RedisSpider
class MySpider(RedisSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'myspider_redis'
    #手動(dòng)設(shè)置允許爬取的域
    allowed_domains = ['設(shè)置允許爬取的域']
    # 注意redis-key的格式:
    redis_key = 'myspider:start_urls'
    # 可選:等效于allowd_domains(),__init__方法按規(guī)定格式寫(xiě)蚕泽,使用時(shí)只需要修改super()里的類(lèi)名參數(shù)即可,一般不用
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        # 修改這里的類(lèi)名為當(dāng)前類(lèi)名
        super(MySpider, self).__init__(*args, **kwargs)
    def parse(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意: RedisSpider類(lèi) 不需要寫(xiě)start_urls:

  • scrapy-redis 一般直接寫(xiě)allowd_domains來(lái)指定需要爬取的域晌梨,也可以從在構(gòu)造方法init()里動(dòng)態(tài)定義爬蟲(chóng)爬取域范圍(一般不用)。
  • 必須指定redis_key,即啟動(dòng)爬蟲(chóng)的命令仔蝌,參考格式:redis_key = 'myspider:start_urls'
  • 根據(jù)指定的格式泛领,start_urls將在 Master端的 redis-cli 里 lpush 到 Redis數(shù)據(jù)庫(kù)里,RedisSpider 將在數(shù)據(jù)庫(kù)里獲取start_urls敛惊。
    執(zhí)行方式:
  • 1.通過(guò)runspider方法執(zhí)行爬蟲(chóng)的py文件(也可以分次執(zhí)行多條)渊鞋,爬蟲(chóng)(們)將處于等待準(zhǔn)備狀態(tài):
scrapy runspider myspider_redis.py

或者

scrapy crawl myspider_redis
  • 2.在Master端的redis-cli輸入push指令,參考格式(指定起始url):
lpush myspider:start_urls http://www.dmoz.org/
  • 3.Slaver端爬蟲(chóng)獲取到請(qǐng)求豆混,開(kāi)始爬取篓像。
    三、mycrawler_redis (class MyCrawler(RedisCrawlSpider))
    這個(gè)RedisCrawlSpider類(lèi)爬蟲(chóng)繼承了RedisCrawlSpider皿伺,能夠支持分布式的抓取员辩。因?yàn)椴捎玫氖莄rawlSpider,所以需要遵守Rule規(guī)則鸵鸥,以及callback不能寫(xiě)parse()方法奠滑。
    同樣也不再有start_urls了,取而代之的是redis_key妒穴,scrapy-redis將key從Redis里pop出來(lái)宋税,成為請(qǐng)求的url地址。
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisCrawlSpider
class MyCrawler(RedisCrawlSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'mycrawler_redis'
    allowed_domains = ['設(shè)置允許爬取的域']
    redis_key = 'mycrawler:start_urls'
    rules = (
        # follow all links
        Rule(LinkExtractor(), callback='parse_page', follow=True),
    )
    # __init__方法必須按規(guī)定寫(xiě)讼油,使用時(shí)只需要修改super()里的類(lèi)名參數(shù)即可(一般不用)
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        # 修改這里的類(lèi)名為當(dāng)前類(lèi)名
        super(MyCrawler, self).__init__(*args, **kwargs)
    def parse_page(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意: 同樣的杰赛,RedisCrawlSpider類(lèi)不需要寫(xiě)start_urls:

  • scrapy-redis 一般直接寫(xiě)allowd_domains來(lái)指定需要爬取的域,也可以從在構(gòu)造方法init()里動(dòng)態(tài)定義爬蟲(chóng)爬取域范圍(一般不用)矮台。
  • 必須指定redis_key乏屯,即啟動(dòng)爬蟲(chóng)的命令,參考格式:redis_key = 'myspider:start_urls'
  • 根據(jù)指定的格式瘦赫,start_urls將在 Master端的 redis-cli 里 lpush 到 Redis數(shù)據(jù)庫(kù)里辰晕,RedisSpider 將在數(shù)據(jù)庫(kù)里獲取start_urls。
    執(zhí)行方式
  • 1.通過(guò)runspider方法執(zhí)行爬蟲(chóng)的py文件(也可以分次執(zhí)行多條)确虱,爬蟲(chóng)(們)將處于等待準(zhǔn)備狀態(tài):
scrapy runspider myspider_redis.py

或者

scrapy crawl myspider_redis
  • 2.在Master端的redis-cli輸入push指令含友,參考格式(指定起始url):
lpush myspider:start_urls http://www.dmoz.org/
  • 3.Slaver端爬蟲(chóng)獲取到請(qǐng)求,開(kāi)始爬取校辩。

總結(jié):

1 如果只是用到Redis的去重和保存功能窘问,就選第一種; 2 如果要寫(xiě)分布式宜咒,則根據(jù)情況惠赫,選擇第二種、第三種荧呐; 3 通常情況下汉形,會(huì)選擇用第三種方式編寫(xiě)深度聚焦爬蟲(chóng)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末倍阐,一起剝皮案震驚了整個(gè)濱河市概疆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峰搪,老刑警劉巖岔冀,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異概耻,居然都是意外死亡使套,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén)鞠柄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)侦高,“玉大人,你說(shuō)我怎么就攤上這事厌杜》钋海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵夯尽,是天一觀的道長(zhǎng)瞧壮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)匙握,這世上最難降的妖魔是什么咆槽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮圈纺,結(jié)果婚禮上秦忿,老公的妹妹穿的比我還像新娘。我一直安慰自己赠堵,他們只是感情好小渊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著茫叭,像睡著了一般酬屉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揍愁,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天呐萨,我揣著相機(jī)與錄音,去河邊找鬼莽囤。 笑死谬擦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的朽缎。 我是一名探鬼主播惨远,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼谜悟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了北秽?” 一聲冷哼從身側(cè)響起葡幸,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贺氓,沒(méi)想到半個(gè)月后蔚叨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辙培,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蔑水,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扬蕊。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搀别,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尾抑,到底是詐尸還是另有隱情领曼,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布蛮穿,位于F島的核電站庶骄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏践磅。R本人自食惡果不足惜单刁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望府适。 院中可真熱鬧羔飞,春花似錦、人聲如沸檐春。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疟暖。三九已至卡儒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俐巴,已是汗流浹背骨望。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欣舵,地道東北人擎鸠。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缘圈,于是被迫代替她去往敵國(guó)和親劣光。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袜蚕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容