Item Pipeline是項目管道,本節咱們詳細瞭解它的用法。
html
首先咱們看看Item Pipeline在Scrapy中的架構,以下圖所示。mysql
圖中的最左側即爲Item Pipeline,它的調用發生在Spider產生Item以後。當Spider解析完Response以後,Item就會傳遞到Item Pipeline,被定義的Item Pipeline組件會順次調用,完成一連串的處理過程,好比數據清洗、存儲等。git
Item Pipeline的主要功能有以下4點。github
清理HTML數據。sql
驗證爬取數據,檢查爬取字段。數據庫
查重並丟棄重複內容。json
將爬取結果保存到數據庫。瀏覽器
咱們能夠自定義Item Pipeline,只須要實現指定的方法,其中必需要實現的一個方法是: process_item(item, spider)
。
bash
另外還有以下幾個比較實用的方法。微信
open_spider(spider)
。
close_spider(spider)
。
from_crawler(cls, crawler)
。
下面咱們詳細介紹這幾個方法的用法。
process_item()
是必需要實現的方法,被定義的Item Pipeline會默認調用這個方法對Item進行處理。好比,咱們能夠進行數據處理或者將數據寫入到數據庫等操做。它必須返回Item
類型的值或者拋出一個DropItem
異常。
process_item()
方法的參數有以下兩個。
item
,是Item對象,即被處理的Item。
spider
,是Spider對象,即生成該Item的Spider。
process_item()
方法的返回類型概括以下。
若是它返回的是Item對象,那麼此Item會被低優先級的Item Pipeline的process_item()
方法處理,直到全部的方法被調用完畢。
若是它拋出的是DropItem異常,那麼此Item會被丟棄,再也不進行處理。
open_spider()
方法是在Spider開啓的時候被自動調用的。在這裏咱們能夠作一些初始化操做,如開啓數據庫鏈接等。其中,參數spider
就是被開啓的Spider對象。
close_spider()
方法是在Spider關閉的時候自動調用的。在這裏咱們能夠作一些收尾工做,如關閉數據庫鏈接等。其中,參數spider
就是被關閉的Spider對象。
from_crawler()
方法是一個類方法,用@classmethod
標識,是一種依賴注入的方式。它的參數是crawler
,經過crawler
對象,咱們能夠拿到Scrapy的全部核心組件,如全局配置的每一個信息,而後建立一個Pipeline實例。參數cls
就是Class,最後返回一個Class實例。
下面咱們用一個實例來加深對Item Pipeline用法的理解。
咱們以爬取360攝影美圖爲例,來分別實現MongoDB存儲、MySQL存儲、Image圖片存儲的三個Pipeline。
請確保已經安裝好MongoDB和MySQL數據庫,安裝好Python的PyMongo、PyMySQL、Scrapy框架。
咱們此次爬取的目標網站爲:https://image.so.com。打開此頁面,切換到攝影頁面,網頁中呈現了許許多多的攝影美圖。咱們打開瀏覽器開發者工具,過濾器切換到XHR選項,而後下拉頁面,能夠看到下面就會呈現許多Ajax請求,以下圖所示。
咱們查看一個請求的詳情,觀察返回的數據結構,以下圖所示。
返回格式是JSON。其中list
字段就是一張張圖片的詳情信息,包含了30張圖片的ID、名稱、連接、縮略圖等信息。另外觀察Ajax請求的參數信息,有一個參數sn
一直在變化,這個參數很明顯就是偏移量。當sn
爲30時,返回的是前30張圖片,sn爲60時,返回的就是第31~60張圖片。另外,ch
參數是攝影類別,listtype
是排序方式,temp
參數能夠忽略。
因此咱們抓取時只須要改變sn
的數值就行了。
下面咱們用Scrapy來實現圖片的抓取,將圖片的信息保存到MongoDB、MySQL,同時將圖片存儲到本地。
首先新建一個項目,命令以下所示:
scrapy startproject images360複製代碼
接下來新建一個Spider,命令以下所示:
scrapy genspider images images.so.com複製代碼
這樣咱們就成功建立了一個Spider。
接下來定義爬取的頁數。好比爬取50頁、每頁30張,也就是1500張圖片,咱們能夠先在settings.py裏面定義一個變量MAX_PAGE
,添加以下定義:
MAX_PAGE = 50複製代碼
定義start_requests()
方法,用來生成50次請求,以下所示:
def start_requests(self):
data = {'ch': 'photography', 'listtype': 'new'}
base_url = 'https://image.so.com/zj?'
for page in range(1, self.settings.get('MAX_PAGE') + 1):
data['sn'] = page * 30
params = urlencode(data)
url = base_url + params
yield Request(url, self.parse)複製代碼
在這裏咱們首先定義了初始的兩個參數,sn
參數是遍歷循環生成的。而後利用urlencode()
方法將字典轉化爲URL的GET
參數,構造出完整的URL,構造並生成Request。
還須要引入scrapy.Request和urllib.parse模塊,以下所示:
from scrapy import Spider, Request
from urllib.parse import urlencode複製代碼
再修改settings.py中的ROBOTSTXT_OBEY
變量,將其設置爲False
,不然沒法抓取,以下所示:
ROBOTSTXT_OBEY = False複製代碼
運行爬蟲,便可以看到連接都請求成功,執行命令以下所示:
scrapy crawl images複製代碼
運行示例結果以下圖所示。
全部請求的狀態碼都是200,這就證實圖片信息爬取成功了。
首先定義一個Item,叫做ImageItem
,以下所示:
from scrapy import Item, Field
class ImageItem(Item):
collection = table = 'images'
id = Field()
url = Field()
title = Field()
thumb = Field()複製代碼
在這裏咱們定義了4個字段,包括圖片的ID、連接、標題、縮略圖。另外還有兩個屬性collection
和table
,都定義爲images字符串,分別表明MongoDB存儲的Collection名稱和MySQL存儲的表名稱。
接下來咱們提取Spider裏有關信息,將parse()
方法改寫爲以下所示:
def parse(self, response):
result = json.loads(response.text)
for image in result.get('list'):
item = ImageItem()
item['id'] = image.get('imageid')
item['url'] = image.get('qhimg_url')
item['title'] = image.get('group_title')
item['thumb'] = image.get('qhimg_thumb_url')
yield item複製代碼
首先解析JSON,遍歷其list字段,取出一個個圖片信息,而後再對ImageItem
賦值,生成Item對象。
這樣咱們就完成了信息的提取。
接下來咱們須要將圖片的信息保存到MongoDB、MySQL,同時將圖片保存到本地。
首先確保MongoDB已經正常安裝而且正常運行。
咱們用一個MongoPipeline將信息保存到MongoDB,在pipelines.py裏添加以下類的實現:
import pymongo
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):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DB')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
self.db[item.collection].insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()複製代碼
這裏須要用到兩個變量,MONGO_URI
和MONGO_DB
,即存儲到MongoDB的連接地址和數據庫名稱。咱們在settings.py裏添加這兩個變量,以下所示:
MONGO_URI = 'localhost'
MONGO_DB = 'images360'複製代碼
這樣一個保存到MongoDB的Pipeline的就建立好了。這裏最主要的方法是process_item()
方法,直接調用Collection對象的insert()
方法便可完成數據的插入,最後返回Item對象。
首先確保MySQL已經正確安裝而且正常運行。
新建一個數據庫,名字仍是images360,SQL語句以下所示:
CREATE DATABASE images360 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci複製代碼
新建一個數據表,包含id、url、title、thumb四個字段,SQL語句以下所示:
CREATE TABLE images (id VARCHAR(255) NULL PRIMARY KEY, url VARCHAR(255) NULL , title VARCHAR(255) NULL , thumb VARCHAR(255) NULL)複製代碼
執行完SQL語句以後,咱們就成功建立好了數據表。接下來就能夠往表裏存儲數據了。
接下來咱們實現一個MySQLPipeline
,代碼以下所示:
import pymysql
class MysqlPipeline():
def __init__(self, host, database, user, password, port):
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
@classmethod
def from_crawler(cls, crawler):
return cls(
host=crawler.settings.get('MYSQL_HOST'),
database=crawler.settings.get('MYSQL_DATABASE'),
user=crawler.settings.get('MYSQL_USER'),
password=crawler.settings.get('MYSQL_PASSWORD'),
port=crawler.settings.get('MYSQL_PORT'),
)
def open_spider(self, spider):
self.db = pymysql.connect(self.host, self.user, self.password, self.database, charset='utf8', port=self.port)
self.cursor = self.db.cursor()
def close_spider(self, spider):
self.db.close()
def process_item(self, item, spider):
data = dict(item)
keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))
sql = 'insert into %s (%s) values (%s)' % (item.table, keys, values)
self.cursor.execute(sql, tuple(data.values()))
self.db.commit()
return item複製代碼
如前所述,這裏用到的數據插入方法是一個動態構造SQL語句的方法。
這裏又須要幾個MySQL的配置,咱們在settings.py裏添加幾個變量,以下所示:
MYSQL_HOST = 'localhost'
MYSQL_DATABASE = 'images360'
MYSQL_PORT = 3306
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'複製代碼
這裏分別定義了MySQL的地址、數據庫名稱、端口、用戶名、密碼。
這樣,MySQL Pipeline就完成了。
Scrapy提供了專門處理下載的Pipeline,包括文件下載和圖片下載。下載文件和圖片的原理與抓取頁面的原理同樣,所以下載過程支持異步和多線程,下載十分高效。下面咱們來看看具體的實現過程。
官方文檔地址爲:https://doc.scrapy.org/en/latest/topics/media-pipeline.html。
首先定義存儲文件的路徑,須要定義一個IMAGES_STORE
變量,在settings.py中添加以下代碼:
IMAGES_STORE = './images'複製代碼
在這裏咱們將路徑定義爲當前路徑下的images子文件夾,即下載的圖片都會保存到本項目的images文件夾中。
內置的ImagesPipeline
會默認讀取Item的image_urls
字段,並認爲該字段是一個列表形式,它會遍歷Item的image_urls
字段,而後取出每一個URL進行圖片下載。
可是如今生成的Item的圖片連接字段並非image_urls
字段表示的,也不是列表形式,而是單個的URL。因此爲了實現下載,咱們須要從新定義下載的部分邏輯,即要自定義ImagePipeline
,繼承內置的ImagesPipeline
,重寫幾個方法。
咱們定義ImagePipeline
,以下所示:
from scrapy import Request
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline
class ImagePipeline(ImagesPipeline):
def file_path(self, request, response=None, info=None):
url = request.url
file_name = url.split('/')[-1]
return file_name
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem('Image Downloaded Failed')
return item
def get_media_requests(self, item, info):
yield Request(item['url'])複製代碼
在這裏咱們實現了ImagePipeline
,繼承Scrapy內置的ImagesPipeline
,重寫下面幾個方法。
get_media_requests()
。它的第一個參數item
是爬取生成的Item對象。咱們將它的url
字段取出來,而後直接生成Request對象。此Request加入到調度隊列,等待被調度,執行下載。
file_path()
。它的第一個參數request
就是當前下載對應的Request對象。這個方法用來返回保存的文件名,直接將圖片連接的最後一部分看成文件名便可。它利用split()
函數分割連接並提取最後一部分,返回結果。這樣此圖片下載以後保存的名稱就是該函數返回的文件名。
item_completed()
,它是當單個Item完成下載時的處理方法。由於並非每張圖片都會下載成功,因此咱們須要分析下載結果並剔除下載失敗的圖片。若是某張圖片下載失敗,那麼咱們就不需保存此Item到數據庫。該方法的第一個參數results
就是該Item對應的下載結果,它是一個列表形式,列表每個元素是一個元組,其中包含了下載成功或失敗的信息。這裏咱們遍歷下載結果找出全部成功的下載列表。若是列表爲空,那麼該Item對應的圖片下載失敗,隨即拋出異常DropItem,該Item忽略。不然返回該Item,說明此Item有效。
如今爲止,三個Item Pipeline的定義就完成了。最後只須要啓用就能夠了,修改settings.py,設置ITEM_PIPELINES
,以下所示:
ITEM_PIPELINES = {
'images360.pipelines.ImagePipeline': 300,
'images360.pipelines.MongoPipeline': 301,
'images360.pipelines.MysqlPipeline': 302,
}複製代碼
這裏注意調用的順序。咱們須要優先調用ImagePipeline
對Item作下載後的篩選,下載失敗的Item就直接忽略,它們就不會保存到MongoDB和MySQL裏。隨後再調用其餘兩個存儲的Pipeline,這樣就能確保存入數據庫的圖片都是下載成功的。
接下來運行程序,執行爬取,以下所示:
scrapy crawl images複製代碼
爬蟲一邊爬取一邊下載,下載速度很是快,對應的輸出日誌以下圖所示。
查看本地images文件夾,發現圖片都已經成功下載,以下圖所示。
查看MySQL,下載成功的圖片信息也已成功保存,以下圖所示。
查看MongoDB,下載成功的圖片信息一樣已成功保存,以下圖所示。
這樣咱們就能夠成功實現圖片的下載並把圖片的信息存入數據庫。
本節代碼地址爲:https://github.com/Python3WebSpider/Images360。
Item Pipeline是Scrapy很是重要的組件,數據存儲幾乎都是經過此組件實現的。請讀者認真掌握此內容。
本資源首發於崔慶才的我的博客靜覓: Python3網絡爬蟲開發實戰教程 | 靜覓
如想了解更多爬蟲資訊,請關注個人我的微信公衆號:進擊的Coder
weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)