Python逆向爬蟲之scrapy框架,非常詳細

爬蟲系列目錄

目錄

Python逆向爬蟲之scrapy框架,非常詳細

一、爬蟲入門

那麼,我相信初學的小夥伴現在一定是似懂非懂的。那麼下面我們通過一個案例來慢慢進行分析,具體如下:

今天,我們的目標是一個圖片網站,//www.quanjing.com/tupian/meinv-1.html

首先,我們第一步需要做的就是項目分析,我們來看看爬取這個網站我們需要哪些步驟。

1.1 定義需求

需求就是將該網站中所有的美女圖片分類下載到本地。

image

1.2 需求分析

如果我們需要下載上面所表示的所有的圖片的話,我們需要如下幾個步驟:

  1. 下載某個頁面上所有的圖片
  2. 分頁
  3. 進行下載圖片

1.2.1 下載某個頁面上所有的圖片

# -*- coding: utf-8 -*-

import requests
from lxml import etree

import urllib3
urllib3.disable_warnings()

def getClassification(num):
    """
    獲取分類鏈接
    :return:
    """

    url = f"//www.quanjing.com/tupian/meinv-{num}.html"

    html = sendRequest(url, "get")

    htmlValus = htmlAnalysis(html.text, '//*[@id="gallery-list"]/li')

    for item in htmlValus:
        imgUrl = item.xpath('./a/img/@src')[0]
        downLoad(imgUrl)

def downLoad(url):

    """
    下載圖片
    :param url:
    :return:
    """

    img = sendRequest(url)

    imgName = url.split("@")[0].split("/")[-1]

    with open("./quanjing/" + imgName, 'wb') as imgValue:
        imgValue.write(img.content)

def htmlAnalysis(html, rule):

    """
    根據 xpath 獲取數據
    :param html:
    :param rule:
    :return:
    """

    htmlValues = etree.HTML(html)

    value = htmlValues.xpath(rule)

    return value


def sendRequest(url, method="get"):
    """
    發送請求
    :param url:
    :param method:
    :return:
    """
    if method.lower() == "get":
        html = requests.get(url=url, headers=getHeader(), verify=False)
    elif method.lower() == "post":
        html = requests.post(url=url, headers=getHeader())
    else:
        html = None

    return html


def getHeader():
    """
    獲取Header
    :return:
    """

    ua_headers = {
        "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
        "referer": "//www.quanjing.com/"
    }

    return ua_headers


def main():
    getClassification(1)


if __name__ == '__main__':
    main()

1.2.2 分頁

def main():
    for i in range(10):
        getClassification(i)

1.2.3 進行下載圖片

def downLoad(url):

    """
    下載圖片
    :param url:
    :return:
    """

    img = sendRequest(url)

    imgName = url.split("@")[0].split("/")[-1]

    with open("./quanjing/" + imgName, 'wb') as imgValue:
        imgValue.write(img.content)

二、Scrapy 入門

Scrapy是一個為了爬取網站數據,提取結構性數據而編寫的應用框架。 可以應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。

其最初是為了頁面抓取 (更確切來說, 網絡抓取 )所設計的, 也可以應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。

下面我們給出一個 Scrapy 的架構圖

上面的架構圖明確的說明了 Scrapy 主要有 5 個部分。

  • 引擎(Scrapy Engine):引擎負責控制數據流在系統中所有組件中流動,並在相應動作發生時觸發事件。
  • 管道(Pipline):主要提供存儲服務,把需要存儲的數據存儲到相關數據庫之中。
  • 調度器(Scheduler):主要提供兩個功能,分別是 去重 和 隊列。
  • 下載器(Downloader):下載器負責獲取頁面數據並提供給引擎,而後提供給spider。
  • 爬蟲(Spiders):Spider是Scrapy用戶編寫用於分析response並提取item(即獲取到的item)或額外跟進的URL的類。 每個spider負責處理一個特定(或一些)網站。

其實除了上述的內容外,Scrapy 還提供一些中間件,例如:下載器中間件(Downloader Middlewares)和爬蟲中間件(Spider Middlewares)等。

所以,把上面完整的圖可以畫成如下:

image

2.1 安裝 Scrapy

在命令行模式下使用pip命令即可安裝。

$ pip install scrapy

2.2 Scrapy 創建項目

第一步:創建一個scrapy項目

$ scrapy startproject mySpider

第二步:生成一個爬蟲

$ cd mySpider
$ scrapy genspider tupian //www.quanjing.com/

2.3 scrapy 命令

#1 查看幫助
scrapy -h
scrapy <command> -h

#2 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不需要
Global commands:
    startproject #創建項目
    genspider    #創建爬蟲程序
    settings     #如果是在項目目錄下,則得到的是該項目的配置
    runspider    #運行一個獨立的python文件,不必創建項目
    shell        #scrapy shell url地址  在交互式調試,如選擇器規則正確與否
    fetch        #獨立於程單純地爬取一個頁面,可以拿到請求頭
    view         #下載完畢後直接彈出瀏覽器,以此可以分辨出哪些數據是ajax請求
    version      #查看scrapy的版本

Project-only commands:
    crawl        #運行爬蟲,必須創建項目才行,確保配置文件中ROBOTSTXT_OBEY = False
    check        #檢測項目中有無語法錯誤
    list         #列出項目中所包含的爬蟲名
    edit         #編輯器,一般不用
    parse        #scrapy parse url地址 --callback 回調函數
    bench        #scrapy bentch壓力測試

#3 官網鏈接
    //docs.scrapy.org/en/latest/topics/commands.html

2.4 生成文件詳情

  • scrapy.cfg:項目的主配置信息,用來部署scrapy時使用,爬蟲相關的配置信息在settings.py文件中。
  • items.py:設置數據存儲模板,用於結構化數據,如:Django的Model
  • pipelines:數據處理行為,如:一般結構化的數據持久化
  • settings.py:配置文件。
  • spiders:爬蟲目錄,如:創建文件,編寫爬蟲規則

2.5 第一個 scrapy 爬蟲程序

2.5.1 編輯 spider

import scrapy
from ..items import MyspiderItem

class TupianSpider(scrapy.Spider):
    # 定義爬蟲名,scrapy會根據該值定位爬蟲程序,所以它必須要有且必須唯一
    name = 'tupian'
    # 定義允許爬取的域名,如果OffsiteMiddleware啟動(默認就啟動),
    # 那麼不屬於該列表的域名及其子域名都不允許爬取
    allowed_domains = ['www.quanjing.com']
    # 如果沒有指定url,就從該列表中讀取url來生成第一個請求
    start_urls = ['//www.quanjing.com/tupian/meinv.html']

    # 爬蟲啟動函數,必須定義成這個函數名稱
    def parse(self, response):

        imgList = response.xpath('//*[@id="gallery-list"]/li')

        for item in imgList:

            imgSrcValue = item.xpath('./a/img/@src').get()

            yield MyspiderItem(img_url=imgSrcValue)

2.5.2 編輯 piplines

from itemadapter import ItemAdapter


class MyspiderPipeline:

    def process_item(self, item, spider):
        print(item)
        return item

2.5.3 編輯配置文件

# 設置日誌的級別
LOG_LEVEL = "WARNING"

# 關閉 Robots.txt 協議
ROBOTSTXT_OBEY = False

# 開啟 pipelines
ITEM_PIPELINES = {
   'mySpider.pipelines.MyspiderPipeline': 300,
}

from scrapy import cmdline

# 方法 1
cmdline.execute('scrapy crawl yourspidername'.split())

# 方法 2
sys.argv = ['scrapy', 'crawl', 'down_info_spider']
cmdline.execute()

三、Scrapy Spider

Spider 負責處理所有Responses,從中分析提取數據,獲取Item字段需要的數據,並將需要跟進的URL提交給引擎,再次進入Scheduler(調度器)。

總結 Spider 主要有三個作用,分別是:鏈接配置、抓取邏輯和解析邏輯。

Spider 的整個爬取循環過程如下:

  • 以初始的 URL 初始化 Request ,並設置回調函數。當該 Request 成功請求並返回時, Response 生成並作為參數傳給該回調函數。
  • 在回調函數內分析返回的網頁內容 。 返回結果有兩種形式:
    • 一種是解析到的有效結果返回字典或 Item 對象,它們可以經過處理後(或直接)保存
    • 另一種是解析得到下一個(如下一頁)鏈接,可以利用此鏈接構造 Request 並設置新的回調函數,返回 Request 等待後續調度
  • 如果返回的是字典或 Item 對象,我們可通過 Feed Exports 等組件將返回結果存入到文件。 如果設置了 Pipeline 的話,我們可以使用 Pipeline 處理 (如過濾、修正等)並保存。
  • 如果返回的是 Request ,那麼 Request 執行成功得到 Response 之後, Response 會被傳遞給Request 中定義的回調函數,在回調函數中我們可以再次使用選擇器來分析新得到的網頁內容,並根據分析的數據生成 Item。

3.1 Spider 詳細

Spider 繼承自 scrapy.spiders.Spiderscrapy.spiders.Spider 這個類是最簡單最基本的 Spider 類,其他 Spider 必須繼承這個類。
scrapy.spiders.Spider 類提供了start_requests()方法的默認實現,讀取並請求 start_urls 屬性 ,並根據返回的結果調用 parse() 方法解析結果 。

它有如下一些基礎屬性:

  • name:爬蟲名稱,是定義 Spider 名字的字符串。Spider 的名字定義了 Scrapy 如何定位並初始化 Spider ,它必須是唯一的。不過我們可以生成多個相同的 Spider 實例,數量沒有限制。name 是 Spider 最重要的屬性 。 如果 Spider 爬取單個網站, 一個常見的做法是以該網站的域名名稱來命名 Spider。 例如, Spider 爬取 mywebsite.com , 該 Spider通常會被命名為 mywebsite。

  • allowed_domains允許爬取的域名,是可選配置,不在此範圍的鏈接不會被跟進爬取

  • start_urls:它是起始 URL 列表,當我們沒有實現start_requests()方法時,默認會從這個列表開始抓取。

  • custom_settings它是一個字典,是專屬於本 Spider 的配置,此設置會覆蓋項目全局的設置。此設置必須在初始化前被更新,必須定義成類變量。

    不同爬蟲pipeline設置
    custom_settings = {
        'ITEM_PIPELINES': {
            'video.pipelines.VideoPipeline': 301,
        }
    }
    
  • crawler:它是由 from_crawler() 方法設置的,代表的是本 Spider 類對應的 Crawler 對象 。Crawler 對象包含了很多項目組件,利用它我們可以獲取項目的一些配置信息,如最常見的獲取項目的設置信息,即 Settings。

  • settings:它是一個 Settings 對象,利用它我們可以直接獲取項目的全局設置變量

除了基礎屬性,Spider 還有一些常用的方法。

  • start_requests():此方法用於生成初始請求,它必須返回一個可迭代對象,該方法可以被重寫。此方法會默認使用 start_urls 裏面的 URL 來構造 Request,而且 Request 默認是 GET 請求方式。如果我們想在啟動時以 POST方式訪問某個站點,可以直接重寫這個方法,發送 POST請求時使用 FormRequest 即可 。
  • parse(response)當 Response 沒有指定回調函數時,該方法會默認被調用 。 它負責處理 Response 處理返回結果,並從中提取出想要的數據和下一步的請求,然後返回。該方法需要返回一個包含 Request ltem的可迭代對象。
  • closed(reason)當 Spider 關閉時,該方法會被調用,在這裡一般會定義釋放資源的一些操作或其他收尾操作
import scrapy
from ..items import MyspiderItem

class TupianSpider(scrapy.Spider):
    # 定義爬蟲名,scrapy會根據該值定位爬蟲程序,所以它必須要有且必須唯一
    name = 'tupian'
    # 定義允許爬取的域名,如果OffsiteMiddleware啟動(默認就啟動),
    # 那麼不屬於該列表的域名及其子域名都不允許爬取
    allowed_domains = ['www.quanjing.com']
    # 如果沒有指定url,就從該列表中讀取url來生成第一個請求
    start_urls = ['//www.quanjing.com/tupian/meinv.html']

    def start_requests(self):
        
        """
        開始請求之前的執行
        :return: 
        """
        print("我是開始")
        yield scrapy.Request(
            url=self.start_urls[0],
            callback=self.parse
        )


    # 爬蟲啟動函數,必須定義成這個函數名稱
    def parse(self, response):
        
        """
        爬蟲具體內容
        :param response: 
        :return: 
        """
        print("我是 parse")

        imgList = response.xpath('//*[@id="gallery-list"]/li')

        for item in imgList:

            imgSrcValue = item.xpath('./a/img/@src').get()

            yield MyspiderItem(img_url=imgSrcValue)

    def close(spider, reason):
        
        """
        結束時調用
        :param reason: 
        :return: 
        """
        print("關閉")
        return None

image

3.2 spider常用的方法

3.2.1 解析常用的幾個方法

我們可以通過 scrapy.selector.unified.SelectorList 對象來查找get()getall()extract()extract_first()re.first()的如何使用。

  1. extract()方法:獲取的是一個列表內容
    def parse(self, response):

        """
        爬蟲具體內容
        :param response:
        :return:
        """
        imgList = response.xpath('//*[@id="gallery-list"]/li').extract()
        print(imgList)

image

  1. extract_first()方法:返回列表的第一個內容,也就是extract()列表的第一個元素
    def parse(self, response):

        """
        爬蟲具體內容
        :param response:
        :return:
        """
        imgList = response.xpath('//*[@id="gallery-list"]/li').extract_first()
        print(imgList)

image

  1. getall()方法:返回所有的元素
imgList = response.xpath('//*[@id="gallery-list"]/li').extract_first()
print(imgList)

image

  1. get()方法:返回第一個元素,是str類型數據
    def parse(self, response):

        """
        爬蟲具體內容
        :param response:
        :return:
        """
        imgList = response.xpath('//*[@id="gallery-list"]/li').get()
        print(imgList)

image

  1. re()方法:正則的使用。返回所以的滿足條件,結果是列表類型
def parse(self, response):

    """
        爬蟲具體內容
        :param response:
        :return:
        """
    imgList = response.xpath('//*[@id="gallery-list"]/li').re("\d+")
    print(imgList)

image

  1. re_first()方法:正則使用,返回的是滿足條件第一個元素
def parse(self, response):

    """
        爬蟲具體內容
        :param response:
        :return:
        """
    imgList = response.xpath('//*[@id="gallery-list"]/li').re_first("\d+")
    print(imgList)

image

3.2.2 response 常用的幾個方法

常見的幾個 response 方法。

  1. response.body.decode(“utf-8”):返回 HTML 並設置字符集編碼

image

  1. response.body:以 bytes 類型返回請求的 HTML 。

image

  1. response.url:返回 URL
print(response.url)
  1. response.urljoin(“dsadasd”):返回 URL 拼接後的結果
print(response.urljoin("dsadasd"))

image

  1. response.encoding:返回請求狀態碼

image

四、Scrapy Pipline

當 Item 在 Spider 中被收集之後,就會被傳遞到 Item Pipeline 中進行處理。

每個 item pipeline 組件是實現了簡單的方法的 python 類,負責接收到 item 並通過它執行一些行為,同時也決定此 Item 是否繼續通過 pipeline ,或者被丟棄而不再進行處理。

item pipeline 的主要作用:清理html數據、驗證爬取的數據、去重並丟棄和保存數據。

每個pipeline組件是一個獨立的 pyhton 類,必須實現以process_item(self, item,spider) 方法。

每個item pipeline組件都需要調用該方法,這個方法必須返回一個具有數據的 dict,或者 item對象,或者拋出 DropItem 異常,被丟棄的 item 將不會被之後的 pipeline 組件所處理。

注意:如果要使用哪一個 pipeline ,必須在配置文件中配置 ITEM_PIPELINES

4.1 pipeline 中的函數

scrapy pipeline 中主要的函數有 open_spider(self,spider)close_spider(self,spider)from_crawler(cls,crawler)

4.1.1 process_item(self, item, spider)

表示當 spider 被開啟的時候調用的主方法。

class MyspiderPipeline:

    def process_item(self, item, spider):
        print(item)
        return item

其中,item 參數是 spider 返回的數據。

image

spider 是調用該管道的 spider。

image

4.1.2 close_spider(self,spider)

當 spider 結束的時候這個方法被調用。

class MyspiderPipeline:

    def process_item(self, item, spider):
        print(type(spider))
        return item

    def close_spider(self, spider):
        print("結束時調用!")

image

4.1.3 open_spider(self,spider)

表示當 spider 被開啟的時候調用這個方法。

def open_spider(self, spider):
    print("開始時調用")

image

4.1.4 from_crawler(cls,crawler)

獲取參數。

@classmethod
def from_crawler(cls, crawler):
    log_level = crawler.settings.get('LOG_LEVEL')
    # FIXME: for now, stats are only supported from this constructor
    return cls(log_level)

def __init__(self, log_level):
    self.log_level = log_level

image

但是,scrapy 還提供一個新的獲取配置的方法。

from scrapy.utils.project import get_project_settings
settings = get_project_settings()
print(settings.get('LOG_LEVEL'))

4.2 scrapy 自帶的 pipeline

scrapy 默認攜帶了很多常用的中間件。下面我們來跟大家介紹幾個常用的自帶中間件。

4.2.1 FilesPipeline

文件下載中間件。

注意:使用 ImagesPipeline 首先定義存儲文件的路徑,所以需要定義一個FILES_STORE變量,在settings.py中添加如下代碼:

FILES_STORE = "./quanjing/"
1. get_media_requests

設置下載文件時的請求頭, 並返回一個 request 對象。

def get_media_requests(self, item, info):
    
    """
        設置下載文件時的請求頭, 返回一個 request 對象
        :param item:
        :param info:
        :return:
        """
    yield scrapy.Request(item['img_url'], meta={'Referer': item['img_url']})
2. file_path

設置下載文件的名稱。

def file_path(self, request, response=None, info=None, *, item=None):

    """
        設置下載路徑
        :param request:
        :param response:
        :param info:
        :param item:
        :return:
        """
    return request.url.split("@")[0].split("/")[-1]

4.2.2 ImagesPipeline

圖片下載管道。

注意:使用 ImagesPipeline 首先定義存儲文件的路徑,所以需要定義一個IMAGES_STORE變量,在settings.py中添加如下代碼:

IMAGES_STORE = './images'
1. get_media_requests

設置下載文件時的請求頭, 並返回一個 request 對象。

def get_media_requests(self, item, info):
    """
        設置下載文件時的請求頭, 返回一個 request 對象
        :param item:
        :param info:
        :return:
        """

    yield scrapy.Request(item['img_url'], meta={'Referer': item['img_url']})
2. file_path

設置下載文件的名稱。

def file_path(self, request, response=None, info=None, *, item=None):
    """
        設置下載路徑
        :param request:
        :param response:
        :param info:
        :param item:
        :return:
        """
    return request.url.split("@")[0].split("/")[-1]

3. item_completed

當下載結束之後調用。

def item_completed(self, results, item, info):

    """
        當下載文件結束之後調用
        :param results:
        :param item:
        :param info:
        :return:
        """
    image_path = []
    error_path = []
    for ok, x in results:
        if ok:
            image_path.append(x)
        else:
            error_path.append(x)
            print(error_path)
            print(image_path)
            return item


# []
# [{'url': '//pic.quanjing.com/9e/57/QJ6173385556.jpg@!350h', 'path': 'QJ6173385556.jpg', 'checksum': 'e5af2c0cf607b968c1e7eec24e4934ac', 'status': 'uptodate'}]

五、scrapy 中間件

scrapy 中間件是 scrapy框架的重要組成部分,主要分為兩大種類,分別是:下載器中間件(DownloaderMiddleware)和 爬蟲中間件(SpiderMiddleware)。

引擎(engine)將 request 對象交給下載器之前,會經過下載器中間件;並且 scrapy 是支持同時使用多個中間件的。多個中間件之間遵循先進後出的原理。

5.1 下載中間件(DownloaderMiddleware

位於 Scrapy 引擎和下載器之間,主要用來處理從 EGINE 傳到 DOWLOADER 的請求 request 和已經從 DOWNLOADER 傳到 EGINE 的響應 response。在這個過程中你可用該中間件做以下幾件事,分別是:添加ip代理添加cookie添加UA請求重試 等。

image

在下載中間件中,主要包含了 process_request(request, spider)process_response(request, response, spider)process_exception(request, exception, spider)三個函數。

5.1.1 process_request(request, spider)

當每個request通過下載中間件時,該方法被調用。process_request() 必須返回其中之一: 返回 None 、返回一個 Response 對象、返回一個 Request 對象或raise IgnoreRequest 。

  • 返回 None 時,,Scrapy 將繼續處理該 request,執行其他的中間件的相應方法,直到合適的下載器處理函數 (download handler) 被調用, 該request被執行(其response被下載)。
  • 如果其返回 Response 對象,Scrapy將不會調用 任何 其他的 process_request() 或 process_exception() 方法,或相應地下載函數; 其將返回該response。 已安裝的中間件的 process_response() 方法則會在每個response返回時被調用。
  • 如果其返回 Request 對象,Scrapy 則停止調用 process_request 方法並重新調度返回的 request。當新返回的 request 被執行後, 相應地中間件鏈將會根據下載的 response 被調用。
  • 如果其 raise 一個 IgnoreRequest 異常,則安裝的下載中間件的 process_exception() 方法會被調用。如果沒有任何一個方法處理該異常, 則 request 的 errback(Request.errback) 方法會被調用。如果沒有代碼處理拋出的異常, 則該異常被忽略且不記錄(不同於其他異常那樣)。

5.1.2 process_response(request, response, spider)

當下載器完成HTTP請求,傳遞響應給引擎的時候調用,它會返回 ResponseRequestIgnoreRequest三種對象的一種。

  • 若返回 Response 對象,它會被下個中間件中的 process_response() 處理
  • 若返回 Request 對象,中間鏈停止,然後返回的 Request 會被重新調度下載
  • 拋出 IgnoreRequest,回調函數 Request.errback 將會被調用處理,若沒處理,將會忽略

5.1.3 process_exception(request, exception, spider)

當下載處理器 (download handler) 或 process_request() 拋出異常(包括 IgnoreRequest 異常)時, Scrapy 調用 process_exception() ,通常返回 None,它會一直處理異常

5.1.3 下載圖片案例

  • tupian.py
import scrapy
from ..items import MyspiderItem

class TupianSpider(scrapy.Spider):
    # 定義爬蟲名,scrapy會根據該值定位爬蟲程序,所以它必須要有且必須唯一
    name = 'tupian'
    # 定義允許爬取的域名,如果OffsiteMiddleware啟動(默認就啟動),
    # 那麼不屬於該列表的域名及其子域名都不允許爬取
    allowed_domains = ['www.quanjing.com', 'pic.quanjing.com']
    # 如果沒有指定url,就從該列表中讀取url來生成第一個請求
    start_urls = ['//www.quanjing.com/tupian/meinv.html']

    # 爬蟲啟動函數,必須定義成這個函數名稱
    def parse(self, response):

        """
        爬蟲具體內容
        :param response:
        :return:
        """

        imgList = response.xpath('//*[@id="gallery-list"]/li')

        ua_header = {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            "Referer": "//www.quanjing.com/"
        }

        for item in imgList:

            imgSrcValue = item.xpath('./a/img/@src').extract_first()

            yield scrapy.Request(
                url=imgSrcValue,
                callback=self.parse_detail,
                headers=ua_header,
                meta={ "img_url": imgSrcValue }
            )


    def parse_detail(self, response):

        """
        圖片詳情
        :param response: 
        :return: 
        """

        item = MyspiderItem()
        item['img_url'] = response.meta['img_url']
        item['img_body'] = response.body

        yield item

  • item.py
import scrapy

class MyspiderItem(scrapy.Item):
    # define the fields for your item here like:
    img_url = scrapy.Field()
    img_body = scrapy.Field()
  • pipeline.py
class MyspiderPipeline:

    def process_item(self, item, spider):
        settings = get_project_settings()

        with open(settings['IMAGES_STORE'] + item['img_url'].split("@")[0].split("/")[-1], 'wb') as f:
            f.write(item['img_body'])

        return item
  • setting.py
IMAGES_STORE = "./quanjing/"

image

5.2 scrapy 爬蟲(spider)中間件

spider 中間件是一個與 Scrapy 的 spider 處理機制掛鈎的框架,您可以在其中插入自定義功能來處理髮送給spider進行處理的響應,並處理從 spider 生成的請求和項目。

5.2.1 激活 Spider 中間件

要激活蜘蛛中間件組件,請將其添加到 SPIDER_MIDDLEWARES設置中,這是一個 dict,其鍵是中間件類路徑,它們的值是中間件順序。

SPIDER_MIDDLEWARES = {
   'mySpider.middlewares.MyspiderSpiderMiddleware': 543,
}

5.2.2 Spider 中間件

spider 中間件主要是作用於引擎和調度器之間的。

image

  1. from_crawler:類方法,用於初始化中間件
  2. process_spider_input:當 response 通過 spider 中間件時,該方法被調用,處理該 response
  3. process_spider_output:當 Spider 處理 response 返回 result 時,該方法被調用
  4. process_spider_exception:異常時,該方法被調用
  5. process_start_requests:該方法以 spider 啟動的 request 為參數被調用,執行的過程類似於
  6. process_spider_outpu ,只不過其沒有相關聯的 response 並且必須返回 request(不是item)。

調用順序為:from_crawler –> spider_opened –> process_start_requests –> process_spider_input –> process_spider_output。

5.3 Scrapy 自帶中間件

scrapy 默認自帶了一部分常用的中間件。下面我們舉幾個案例介紹一些 Scrapy 自帶的中間件。

5.3.1 HttpProxyMiddleware 代理中間件

middlewares.py

class RequestProxyMiddleware(HttpProxyMiddleware):

    def process_request(self, request, spider):
        settings = get_project_settings()
        self.proxies = settings.get("HTTP_PROXY")

        request.meta["proxy"] = random.choice(self.proxies)

        return None

settings.py

DOWNLOADER_MIDDLEWARES = {
   'mySpider.middlewares.RequestProxyMiddleware': 544
}
HTTP_PROXY = {
   "47.92.113.71:80",
   "59.124.224.205:3128",
   "118.163.13.200:8080",
   "47.57.188.208:80",
   "59.124.224.205:3128",
   "59.124.224.205:3128",
   "59.124.224.205:3128",
   "112.250.107.37:53281",
   "59.124.224.205:3128",
   "47.92.113.71:80",
   "59.124.224.205:3128"
}

六、CrawlSpider全站數據抓取

CrawlSpider 其實是 Spider 的一個子類,除了繼承到 Spider 的特性和功能外,還派生除了其自己獨有的更加強大的特性和功能。其中最顯著的功能就是 」LinkExtractors鏈接提取器「。Spider 是所有爬蟲的基類,其設計原則只是為了爬取 start_url 列表中網頁,而從爬取到的網頁中提取出的 url 進行繼續的爬取工作使用 CrawlSpider 更合適。

6.1 創建 CrawSpider 項目

$ scrapy startproject crawSpiderProject
$ cd crawSpiderProject
$ scrapy genspider -t crawl netbian pic.netbian.com

6.2 參數說明

  • allow:接收一個正則表達式或一個正則表達式列表,提取絕對url於正則表達式匹配的鏈接,如果該參數為空,默認全部提取。
  • deny:接收一個正則表達式或一個正則表達式列表,與allow相反,排除絕對url於正則表達式匹配的鏈接,換句話說,就是凡是跟正則表達式能匹配上的全部不提取。
  • allow_domains:接收一個域名或一個域名列表,提取到指定域的鏈接。
  • deny_domains:和allow_doains相反,拒絕一個域名或一個域名列表,提取除被deny掉的所有匹配url。
  • deny_extensions:拒絕一個後綴。
  • restrict_xpaths:接收一個xpath表達式或一個xpath表達式列表,提取xpath表達式選中區域下的鏈接。
  • restrict_css:這參數和restrict_xpaths參數經常能用到,所以同學必須掌握
  • tags:接收一個標籤(字符串)或一個標籤列表,提取指定標籤內的鏈接,默認為tags=(『a』,『area』)
  • attrs:接收一個屬性(字符串)或者一個屬性列表,提取指定的屬性內的鏈接,默認為attrs=(『href』,),示例,按照這個中提取方法的話,這個頁面上的某些標籤的屬性都會被提取出來,如下例所示,這個頁面的a標籤的href屬性值都被提取到了
  • process_value (callable) :它接收來自掃描標籤和屬性提取每個值, 可以修改該值, 並返回一個新的, 或返回 None 完全忽略鏈接的功能。如果沒有給出, process_value 默認是 lambda x: x。
  • cononicalize=(boolean) 規範化每個提取的url(使用w3lib.url.canonicalize_url)。默認為True。
  • unique=(boolean) 是否應對提取的鏈接應用重複過濾。
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class CrawlquanjingSpider(CrawlSpider):
    name = 'crawlquanjing'
    allowed_domains = ['so.gushiwen.cn']
    start_urls = ['//so.gushiwen.cn/guwen/default.aspx?p=1']

    # 需求:
    # 爬取所有的圖片
    # follow : 是否將該規則作用於 response
    # 1 2 3 4 5 ... 77 78 79         5    3 4 5 6  ... 77 78 79
    # 1 2 3 4 5 77 78 79   6 ~ 76
    rules = (
        # 列表頁 規則
        # Rule(LinkExtractor(allow=r'//movie.douban.com/subject/\d+/'), callback='parse_item'),
        # # 下一頁 規則
        Rule(LinkExtractor(allow=r'/guwen/default.aspx\?p=\d+',
                           deny_extensions=['xxx'],
                           deny=r'/user/\w+\.aspx'),
             follow=True, callback='parse_item'),
    )

    def parse_item(self, response, **kwargs):
        # 只需要出來結果,不需要處理請求
        print(response.url)
        item = {}
        # item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
        # item['name'] = response.xpath('//div[@id="name"]').get()
        # item['description'] = response.xpath('//div[@id="description"]').get()
        return item

七、分佈式爬蟲

搭建一個分佈式的集群,讓其對一組資源進行分佈聯合爬取,提升爬取效率。

pip install scrapy-redis

7.1 scrapy框架是否可以自己實現分佈式?

不可以!!!

  • 其一:因為多台機器上部署的scrapy會各自擁有各自的調度器,這樣就使得多台機器無法分配start_urls列表中的url。(多台機器無法共享同一個調度器)
  • 其二:多台機器爬取到的數據無法通過同一個管道對數據進行統一的數據持久化存儲。(多台機器無法共享同一個管道)

7.2 基於scrapy-redis組件的分佈式爬蟲

scrapy-redis組件中為我們封裝好了可以被多台機器共享的調度器和管道,我們可以直接使用並實現分佈式數據爬取。

  • 實現方式:
    • 基於該組件的RedisSpider類
    • 基於該組件的RedisCrawlSpider類

7.3 分佈式實現流程

上述兩種不同方式的分佈式實現流程是統一的

1.下載 scrapy-redis 組件

pip install scrapy-redis

2. redis 配置文件的配置

- linux或者mac:redis.conf
- windows:redis.windows.conf
修改
- 注釋該行:bind 127.0.0.1,表示可以讓其他ip訪問redis

- 將yes改為no: protected-mode no,表示可以讓其他ip操作redis

image

3. 修改爬蟲文件中的相關代碼

  • 將爬蟲類的父類修改成基於RedisSpider或者RedisCrawlSpider。

注意:如果原始爬蟲文件是基於Spider的,則應該將父類修改成RedisSpider,如果原始爬蟲文件是基於CrawlSpider的,則應該將其父類修改成RedisCrawlSpider。

  • 注釋或者刪除start_urls列表,且加入redis_key屬性,屬性值為scrpy-redis組件中調度器隊列的名稱

4. 在配置文件中進行相關配置,開啟使用scrapy-redis組件中封裝好的管道

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400
}

5. 在配置文件(setting)中進行相關配置,開啟使用scrapy-redis組件中封裝好的調度器

# 增加了一個去重容器類的配置, 作用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重的持久化
 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
 # 使用scrapy-redis組件自己的調度器
 SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。如果是True, 就表示要持久化存儲, 就不清空數據, 否則清空數據
 SCHEDULER_PERSIST = True

6. 在配置文件中進行爬蟲程序鏈接redis的配置

REDIS_HOST = 'redis服務的ip地址'
REDIS_PORT = 6379
REDIS_ENCODING ='utf-8'
REDIS_PARAMS = {'password':'xx'}

7. 運行爬蟲文件:scrapy runspider SpiderFile(x.py)

scrapy runspider xxx.py

8.向調度器隊列中扔入一個起始url(在redis客戶端中操作):lpush redis_key屬性值 起始url

八、布隆過濾器

pip install scrapy-redis-bloomfilter

# 替換scrapy_redis的去重類
DUPEFILTER_CLASS = "scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter"

# 替換原來的請求調度器的實現類,使用 scrapy-redis 中請求調度器
SCHEDULER = "scrapy_redis_bloomfilter.scheduler.Scheduler"

# 設置布隆過濾器散列函數的個數,默認為6,可以自行修改
BLOOMFILTER_HASH_NUMBER = 6

# Bloom Filter的bit參數,默認30,佔用128MB空間,可存儲數據量級1億
# BLOOMFILTER_BIT決定了位圖的位數。如果BLOOMFILTER_BIT為30,那麼位數組位數為2的30次方,這將佔用Redis 128 MB的存儲空間,去重量級在1億左右。
BLOOMFILTER_BIT = 30