分佈式的Scrapy過於能打!十個resquests都頂不住! ๑乛◡乛๑ Scrapy框架使用方法

Scrapy簡介

也許使用Scrapy和resquests對比是一件不太公平的事情,因爲Scrapy作爲一個框架和作爲一個庫的resquest功能偏向有所不同,Scrapy更加註重大而全。下圖左邊的文件管理器中爲只安裝resquest需要的庫,右邊爲安裝scrapy和scrapy-readis(做分佈式使用)的庫。從中我們可以看出scrapy相較於resquests龐大數倍。
在這裏插入圖片描述
雖然在體積上Scrapy顯得更加龐大,但在實用性上Scrapy卻比resquests有着更好的表現,無論是在代碼量上還是在運行速度上,Scrapy都遠勝於resquests。Scrapy的流程對於初學者來說初看可能會顯得比較繁瑣,但等使用過scrapy後,你會發現真個爬蟲流程中,scrapy幫你已經完成了很多事情,你需要做的大部分事情就是告訴Scrapy要去哪裏下載數據、那些數據對你來說需要保存和保存數據到哪裏。
在這裏插入圖片描述
scrapy中還有一些非常實用,已經封裝完成的方法,比如鏈接提取器、自動登錄、圖片(文件)下載器等。在我往期的scrapy博客中都有詳細介紹,這裏就不在過多贅述。千萬別被Scrapy看似複雜的流程勸退,相信我等你真正瞭解的Scrapy,你會覺得真想!(王境澤定理警告!!!)

分佈式Scrapy簡介

分佈式scrapy需要用到的redis這個非關係數據庫進行存儲,數據庫種類那麼多,我們爲什麼非要用redis呢?
這個問題就和redis數據庫的性質有關了,redis雖然是數據庫,但大多數時候他都是已一個類似於中間件的形式存在,因爲redis數據庫將數據存在了內存中,所以redis有着非常快的讀寫能力,除此之外redis還一個特效是會自動去重。而我們分佈式爬蟲需要做到數據的快速爬取和記錄已經完成爬取的網站,保證不會二次爬取造成資源浪費!而redis的快速讀寫非常適合爬蟲爬取記錄數據使用,自動去重又可以拿來驗證資源是否已經完成爬取。

準備工作

首先我們需要用pip安裝scrapypip install scrapy和scrapy-redispip install scrapy-redis,然後你需要安裝一個可以正常運行的redis,可以就在你本地安裝redis,也可以在服務器上安裝一個redis。
redis數據庫安裝
在這裏插入圖片描述

scrapy-redis簡介

scrapy-redis是一個基於scrapy和redis的開源分佈式項目,我們基於此項目可以快速搭建出我們自己的分佈式爬蟲,減少了重複造輪子帶來的麻煩。
在這裏插入圖片描述
scrapy-redis最主要是將我們的調度器放入了redis數據庫中,這樣能做到多個爬蟲共用一個調度器,還一個重要的作用就是存儲了指紋集合,他的主要作用是記錄已經爬取過得網頁。
其中收集的數據,可以根據你的需求自行決定是否要放入到redis中。

當我們啓動scrapy_redis後,在redis數據庫中,我們最多會出現三個鍵

  1. 爬蟲名:requests
    request:存放代請求(request)對象,獲取操作是pop形式,即獲取的請求會從requests中去除。==使用了:zset(有序集合)的數據格式, ==使用zrange 鍵名 0 -1即可查看所有數據。
  2. 爬蟲名:dupefilter
    dupefilter: 存放所有已經爬取過頁面的指紋,這裏之所以說是指紋而不是簡單的url,因爲默認指紋包含,請求方法、url和請求體使用了:set(集合)的數據格式, 使用smembers 鍵名即可查看所有數據
    在這裏插入圖片描述
  3. 爬蟲名:items
    這裏存放的是我們提取的數據,需要開啓管道'scrapy_redis.pipelines.RedisPipeline'此數據纔會同步到scrapy中,使用了:list(列表)的數據格式, 使用LRANGE 鍵名 0 -1即可查看所有數據。(中文默認使用ASCII進行了轉碼)

scrapy_redis設置(settings.py)

# 在redis中啓動調度器(Scheduler)存儲隊列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 確保所有爬蟲(spiders)共享相同的重複項數據集合,並通過redis進行過濾。
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# Default requests serializer is pickle, but it can be changed to any module with loads and dumps functions. Note that pickle is not compatible between python versions.
# 默認的請求序列化器是pickle,但是可以更改爲具有裝入和轉儲功能的任何模塊。 請注意,pickle在python版本之間不兼容。
# Caveat: In python 3.x, the serializer must return strings keys and support bytes as values. Because of this reason the json or msgpack module will not work by default. In python 2.x there is no such issue and you can use 'json' or 'msgpack' as serializers.
# 注意:在python 3.x中,序列化程序必須返回字符串鍵並支持字節作爲值。 因此,默認情況下json或msgpack模塊將無法正常工作。 在python 2.x中沒有這樣的問題,您可以使用'json'或'msgpack'作爲序列化程序。
#SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"

# 不清理redis隊列,允許暫停/恢復爬網。
#SCHEDULER_PERSIST = True

# 使用優先級隊列調度請求。(默認爲 PriorityQueue:優先隊列)
#SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'

# 備用隊列。(FifoQueue:先進先出隊列,LifoQueue:進先出隊列)
#SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue'
#SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'

# 最大空閒時間,以防止爬蟲(spiders)在分佈式爬行時被關閉。
# 這隻在隊列類是SpiderQueue或SpiderStack時有效,也可能在蜘蛛第一次啓動的同時阻塞(因爲隊列是空的)。
#SCHEDULER_IDLE_BEFORE_CLOSE = 10

# 將爬取下來的項目(items)存儲在Redis中以進行後期處理。
ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 300
}

# 項目(item)管道(pipeline)將項目序列化並存儲在此redis鍵中。
#REDIS_ITEMS_KEY = '%(spider)s:items'

# The items serializer is by default ScrapyJSONEncoder. You can use any importable path to a callable object.
# 默認情況下,項序列化程序是ScrapyJSONEncoder。可以使用可調用對象的任何可導入路徑。
#REDIS_ITEMS_SERIALIZER = 'json.dumps'

# 指定連接到Redis時要使用的主機和端口(默認爲:127.0.0.1:6379)。
#REDIS_HOST = 'localhost'
#REDIS_PORT = 6379

# 指定用於連接的完整Redis URL(可選)。此設置優先於REDIS_HOST和REDIS_PORT設置。
#REDIS_URL = 'redis://user:pass@hostname:9001'

# 自定義redis客戶端參數。即:密碼, 套接字(socket)超時等
REDIS_PARAMS = {
    'password': '連接密碼'
}
# 使用自定義編輯客戶端類。
#REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient'

# 如果爲True,則使用redis``SPOP``操作。必須使用“SADD”命令向redis隊列添加url。如果您希望避免在開始url列表中出現重複,並且處理順序無關緊要,那麼這可能非常有用。
#REDIS_START_URLS_AS_SET = False

# RedisSpider和rediscrawpsider的默認開始url鍵。
#REDIS_START_URLS_KEY = '%(name)s:start_urls'

# 對redis使用utf-8以外的編碼。
#REDIS_ENCODING = 'latin1'

有三種爬蟲模式可以使用分佈式Scrapy,分別是CrawlSpiderRedisCrawlSpiderRedisSpider如果你只接觸過Scrapy可能其中只有CrawlSpider是你聽說過得,這種也就是我們常用的鏈接提取器形式,這三種我們會在下面的實戰測試中詳細演示其中的作用。

實戰測試

我當前的設置redis部分和上圖一致,常規部分啓動了DOWNLOAD_DELAY = 1爬蟲限制和LOG_LOVER = 'DEBUG'錯誤警告信息這兩個

CrawlSpider

首先我們使用繼承與CrawlSpider的爬蟲,使用起來和使用鏈接提取器時候完全一致,鏈接提取器我在之前已經講過如何使用,這裏就不在贅述。

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class A陽光問政Spider(CrawlSpider):
    name = '陽光問政'
    allowed_domains = ['wz.sun0769.com']
    start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']

    rules = (
        # 翻頁鏈接提取(follow:用於重複提取,從提取的新頁面中繼續尋找符合當前要求的頁面)
        Rule(LinkExtractor(allow=r'wz.sun0769.com/political/index/politicsNewest\?id=1&page=\d+'), follow=True),
        # 詳情鏈接提取(callback:將提取到的詳情頁面傳入parse_item方法進行處理)
        Rule(LinkExtractor(allow=r'wz.sun0769.com/political/politics/index\?id=\d+'), callback='parse_item'),
    )

    def parse_item(self, response):
        item = {}
        item['標題'] = response.xpath('//p[@class="focus-details"]/text()').extract()
        item['內容'] = response.xpath('//div[@class="details-box"]/pre/text()').extract()
        item['配圖'] = response.xpath('//div[@class="mr-three"]/div[3]/img/@src').extract()
        return item

在這裏插入圖片描述
此爬蟲跑起來後,在redis數據庫中即會出現數據。

RedisCrawlSpider

接下來我們來看一看繼承了RedisCrawlSpider方法的分佈式爬蟲,這個爬蟲和CrawlSpider不同處在於初始URL也被放入了我們的redis中,項目啓動後,會自動從reids中提取初始URL而非在項目中的start_urls
在這裏插入圖片描述
如上圖所示,因爲無需在從爬蟲中獲取首個URL地址,所以start_urls,我們就不在需要了,redis_key用於指定在redis中首個URL隊列的鍵名,首個URL隊列,我們在redis也需要一個列表。使用lpush 鍵名 url即可創建一個url列表。如果啓動項目時沒有添加首個URL隊列,項目將會停止等待,一旦添加首個URL列表後,項目會自動開始抓取數據。

# -*- coding: utf-8 -*-
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor

from scrapy_redis.spiders import RedisCrawlSpider


class A陽光問政1Spider(RedisCrawlSpider):
    name = '陽光問政1'
    # redis_key用於指定在redis中首個URL列表的鍵名,具體名稱可以自定義
    redis_key = 'url'

    rules = (
        # 翻頁鏈接提取(follow:用於重複提取,從提取的新頁面中繼續尋找符合當前要求的頁面)
        Rule(LinkExtractor(allow=r'wz.sun0769.com/political/index/politicsNewest\?id=1&page=\d+'), follow=True),
        # 詳情鏈接提取(callback:將提取到的詳情頁面傳入parse_item方法進行處理)
        Rule(LinkExtractor(allow=r'wz.sun0769.com/political/politics/index\?id=\d+'), callback='parse_item'),
    )

    def parse_item(self, response):
        item = {}
        item['標題'] = response.xpath('//p[@class="focus-details"]/text()').extract()
        item['內容'] = response.xpath('//div[@class="details-box"]/pre/text()').extract()
        item['配圖'] = response.xpath('//div[@class="mr-three"]/div[3]/img/@src').extract()
        return item

在這裏插入圖片描述
redis_key中的url同樣使用pop形式獲取,獲得的數據會從隊列中刪除。
除了使用redis_key來指定url存放地址外,你還可以在設置中的REDIS_START_URLS_KEY指定初始url隊列地址
如果你可能會在初始url隊列中添加大量url可以使用REDIS_START_URLS_AS_SET,啓動此設置後,隊列會變成集合的形式,添加語句變爲sadd 鍵名 url,集合的形式會自動去重,但集合是無序的!!!
在這裏插入圖片描述

RedisSpider

最後,我們介紹一下RedisSpider,和RedisCrawlSpider不同的是這種分佈式爬蟲沒有了數據提取隊列。也最接近我們寫的普通爬蟲,其中首個url隊列和RedisCrawlSpider相同,都將其添加到了數據庫中。
這裏因爲沒有了便捷的鏈接提取器,我們需要自行提取連接,所以代碼量比上述兩種方式略大。

# -*- coding: utf-8 -*-
from scrapy_redis.spiders import RedisSpider


class A陽光問政2Spider(RedisSpider):
    name = '陽光問政2'
    redis_key = 'url'

    def parse(self, response):
        # print(SQL_HOST)
        # print(self.settings.get('SQL_HOST'))
        tr_data = response.xpath('//div[@class="width-12"]/ul[@class="title-state-ul"]/li/span[3]/a')
        for tr in tr_data:
            data = {}
            data['標題'] = tr.xpath('./text()').extract_first()
            data['詳情URL'] = tr.xpath('./@href').extract_first()
            data['詳情URL'] = "http://wz.sun0769.com" + data['詳情URL']
            yield response.follow(
                url=data['詳情URL'],
                callback=self.details_parse,
                meta={'data': data}
            )

        next_url = "http://wz.sun0769.com" + response.xpath(
            '//div[@class="width-12"]/div[3]/a[2]/@href').extract_first()
        yield response.follow(
            url=next_url,
            callback=self.parse
        )

    def details_parse(self, response):
        data = response.meta.get('data')
        data['內容'] = response.xpath('//div[@class="details-box"]/pre/text()').extract()
        img = response.xpath('//div[@class="mr-three"]/div[3]/img/@src').extract()
        # 視頻暫時無法加載出來
        # video = supporting.xpath('//div[@class="mr-three"]/div[3]/div/div/div/video/@src').extract()
        if img:
            data['配圖'] = img
        # if video:
        #     data['視頻'] = video
        yield data

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章