Scrapy入門到放棄05:讓Item在Pipeline中飛一會兒

前言

“又回到最初的起點,獃獃地站在鏡子前”。

本來這篇是打算寫Spider中間件的,但是因為這一塊涉及到Item,所以這篇文章先將Item講完,順便再講講Pipeline,然後再講Spider中間件。

Item和Pipeline

依舊是先上架構圖。

Scrapy架構

從架構圖中可以看出,當下載器從網站獲取了網頁響應內容,通過引擎又返回到了Spider程序中。我們在程序中將響應內容通過css或者xpath規則進行解析,然後構造成Item對象。

而Item和響應內容在傳遞到引擎的過程中,會被Spider中間件進行處理。最後Pipeline會將引擎傳遞過來的Item持久化存儲。

總結:Item是數據對象,Pipeline是數據管道。

四大模塊

Item

Item說白了就是一個類,裏面包含數據字段。目的是為了讓你把從網頁解析出來的目標數據進行結構化。需要注意的是,我們通常要先確定Item的結構,然後再在程序中構造、在pipeline中處理。

這裡依舊還是以斗羅大陸為例。

Item類定義

Item在items.py中定義。我們先看看此py文件中的Item定義模板。

items.py

如圖所示,即是模板,要點有二。

  1. Item類繼承scrapy.Item
  2. 字段 = scrapy.Field()

這裡根據我們在斗羅大陸頁面需要採集的數據字段,進行Item定義。

class DouLuoDaLuItem(scrapy.Item):
    name = scrapy.Field()
    alias = scrapy.Field()
    area = scrapy.Field()
    parts = scrapy.Field()
    year = scrapy.Field()
    update = scrapy.Field()
    describe = scrapy.Field()

Item數據構造

當我們將Item類定義之後,就要在spider程序中進行構造,即填充數據。

# 導入Item類,ScrapyDemo是包名
from ScrapyDemo.items import DouLuoDaLuItem
# 構造Item對象
item = DouLuoDaLuItem
item['name'] = name
item['alias'] = alias
item['area'] = area
item['parts'] = parts
item['year'] = year
item['update'] = update
item['describe'] = describe

代碼如上,一個Item數據對象就被構造完成。

發射Item到Pipeline

在Item對象構造完成之後,還需要一行代碼就能將Item傳遞到Pipeline中。

yield item

至此,Pipeline,我來了。

Pipeline

Pipeline直譯就是管道,負責處理Item數據,從而實現持久化。說白了就是將數據放到各種形式的文件、數據庫中。

功能

官方給出的Pipeline功能有:

  1. 清理HTML數據
  2. 驗證數據(檢查item包含某些字段)
  3. 查重(並丟棄)
  4. 將爬取結果保存到數據庫

在實際開發中,4的場景比較多。

定義Pipeline

Pipeline定義在pipeline.py中,這裡依舊先看看Pipeline給定的模板。

如圖,只實現了process_item()方法,來處理傳遞過來的Item。但是在實際開發中,我們通常要實現三個方法:

  1. __init__:用來構造對象屬性,例如數據庫連接等
  2. from_crawler:類方法,用來初始化變量
  3. process_item:核心邏輯代碼,處理Item

這裡,我們就自定義一個Pipeline,將Item數據放入數據庫。

配置Pipeline

和middleware一樣在settings.py中進行配置,這裡對應的是ITEM_PIPELINE參數。

ITEM_PIPELINES = {
    'ScrapyDemo.pipelines.CustomDoLuoDaLuPipeline': 300
}

Key依舊對應的是類全路徑,Value為優先級,數字越小,優先級越高。Item會根據優先級依此通過每個Pipeline,這樣可以在每個Pipeline中對Item進行處理。

為了直觀,後續我將Pipeline在代碼中進行局部配置。

pipeline連接數據庫

1. 配置數據庫屬性

我們首先在setttings.py中將數據庫的IP、賬號、密碼、數據庫名稱配置,這樣在pipeline中就可直接讀取,並創建連接。

MYSQL_HOST = '175.27.xx.xx'
MYSQL_DBNAME = 'scrapy'
MYSQL_USER = 'root'
MYSQL_PASSWORD = 'root'

2. 定義pipeline

主要使用pymysql驅動連接數據庫、twisted的adbapi來異步操作數據庫,這裡異步劃重點,基本上異步就是效率、快的代名詞。

import pymysql
from twisted.enterprise import adbapi
from ScrapyDemo.items import DouLuoDaLuItem


class CustomDoLuoDaLuPipeline(object):

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

    @classmethod
    def from_crawler(cls, crawler):
        # 讀取settings中的配置
        params = dict(
            host=crawler.settings['MYSQL_HOST'],
            db=crawler.settings['MYSQL_DBNAME'],
            user=crawler.settings['MYSQL_USER'],
            passwd=crawler.settings['MYSQL_PASSWORD'],
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
            use_unicode=False
        )
        # 創建連接池,pymysql為使用的連接模塊
        dbpool = adbapi.ConnectionPool('pymysql', **params)
        return cls(dbpool)

    def process_item(self, item, spider):
        if isinstance(item, DouLuoDaLuItem):
            query = self.dbpool.runInteraction(self.do_insert, item)
            query.addErrback(self.handle_error, item, spider)
        return item
        
    # 執行數據庫操作的回調函數
    def do_insert(self, cursor, item):
        sql = 'insert into DLDLItem(name, alias, area, parts, year, `update`, `describe`) values (%s, %s, %s, %s, %s, %s, %s)'
        params = (item['name'], item['alias'], item['area'], item['parts'], item['year'], item['update'], item['describe'])
        cursor.execute(sql, params)

    # 當數據庫操作失敗的回調函數
    def handle_error(self, failue, item, spider):
        print(failue)

這裡要重點強調一下上面代碼中的幾個點。

  1. process_item()中為什麼使用isinstance來判斷item的類型?

這個是為了解決多種Item經過同一個Pipiline時,需要調用不同的方法來進行數據庫操作的場景。如下圖所示:

不同的Item具有不同的結構,意味着需要不同的sql來插入到數據庫中,所以會先判斷Item類型,再調用對應方法處理。

  1. sql中update、describe字段為什麼要加反引號?

update、describe和select一樣,都是MySQL的關鍵字,所以如果想要在字段中使用這些單詞,在執行sql和建表語句匯總都要加上反引號,否則就會報錯。

3. 生成Item放入pipeline

即將迎面而來的依舊是熟悉的代碼,Item結構在上面的items.py中已經定義。pipeline也將在代碼內局部配置,這個不清楚的可以看第二篇文章。

import scrapy
from ScrapyDemo.items import DouLuoDaLuItem

class DouLuoDaLuSpider(scrapy.Spider):
    name = 'DouLuoDaLu'
    allowed_domains = ['v.qq.com']
    start_urls = ['//v.qq.com/detail/m/m441e3rjq9kwpsc.html']

    custom_settings = {
        'ITEM_PIPELINES': {
            'ScrapyDemo.pipelines.CustomDoLuoDaLuPipeline': 300
        }
    }

    def parse(self, response):
        name = response.css('h1.video_title_cn a::text').extract()[0]
        common = response.css('span.type_txt::text').extract()
        alias, area, parts, year, update = common[0], common[1], common[2], common[3], common[4]
        describe = response.css('span._desc_txt_lineHight::text').extract()[0]
        item = DouLuoDaLuItem()
        item['name'] = name
        item['alias'] = alias
        item['area'] = area
        item['parts'] = parts
        item['year'] = year
        item['update'] = update
        item['describe'] = describe
        print(item)
        yield item

4.程序測試

啟動程序,可以看到控制台打印了已經啟用的pipeline列表,同時也可以看到item的內容。程序執行結束後,我們去數據庫查看數據是否已經放到數據庫。

如圖,在數據庫的DLDLItem表中已經可以查到數據。

結語

Item和Pipeline讓數據結構存儲流程化,我們可以定義並配置多個Pipeline,當yield item之後,數據就會根據存儲在文件里、數據庫里

與之相關的還有一個ItemLoaders,我基本上沒有用過,但是後面還是當做擴展來寫一下。期待下一次相遇。


95後小程序員,寫的都是日常工作中的親身實踐,置身於初學者的角度從0寫到1,詳細且認真。文章會在公眾號 [入門到放棄之路] 首發,期待你的關注。

感謝每一份關注