爬蟲(十五):Scrapy框架(二) Selector、Spider、Downloader Middleware

1. Scrapy框架

1.1 Selector的用法

咱們以前介紹了利用Beautiful Soup、正則表達式來提取網頁數據,這確實很是方便。而Scrapy還提供了本身的數據提取方法,即Selector(選擇器)。Selector 是基於lxml來構建的,支持XPath選擇器、CSS選擇器以及正則表達式,功能全面,解析速度和準確度很是高。css

Selector是一個能夠獨立使用的模塊。咱們能夠直接利用Selector這個類來構建一個選擇器對象,而後調用它的相關方法如xpath()、css()等來提取數據。html

案例:web

from scrapy import Selector
body= '<html><head><title>Hello World</title></head><body></body></ html>'
selector = Selector(text=body)
title = selector.xpath('//title/text()').extract_first()
print(title) 

結果:正則表達式

咱們沒有在Scrapy框架中運行,而是把Scrapy中的Selector單獨拿出來使用了,構建的時候傳入text參數,就生成了Selector選擇器對象,而後就能夠像前面咱們所用的Scrapy中的解析方式同樣,調用xpath()、css()等方法來提取。架構

在這裏咱們查找的是源代碼中的title中的文本,在Path選擇器最後加 text()方法就能夠實現文本的提取了。框架

以上內容就是Selector的直接使用方式Beautiful Soup等庫相似,Selector其實也是強大的網頁解析庫。若是方便的話,咱們也能夠在其餘項目中直接使用Selector來提取數據。dom

Selector選擇器的使用能夠分爲三步:scrapy

導入選擇器from scrapy.selector import Selectoride

建立選擇器實例selector = Selector(response=response)函數

使用選擇器selector.xpath()或者selector.css()

不過Scrapy項目裏咱們能夠直接response.css()或response.xpath(),怎麼方便怎麼用。

1.2 Spider的用法

在Scrapy中,要抓取網站的連接配置、抓取邏輯、解析邏輯裏其實都是在Spider中配置的在上一章的實例中,咱們發現抓取邏輯也是在Spider中完成的。

1.2.1 Spider運行流程

在實現Scrapy爬蟲項目時,最核心的類即是Spider類了,它定義瞭如何爬取某個網站的流程和解析方式。簡單來講,Spider要作的事就是兩件:定義爬取網站的動做和分析爬取下來的網頁。

Spider循環爬取過程:

以初始的URL初始化Request,並設置回調函數。當該Request成功請求並返回時,Response生成並做爲參數傳給該回調函數。

在回調函數內分析返回的網頁內容。返回結果有兩種形式。一種是解析到的有效結果返回字典或Item對象,它們能夠通過處理後(或直接)保存。另外一種是解析獲得下一個(以下頁)連接,能夠利用此連接構造Reque並設置新的回調函數,返回Request等待後續調度。

若是返回的是字典或Item對象,咱們可經過Feed Exports等組件將返回結果存入到文件。若是設置了Pipeline的話,咱們可使用Pipeline處理(如過濾、修正等)並保存。

若是返回的是Reqeust,那麼Request執行成功獲得Response以後,Response會被傳遞給Request中定義的回調函數,在回調函數中咱們能夠再次使用選擇器來分析新獲得的網頁內 容,並根據分析的數據生成Item。

經過以上幾步循環往復進行,咱們完成了站點的爬取。

1.2.2 Spider類分析

在上一章的例子中,咱們定義的Spider是繼承自scrapy.spiders.Spider。scrapy.spiders.Spider這個類是最簡單最基本的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的配置,此設置會覆蓋項目全局的設置。此設置必須在初始化前被更新,必須定義成類變量。

crawler:它是由from_crawler()方法設置的,表明的是本Spider類對應的Crawler對象。Crawler對象包含了不少項目組件,利用它咱們能夠獲取項目的一些配置信息,如最多見的獲取項目的設置信息,即Settings。

settings:它是一個Settings對象,利用它咱們能夠直接獲取項目的全局設置變量。

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

start_requests():此方法用於生成初始請求,它必須返回一個可迭代對象。此方法會默認使用start_urls裏面的URL來構造Request,並且Request是GET請求方式。若是咱們想在啓動時以POST方式訪問某個站點,能夠直接重寫這個方法,發送 POST請求時使用FormRequest便可。

parse():當Response沒有指定回調函數時,該方法會默認被調用。它負責處理Response處理返回結果,並從巾提取處想要的數據和下一步的請求,而後返回。該方法須要返回一個包含Request或ltem的可迭代對象。

closed():當Spider關閉時,該方法會被調用,在這裏通常會定義釋放資源的一些操做或其餘收尾操做。

1.3 Downloader Middleware的用法

Downloader Middleware即下載中間件,它是處於Scrapy的Request和Response之間的處理模塊。

咱們上一章已經看過Scrapy框架的架構了。

Scheduler從隊列中拿出一個Request發送給Downloader執行下載,這個過程會通過Downloader Middleware的處理。另外,當Downloader將Request下載完成獲得Response返回給Spider時會再次通過Downloader Middleware處理。

也就是說,Downloader Middleware在整個架構中起做用的位置有兩個,分別是:

在Scheduler調度出隊列的Request發送給Doanloader下載以前,也就是咱們能夠在Request執行下載以前對其進行修改。

在下載後生成的Response發送給Spider以前,也就是咱們能夠在生成Resposne被Spider解析以前對其進行修改。

Downloader Middleware的功能很是強大,修改User-Agent處理重定向、設置代理、失敗重試、設置 Cookies等功能都須要藉助它來實現。

1.3.1 使用說明

Scrapy其實已經提供了許多Downloader Middleware,好比負責失敗重試、自動重定向等功能的Middleware,它們被DOWNLOADER_MIDDLEWARES_BASE變量所定義。

官網:https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/settings.html#std:setting-DOWNLOADER_MIDDLEWARES_BASE

DOWNLOADER_MIDDLEWARES_BASE變量的內容以下所示:

 

 

這是一個字典格式,字典的鍵名是Scrapy內置的Downloader Middleware的名稱,鍵值表明了調用的優先級,優先級是一個數字,數字越小表明越靠近Scrapy引擎,數字越大表明越靠近Downloader,數字小的Downloader Middleware會被優先調用。

若是向己定義的Downloader Middleware要添加到項目裏,DOWNLOADER_MIDDLEWARES_BASE變量不能直接修改。Scrapy提供了另一個設置變量DOWNLOADER_MIDDLEWARES,咱們直接修改這個變量就能夠添加本身定義的DownloaderMiddleware,以及禁用DOWNLOADER_MIDDLEWARES_BASE裏面定義的Downloader Middleware。

1.3.2 核心方法

Scrapy內置的Downloader Middleware爲Scrapy提供了基礎的功能,但在項目實戰中咱們每每須要單獨定義Downloader Middleware。不用擔憂,這個過程很是簡單,咱們只須要實現某幾個方法便可。

每一個Downloader Middleware都定義了一個或多個方法的類,核心的方法有以下三個:

process_request(request,spider)

process_response(request,response,spider)

pro cess_exception(request,exception,spider)

咱們只須要實現至少一個方法,就能夠定義一個Downloader Middleware下面咱們來看看這三個方法的詳細用法。

(1) process_request(request,spider)

Request被Scrapy引擎調度給Downloader以前,process_request()方法就會被調用,也就是在Request從隊列裏調度出來到Downloader下載執行以前,咱們均可以用process_request()方法對 Request進行處理。方法的返回值必須爲None、Response對象、Request對象之一,或者拋出IgnoreRequest異常。

process_request()方法的參數有以下兩個:

request,是Request對象,即被處理的Request。

spider,是Spdier對象,即此Request對應的Spider。

返回類型不一樣,產生的效果也不一樣。下面概括一下不一樣的返回狀況。

  • 當返回是None時,Scrapy將繼續處理該Request,接着執行其餘Downloader Middleware的process_request()方法,一直到Downloader把Request執行後獲得Response才結束。這個過程其實就是修改Request的過程,不一樣的Downloader Middleware按照設置的優先級順序依次對Request進行修改,最後送至Downloader執行。
  • 當返回爲Response對象時,更低優先級的Downloader Middleware的process_request()和process_exception()方法就不會被繼續調用,每一個Downloader Middleware的process_response()方法轉而被依次調用。調用完畢以後,直接將Response對象發送給Spider來處理。
  • 當返回爲Request對象時,更低優先級的Downloader Middleware的process_request()方法會中止執行。這個Request會從新放到調度隊列裏,其實它就是一個全新的Request,等待被調度。若是被Scheduler調度了,那麼全部的Downloader Middleware的process_request()方法會被從新按照順序執行。
  • 若是IgnoreRequest異常拋出,則全部的Downloader Middleware的process_exception()方法會依次執行。若是沒有一個方法處理這個異常,那麼Request的errorback()方法就會回調。若是該異常尚未被處理,那麼它便會被忽略。

(2) process_response (request, response,spider)

Downloader執行Request下載以後,會獲得對應的Response。Scrapy引擎便會將Response發送給 Spider進行解析。在發送以前,咱們均可以用process_response()方法來對Response進行處理。方法的返回值必須爲Request對象、Response對象之一,或者拋出IgnoreRequest異常。

process_response()方法的參數有以下三個:

request,是Request對象,即此Response對應的Request。

response,是Response對象,即此被處理的Response。

spider,是Spider對象,即此Response對應的Spider。

下面概括下不一樣的返回狀況。

  • 當返回爲Request對象時,更低優先級的Downloader Middleware的process_response()方法不會繼續調用。該Request對象會從新放到調度隊列裏等待被調度,它至關於一個全新的Request。而後,該Request會被process_request()方法依次處理。
  • 當返回爲Response對象時,更低優先級的Downloader Middleware的process_response()方法會繼續調用,繼續對該Response對象進行處理。
  • 若是IgnoreRequest異常拋出,則Request的errorback()方法會回調。若是該異常尚未被處理,那麼它便會被忽略。

(3) process_exception(request,exception,spider)

當Downloader或process_request()方法拋出異常時,例如拋出IgnoreRequest異常,process_exception()方法就會被調用。方法的返回值必須爲None、Response對象、Request對象之一。

process_exception()方法的參數有以下:

request,是Request對象,即產生異常的Request。

exception,是Exception對象,即拋出的異常。

spdier,是Spider對象,即Request對應的Spider。

下面概括一下不一樣的返回狀況。

  • 當返回爲None時,更低優先級的Downloader Middleware的process_exception()會被繼續依次調用,直到全部的方法都被調度完畢。
  • 當返回爲Response對象時,更低優先級的Downloader Middleware的process_exception()方法再也不被繼續調用,每一個Downloader Middleware的process_response()方法轉而被依次調用。
  • 當返回爲Request對象時,更低優先級的Downloader Middleware的process_exception()也再也不被繼續調用,該Request對象會從新放到調度隊列裏面等待被調度,它至關於一個全新的Request。而後,該Request又會被process_request()方法依次處理。

以上內容即是這三個方法的詳細使用邏輯。在使用它們以前,請先對這三個方法的返回值的處理狀況有一個清晰的認識。在自定義Downloader Middleware的時候,也必定要注意每一個方法的返回類型。

1.3.3 項目實戰

新建一個項目。

scrapy startproject scrapydownloadertest

新建了一個Scrapy項目,名爲scrapydownloadertest。進入項目,新建一個Spider。

scrapy genspider httpbin httpbin.org

新建了一個Spider,名爲httpbin。

# -*- coding: utf-8 -*-
import scrapy

class HttpbinSpider(scrapy.Spider):
    name = 'httpbin'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/']

    def parse(self, response):
        pass

接下來咱們修改start_urls,將parse()方法添加一行日誌輸出,將response變量的text屬性輸出出來,這樣咱們即可以看到Scrapy發送的Request信息了。

修改Spider內容以下:

# -*- coding: utf-8 -*-
import scrapy


class HttpbinSpider(scrapy.Spider):
    name = 'httpbin'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/get']

    def parse(self, response):
        self.logger.debug(response.text)

接下來運行此Spider。

scrapy crawl httpbin

Scrapy運行結果包含Scrapy發送的Request信息,內容以下:

 

咱們觀察一下Headers,Scrapy發送的Request使用的User-Agent是Scrapy/1.8.0(+http: //scrapy.org),這實際上是由Scrapy內置的UserAgentMiddleware設置的,UserAgentMiddleware的源碼以下:

 

在from_crawler()方法中,首先嚐試獲取settings裏面USER_AGENT,而後把USER_AGENT傳遞給__init__()方法進行初始化,其參數就是user_agent。若是沒有傳遞USER_AGENT參數就是默認設置爲Scrapy字符串。咱們新建的項目沒有設置USER_AGENT,因此這裏的user_agent變量就是Scrapy。接下來,在process_request()方法中,將user-agent變量設置爲headers變量的一個屬性,這樣就成功設置了User-Agent。所以,User-Agent就是經過此Downloader Middleware的process_request()方法設置的。

修改請求時的User-Agent能夠有兩種方式:一是修改settings裏面的USER_AGENT變量;二是經過Downloader Middleware的process_request()方法來修改。

第一種方法很是簡單,咱們只須要在setting.py裏面加一行USER_AGENT的定義便可:

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'

通常推薦使用這種方法來設置。可是若是想設置得更加靈活,好比設置隨機的User-Agent的設置。

第二種方法,在middlewares.py裏添加一個RandomUserAgentMiddleware的類。

import random

class RandomUserAgentMiddlerware():
    def __init__(self):
        self.user_agents = [
            #放幾個User-Agent在裏面
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36'
        ]
    def process_request(self,request,spider):
        request.headers['User-Agent'] = random.choice(self.user_agents)

咱們首先在類的__init__()方法中定義幾個不一樣的User-Agent,並用一個列表來表示。接下來實現了process_request()方法,它有一個參數request,咱們直接修改request的屬性便可。在這裏咱們直接設置了request變量的headers屬性的User-Agent,設置內容是隨機選擇的User-Agent,這樣一個Downloader Middleware就寫好了。

不過,要使之生效咱們還須要再去調用這個Downloader Middleware。在settings.py中,將DOWNLOADER_MIDDLEWARES取消註釋,並設置成以下內容:

DOWNLOADER_MIDDLEWARES = {
    'scrapydownloadertest.middlewares.RandomUserAgentMiddleware':543
}

而後從新運行Spider,就能夠看到User-Agent被成功修改成列表中所定義的隨機的一個User-Agent了。

咱們就經過實現Downloader Middleware並利用process_request()方法成功設置了隨機的User-Agent。

另外,Downloader Middleware還有process_response()方法。Downloader對Request執行下載以後會獲得Response,隨後Scrapy引擎會將Response發送回Spider進行處理。可是在Response被髮送給Spider以前,咱們一樣可使用process_response()方法對Response進行處理。好比這裏修改一下Response的狀態碼,在RandomUserAgentMiddleware添加以下代碼:

def process_response(self,request,response,spider):
    response.status = 201
    return response

咱們將response變量的status屬性修改成201,隨後將response返回,這個被修改後的Response就會被髮送到Spider。

咱們再在Spider裏面輸出修改後的狀態碼,在parse()方法中添加以下的輸出語句:

self.logger.debug('Status Code :' + str(response.status)) 

從新運行以後,控制檯輸出了以下內容:

[httpbin] DEBUG: Status Code: 201

能夠發現,Response的狀態碼成功修改了。

所以要想對Response進行後處理,就能夠藉助於process_response()方法。

另外還有一個process_exception()方法,它是用來處理異常的方法。若是須要異常處理的話,咱們能夠調用此方法。不過這個方法的使用頻率相對低一些,這裏就不談了。

相關文章
相關標籤/搜索