Item Pipeline是專案管道,本節我們詳細瞭解它的用法。
首先我們看看Item Pipeline在Scrapy中的架構,如下圖所示。
圖中的最左側即為Item Pipeline,它的呼叫發生在Spider產生Item之後。當Spider解析完Response之後,Item就會傳遞到Item Pipeline,被定義的Item Pipeline元件會順次呼叫,完成一連串的處理過程,比如資料清洗、儲存等。
Item Pipeline的主要功能有如下4點。
清理HTML資料。
驗證爬取資料,檢查爬取欄位。
查重並丟棄重複內容。
將爬取結果儲存到資料庫。
一、核心方法
我們可以自定義Item Pipeline,只需要實現指定的方法,其中必須要實現的一個方法是: process_item(item, spider)
。
另外還有如下幾個比較實用的方法。
open_spider(spider)
。close_spider(spider)
。from_crawler(cls, crawler)
。
下面我們詳細介紹這幾個方法的用法。
1. process_item(item, spider)
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會被丟棄,不再進行處理。
2. open_spider(self, spider)
open_spider()
方法是在Spider開啟的時候被自動呼叫的。在這裡我們可以做一些初始化操作,如開啟資料庫連線等。其中,引數spider
就是被開啟的Spider物件。
3. close_spider(spider)
close_spider()
方法是在Spider關閉的時候自動呼叫的。在這裡我們可以做一些收尾工作,如關閉資料庫連線等。其中,引數spider
就是被關閉的Spider物件。
4. from_crawler(cls, crawler)
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
首先確保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
首先確保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就完成了。
Image 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… (二維碼自動識別)