Python高級特性與網絡爬蟲(五):Scrapy框架簡介

Scrapy框架簡介

scrapy是一個基於Twisted的異步處理框架,是一個純Python實現的爬蟲框架,其架構清晰,模塊之間的耦合程度低,可擴展性很強,我們可以通過定製開發幾個模塊就可以實現一個功能強大的爬蟲。一個Scrapy框架如下圖所示主要由以下幾個部分組成:
在這裏插入圖片描述

  1. Engine:引擎,處理整個系統的數據流處理、觸發事務,是整個框架的核心
  2. Item:項目,它定義了爬取結果的數據結構,爬取的數據會被賦值成該Item對象
  3. Scheduler:調度器,接收引擎發過來的請求並將其加入到隊列中,在引擎再次請求時將請求提供給引擎
  4. Downloader:下載器,下載網頁內容,並將網頁內容返回給爬蟲(Spiders)
  5. Spiders:爬蟲,定義了爬取的邏輯和網頁的解析規則,負責解析響應並生成提取結果和新的請求
  6. Item Pipeline:項目管道,負責處理由Spiders從網頁中抽取的項目,主要任務爲清洗、驗證和存儲數據
  7. Downloader Middlewares:下載器中間件,位於引擎和下載器之間的鉤子框架,主要處理引擎與下載器之間的請求與響應
  8. Spider Middlewares:Spider中間件,位於引擎與Spider之間的鉤子框架,主要處理蜘蛛輸入的響應和輸出的結果以及新的請求

在scrapy中,一個數據流的流向或者說一次爬取的過程是這樣進行的:

  1. 首先在spider中編寫請求的url,相當於引擎問spider想處理那些request請求,這時spider就會做出迴應,將已編寫的request請求發送給engine引擎;
  2. engine引擎將spider發送過的的請求發給scheduler調度器,調度器會將request請求排序成隊列;
  3. engine引擎將會請求scheduler調度器是否已將request請求入隊,若已入隊,則scheduler調度器將請求隊列發送給engine引擎;
  4. engine引擎將scheduler調度器發送過來的的request請求隊列,發送給downloader下載器。downloader下載器若成功下了請求的內容,則將response發送給引擎,引擎再將response發送給spider進行處理。若downloader下載器下載出錯,則downloader下載器會將出錯的請求發送給engine引擎,engine引擎再講請求發送給scheduler調度器再次進行排隊調度;
  5. spider接收engine引擎發送過來的數據,對數據進行分析。該數據由兩部分組成,一部分是我們請求的數據,這部分數據會交給Item、Pipline進行數據存儲或者清洗;另一部分是新的請求,spider會將新的請求發送給引擎,然後引擎再將這些新的請求發送到調度器進行排隊。然後重複1、2、3、4操作,直到獲取到全部的信息爲止。

下面我們通過一個官方的scrapy測試網站http://quotes.toscrape.com爬取上面的text,author和tags存入MongoDB數據庫中,來了解一下具體scrapy項目代碼的構成
在這裏插入圖片描述

開始一個scrapy項目

大致介紹一下windows下scrapy的安裝過程(如果你的windows上的python是通過anaconda安裝的,直接conda install Scrapy即可安裝,否則參考後面的安裝過程),安裝scrapy前,先要安裝lxml庫(通過wheel方式安裝),pyOpenSSL(通過pip安裝),Twisted(通過pip安裝),pyWin32(從https://sourceforge.net/projects/pywin32網站下載exe安裝),安裝完以上依賴庫之後,就可以通過pip install scrapy安裝scrapy了,若沒有報錯,則表明安裝完成,在命令行輸入scrapy,出現如下畫面證明安裝完成
在這裏插入圖片描述
新建一個項目,可以在相應目錄的命令行下使用如下的命令:

scrapy startproject tutorial_lxy

之後該目錄下就會生成一個新的文件夾tutorial_lxy,文件夾的結構如下所示,接下來結合爬蟲項目的構建過程來分別對文件夾中的各個模塊進行介紹:
在這裏插入圖片描述

spiders

scrapy.cfg爲scrapy部署時的配置文件,tutorial_lxy下放置的每一個python文件都是一個Spider,scrapy用它從特定的網頁裏抓取內容,並解析抓取的結果,可以通過命令行創建一個Spider,比如我們要生成爬取quotes.toscrape.com的Spider,在外面的父tutorial_lxy目錄下執行如下命令:

scrapy genspider quotes quotes.toscrape.com

裏面的子tutorial_lxy目錄下就會生成一個quotes.py,即剛剛創建的Spider,內容如下所示(scrapy中所有自動生成的註釋都是英文的,我自己加的註釋都是中文註釋,不會自動生成):

class QuotesSpider(scrapy.Spider): #繼承自scrapy.Spider的爬取類
    name = 'quotes' #每個項目唯一的名字,用來區分不同的Spider
    allowed_domains = ['quotes.toscrape.com'] #允許爬取的域名,如果初始或後續的請求不是這個域名下的,會過濾掉該請求鏈接
    start_urls = ['http://quotes.toscrape.com/'] #Spider在啓動時爬取的url列表,初始的requests請求就是針對該列表內的鏈接的請求
	
	#被調用的start_urls裏面的鏈接構成的請求requests完成download Middlerware(定義在middlewares.py中)的操作後生成Response作爲parse的參數,可以在parse中定義對於response的解析等處理操作
    def parse(self, response):
        pass

items.py

items.py定義的是Item類,是一種保存爬取數據的容器,使用方法和字典類似,初始狀態如下所示:

import scrapy


class TutorialLxyItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass

創建的Item需要繼承scrapy.Item類,並且定義類型爲scrapy.Field的字段(如註釋所示),我們這次需要爬取的內容有text,author和tags,因此定義三個字段,如下所示:

import scrapy


class TutorialLxyItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()

接下來我們就可以在Spiders的quotes.QuotesSpider的parse方法中解析reponse的內容,並將其爬取到數據容器TutorialLxyItem中並展示出來,這裏我們使用css選擇器解析response的內容,將內容寫入一個個TutorialLxyItem中並生成(yield),並且通過選擇器得到下一頁的鏈接並生成請求(yield),代碼如下所示:

import scrapy
from tutorial_lxy.items import TutorialLxyItem

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        quotes=response.css('.quote')
        for quote in quotes:
            item=TutorialLxyItem()
            item['text']=quote.css('.text::text').extract_first()#獲取列表中的第一個元素
            item['author']=quote.css('.author::text').extract_first()
            item['tags']=quote.css('.tags .tag::text').extract()
            yield item
        next=response.css('.pager .next a::attr("href")').extract_first()
        url=response.urljoin(next)#獲取下一頁的鏈接
        yield scrapy.Request(url=url,callback=self.parse)#對下一頁的鏈接進行request請求並再次執行parse操作

此時在父tutorial_lxy中執行如下命令:

scrapy crawl quotes

即可爬取網頁上的內容並顯示,如下所示,最開始先會加載配置文件settings.py,之後就會加載middlewares.py中的Downloader Middlewares和Spider Middlewares(這兩個模塊的作用後面會簡要介紹),之後會加載pipelines中定義的pipelines(這裏在沒有定義pipelines時會爲空列表,後面爲了能夠將爬取結果存儲到MongoDB中需要定義相應的Pipelines),之後便是請求該網站的robots.txt,因爲沒有所以爲404,然後請求http://quotes.toscrape.com/開始爬取內容
在這裏插入圖片描述

pipelines.py

如果想要進行更復雜的操作,比如對爬取結果進行進行一些篩選操作並將爬取結果保存到MongoDB中,我們可以在pipelines.py中定義Item Pipeline來實現。Item Pipeline作爲項目管道,當Item生成後,它會自動被送入Item Pipeline中進行處理。pipelines.py文件的初始狀態如下所示:

class TutorialLxyPipeline:
    def process_item(self, item, spider):
        return item

我們可以看到要實現一個Item Pipeline需要定義一個類並含有process_item()方法,在Settings.py中啓用這個Item Pipeline之後,會自動調用這個方法,process_item()方法必須返回包含數據字典或Item對象,或者會拋出DropItem
異常。process_item()方法有兩個參數,一個參數是item,每次Spider生成的item都會作爲參數傳遞鍋來,另一個參數是spider,即Spider的實例。接下來我們在pipelines.py中實現兩個pipelines,一個是TutorialLxyPipeline,篩選掉text長度大於50的Item,另一個是 MongoPipeline,將結果保存到MongoDB中,代碼如下所示:

from scrapy.exceptions import DropItem
import pymongo

class TutorialLxyPipeline(object):
    def __init__(self):
        self.limit=50

    def process_item(self, item, spider):
        if item['text']:
            if len(item['text'])>self.limit:
                item['text']=item['text'][0:self.limit].rstrip()+'...'
            return item
        else:
            return DropItem('Missing Text')

class MongoPipeline(object):
    def __init__(self,mongo_uri,mongo_db):
        self.mongo_uri=mongo_uri
        self.mongo_db=mongo_db

    @classmethod
    def from_crawler(cls,crawler): #類方法classmethod,通過crawler獲取全局配置信息(在setting.py中)
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )

    def open_spider(self,spider): #Spider開啓時,調用該方法,初始化和MongoDB的連接
        self.client=pymongo.MongoClient(self.mongo_uri)
        self.db=self.client[self.mongo_db]

    def process_item(self,item,spider): 
        name=item.__class__.__name__ #獲取類名TutorialLxyItem
        self.db[name].insert(dict(item)) #插入數據
        return item

    def close_spider(self,spider):
        self.client.close()

其中MONGO_URI和MONGO_DB定義在settings.py中,需要在settings.py中添加如下代碼:

ITEM_PIPELINES = {
   'tutorial_lxy.pipelines.TutorialLxyPipeline': 300,
    'tutorial_lxy.pipelines.MongoPipeline': 400,
}
MONGO_URI='localhost'
MONGO_DB='tutorial_lxy'

此時再執行命令scrapy crawl quotes即可將爬取內容存如MongoDB中

middlewares.py簡介

middlewares.py中主要定義了各種中間件,比如Downloader Middleware和Spider Middleware,其中Downloader Middleware即下載中間件,他主要有以下兩個作用:

  1. 在Request執行下載之前對request的內容(如headers,proxy等)進行修改
  2. 在下載後生成的Response發送給Spider之前,對Response進行修改

每個Downloader Middleware主要有以下三個核心方法,即process_requests(request,spider),process_response(request,reponse,spider)和process_exception(requests,exception,spider),分別對應處理request,response和downloader或process_requests()拋出異常的處理.

Spider Middleware是介入到Scrapy和Spider處理機制的鉤子框架,當Downloader生成Response之後,Response會被髮送給Spider,在發送給Spider之前,Response會首先經過SpiderMiddleware處理,當Spider處理生成Item和Request之後,Item和Request還會經過Spider Middleware處理,所以 Spider Middleware主要有如下三個作用:

  1. 在Downloader生成的Response發送給Spider之前,也就是在Response發送給Spider之前對Response進行處理。
  2. 我們可以在Spider生成的Request發送給Scheduler之前,也就是在Request發送給Scheduler之前對Request進行處理。
  3. 我們可以在Spider生成的Item發送給Item Pipeline之前,也就是在Item發送給Item Pipeline之前對Item進行處理。

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

  1. process_spider_input(response,spider),當Response被Spider Middleware處理時調用,
  2. process_spider_exception(response,exception,spider),當Spider或Spider Middleware的process_spider_input()方法拋出異常時調用
  3. process _spider_output(response,result,spider),
    當Spider處理Response返回結果時調用
  4. process_start_requests (start_requests,spider),process_start_requests()方法以Spider啓動的Request爲參數被調用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章