前言
"又回到最初的起點,呆呆地站在鏡子前"。
本來這篇是打算寫Spider中間件的,但是因為這一塊涉及到Item,所以這篇文章先將Item講完,順便再講講Pipeline,然后再講Spider中間件。
Item和Pipeline
依舊是先上架構圖。
從架構圖中可以看出,當下載器從網站獲取了網頁響應內容,通過引擎又返回到了Spider程序中。我們在程序中將響應內容通過css或者xpath規則進行解析,然后構造成Item對象。
而Item和響應內容在傳遞到引擎的過程中,會被Spider中間件進行處理。最后Pipeline會將引擎傳遞過來的Item持久化存儲。
總結:Item是數據對象,Pipeline是數據管道。
Item
Item說白了就是一個類,里面包含數據字段。目的是為了讓你把從網頁解析出來的目標數據進行結構化。需要注意的是,我們通常要先確定Item的結構,然后再在程序中構造、在pipeline中處理。
這里依舊還是以斗羅大陸為例。
Item類定義
Item在items.py中定義。我們先看看此py文件中的Item定義模板。
如圖所示,即是模板,要點有二。
- Item類繼承scrapy.Item
- 字段 = 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功能有:
- 清理HTML數據
- 驗證數據(檢查item包含某些字段)
- 查重(並丟棄)
- 將爬取結果保存到數據庫
在實際開發中,4的場景比較多。
定義Pipeline
Pipeline定義在pipeline.py中,這里依舊先看看Pipeline給定的模板。
如圖,只實現了process_item()方法,來處理傳遞過來的Item。但是在實際開發中,我們通常要實現三個方法:
- __init__:用來構造對象屬性,例如數據庫連接等
- from_crawler:類方法,用來初始化變量
- 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)
這里要重點強調一下上面代碼中的幾個點。
- process_item()中為什么使用isinstance來判斷item的類型?
這個是為了解決多種Item經過同一個Pipiline時,需要調用不同的方法來進行數據庫操作的場景。如下圖所示:
不同的Item具有不同的結構,意味着需要不同的sql來插入到數據庫中,所以會先判斷Item類型,再調用對應方法處理。
- 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 = ['https://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,詳細且認真。文章會在公眾號 [入門到放棄之路] 首發,期待你的關注。