分布式爬蟲總結(jié)和使用

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

安裝scrapy-redis:

pip3 install scrapy-redis

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

1.Scheduler
2.Duplication Filter
3.Item Pipeline
4.Base Spider

scrapy-redis工作流程

image.png

Scheduler:

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í),來決定該入哪個(gè)隊(duì)列,出列時(shí)則按優(yōu)先級(jí)較小的優(yōu)先出列。為了管理這個(gè)比較高級(jí)的隊(duì)列字典,Scheduler需要提供一系列的方法。但是原來的Scheduler已經(jīng)無法使用,所以使用Scrapy-redis的scheduler組件。

Duplication Filter:

Scrapy中用集合實(shí)現(xiàn)這個(gè)request去重功能,Scrapy中把已經(jīng)發(fā)送的request指紋放入到一個(gè)集合中,把下一個(gè)request的指紋拿到集合中比對(duì),如果該指紋存在于集合中,說明這個(gè)request發(fā)送過了,如果沒有則繼續(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組件來實(shí)現(xiàn)的,它通過redis的set 不重復(fù)的特性,巧妙的實(shí)現(xiàn)了Duplication Filter去重。scrapy-redis調(diào)度器從引擎接受request,將request的指紋存?redis的set檢查是否重復(fù),并將不重復(fù)的request push寫?redis的 request queue。

Item Pipeline:

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

Base Spider:

不在使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個(gè)類,RedisMixin是用來從redis讀取url的類。
當(dāng)我們生成一個(gè)Spider繼承RedisSpider時(shí),調(diào)用setup_redis函數(shù),這個(gè)函數(shù)會(huì)去連接redis數(shù)據(jù)庫,然后會(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)目說明

使用scrapy-redis的example來修改 先從github上拿到scrapy-redis的示例,然后將里面的example-project目錄移到指定的地址:
clone github scrapy-redis源碼文件

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

拿官方的項(xiàng)目范例:

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的去重和保存功能,并沒有實(shí)現(xiàn)分布式

這個(gè)爬蟲繼承的是CrawlSpider,它是用來說明Redis的持續(xù)性,當(dāng)我們第一次運(yùn)行dmoz爬蟲,然后Ctrl + C停掉之后,再運(yùn)行dmoz爬蟲,之前的爬取記錄是保留在Redis里的。

分析起來,其實(shí)這就是一個(gè) scrapy-redis 版 CrawlSpider 類,需要設(shè)置Rule規(guī)則,以及callback不能寫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ī)則,將滿足條件的交給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è)爬蟲繼承了RedisSpider, 它能夠支持分布式的抓取,采用的是basic spider,需要寫parse函數(shù)。 其次就是不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis里pop出來,成為請(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ī)定格式寫,使用時(shí)只需要修改super()里的類名參數(shù)即可,一般不用
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這里的類名為當(dāng)前類名
        super(MySpider, self).__init__(*args, **kwargs)

    def parse(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意: RedisSpider類 不需要寫start_urls:

  • scrapy-redis 一般直接寫allowd_domains來指定需要爬取的域,也可以從在構(gòu)造方法init()里動(dòng)態(tài)定義爬蟲爬取域范圍(一般不用)。
  • 必須指定redis_key,即啟動(dòng)爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'
  • 根據(jù)指定的格式,start_urls將在 Master端的 redis-cli 里 lpush 到 Redis數(shù)據(jù)庫里,RedisSpider 將在數(shù)據(jù)庫里獲取start_urls

執(zhí)行方式:
1.通過runspider方法執(zhí)行爬蟲的py文件(也可以分次執(zhí)行多條),爬蟲(們)將處于等待準(zhǔn)備狀態(tài):

scrapy runspider myspider_redis.py

or

scrapy crawl myspider_redis

2.在Master端的redis-cli輸入push指令,參考格式(指定起始url):

lpush myspider:start_urls http://www.dmoz.org/

3.Slaver端爬蟲獲取到請(qǐng)求,開始爬取。

三.mycrawler_redis (class MyCrawler(RedisCrawlSpider))
這個(gè)RedisCrawlSpider類爬蟲繼承了RedisCrawlSpider,能夠支持分布式的抓取。因?yàn)椴捎玫氖莄rawlSpider,所以需要遵守Rule規(guī)則,以及callback不能寫parse()方法。
同樣也不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis里pop出來,成為請(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ī)定寫,使用時(shí)只需要修改super()里的類名參數(shù)即可(一般不用)
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這里的類名為當(dāng)前類名
        super(MyCrawler, self).__init__(*args, **kwargs)

    def parse_page(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意: 同樣的,RedisCrawlSpider類不需要寫start_urls:

執(zhí)行方式:

1.scrapy runspider myspider_redis.py
2.lpush myspider:start_urls http://www.dmoz.org/
3.Slaver端爬蟲獲取到請(qǐng)求,開始爬取。

總結(jié):

1 如果只是用到Redis的去重和保存功能,就選第一種;
2 如果要寫分布式,則根據(jù)情況,選擇第二種、第三種;
3 通常情況下,會(huì)選擇用第三種方式編寫深度聚焦爬蟲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容