Scrapy 提供了一個 Item Pipeline,來下載屬于某個特定項目的圖片,比如,當(dāng)你抓取產(chǎn)品時,也想把它們的圖片下載到本地。
這條管道,被稱作圖片管道,在 ImagesPipeline 類中實現(xiàn),提供了一個方便并具有額外特性的方法,來下載并本地存儲圖片:
這個管道也會為那些當(dāng)前安排好要下載的圖片保留一個內(nèi)部隊列,并將那些到達(dá)的包含相同圖片的項目連接到那個隊列中。 這可以避免多次下載幾個項目共享的同一個圖片。
Pillow 是用來生成縮略圖,并將圖片歸一化為 JPEG/RGB 格式,因此為了使用圖片管道,你需要安裝這個庫。Python Imaging Library(PIL) 在大多數(shù)情況下是有效的,但眾所周知,在一些設(shè)置里會出現(xiàn)問題,因此我們推薦使用 Pillow 而不是 PIL。
當(dāng)使用 ImagesPipeline,典型的工作流程如下所示:
image_urls 組內(nèi)。ImagesPipeline,image_urls 組內(nèi)的 URLs 將被 Scrapy 的調(diào)度器和下載器(這意味著調(diào)度器和下載器的中間件可以復(fù)用)安排下載,當(dāng)優(yōu)先級更高,會在其他頁面被抓取前處理。項目會在這個特定的管道階段保持“l(fā)ocker”的狀態(tài),直到完成圖片的下載(或者由于某些原因未完成下載)。images)將被更新到結(jié)構(gòu)中。這個組將包含一個字典列表,其中包括下載圖片的信息,比如下載路徑、源抓取地址(從 image_urls 組獲得)和圖片的校驗碼。images 列表中的圖片順序?qū)⒑驮?image_urls 組保持一致。如果某個圖片下載失敗,將會記錄下錯誤信息,圖片也不會出現(xiàn)在 images 組中。為了使用圖片管道,你僅需要啟動它并用 image_urls 和 images 定義一個項目:
import scrapy
class MyItem(scrapy.Item):
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
如果你需要更加復(fù)雜的功能,想重寫定制圖片管道行為,參見實現(xiàn)定制圖片管道。
為了開啟你的圖片管道,你首先需要在項目中添加它 ITEM_PIPELINES setting:
ITEM_PIPELINES = {'scrapy.contrib.pipeline.images.ImagesPipeline': 1}
并將 IMAGES_STORE 設(shè)置為一個有效的文件夾,用來存儲下載的圖片。否則管道將保持禁用狀態(tài),即使你在 ITEM_PIPELINES 設(shè)置中添加了它。
比如:
IMAGES_STORE = '/path/to/valid/dir'
文件系統(tǒng)是當(dāng)前官方唯一支持的存儲系統(tǒng),但也支持(非公開的) Amazon S3。
圖片存儲在文件中(一個圖片一個文件),并使用它們 URL 的 SHA1 hash 作為文件名。
比如,對下面的圖片 URL:
http://www.example.com/image.jpg
它的 SHA1 hash 值為:
3afec3b4765f8f0a07b78f98c07b83f013567a0a
將被下載并存為下面的文件:
<IMAGES_STORE>/full/3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg
其中:
<IMAGES_STORE>是定義在 IMAGES_STORE 設(shè)置里的文件夾full 是用來區(qū)分圖片和縮略圖(如果使用的話)的一個子文件夾。詳情參見縮略圖生成。圖像管道避免下載最近已經(jīng)下載的圖片。使用 IMAGES_EXPIRES 設(shè)置可以調(diào)整失效期限,可以用天數(shù)來指定:
# 90 天的圖片失效期限
IMAGES_EXPIRES = 90
圖片管道可以自動創(chuàng)建下載圖片的縮略圖。
為了使用這個特性,你需要設(shè)置 IMAGES_THUMBS 字典,其關(guān)鍵字為縮略圖名字,值為它們的大小尺寸。
比如:
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}
當(dāng)你使用這個特性時,圖片管道將使用下面的格式來創(chuàng)建各個特定尺寸的縮略圖:
<IMAGES_STORE>/thumbs/<size_name>/<image_id>.jpg
其中:
<size_name>是 IMAGES_THUMBS字典關(guān)鍵字(small,big,等)<image_id>是圖像 url 的 SHA1 hash例如使用 small 和 big 縮略圖名字的圖片文件:
<IMAGES_STORE>/full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/small/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/big/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
第一個是從網(wǎng)站下載的完整圖片。
你可以丟掉那些過小的圖片,只需在:setting:*IMAGES_MIN_HEIGHT* 和 IMAGES_MIN_WIDTH 設(shè)置中指定最小允許的尺寸。
比如:
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
注意:這些尺寸一點也不影響縮略圖的生成。
默認(rèn)情況下,沒有尺寸限制,因此所有圖片都將處理。
下面是你可以在定制的圖片管道里重寫的方法:
在工作流程中可以看到,管道會得到圖片的 URL 并從項目中下載。為了這么做,你需要重寫 get_media_requests()方法,并對各個圖片 URL 返回一個 Request:
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
這些請求將被管道處理,當(dāng)它們完成下載后,結(jié)果將以 2-元素的元組列表形式傳送到 item_completed()方法:
success 是一個布爾值,當(dāng)圖片成功下載時為 True,因為某個原因下載失敗為Falseimage_info_or_error 是一個包含下列關(guān)鍵字的字典(如果成功為 True)或者出問題時為 Twisted Failure。
url - 圖片下載的 url。這是從 get_media_requests()方法返回請求的 url。path - 圖片存儲的路徑(類似 IMAGES_STORE)checksum - 圖片內(nèi)容的 MD5 hash
item_completed()接收的元組列表需要保證與 get_media_requests()方法返回請求的順序相一致。下面是 results 參數(shù)的一個典型值:[(True,
{'checksum': '2b00042f7481c7b056c4b410d28f33cf',
'path': 'full/7d97e98f8af710c7e7fe703abc8f639e0ee507c4.jpg',
'url': 'http://www.example.com/images/product1.jpg'}),
(True,
{'checksum': 'b9628c4ab9b595f72f280b90c4fd093d',
'path': 'full/1ca5879492b8fd606df1964ea3c1e2f4520f076f.jpg',
'url': 'http://www.example.com/images/product2.jpg'}),
(False,
Failure(...))]
默認(rèn) get_media_requests()方法返回 None,這意味著項目中沒有圖片可下載。
當(dāng)一個單獨項目中的所有圖片請求完成時(要么完成下載,要么因為某種原因下載失?。?ImagesPipeline.item_completed()方法將被調(diào)用。
item_completed()方法需要返回一個輸出,其將被送到隨后的項目管道階段,因此你需要返回(或者丟棄)項目,如你在任意管道里所做的一樣。
這里是一個 item_completed() 方法的例子,其中我們將下載的圖片路徑(傳入到 results 中)存儲到 image_paths 項目組中,如果其中沒有圖片,我們將丟棄項目:
from scrapy.exceptions import DropItem
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item
默認(rèn)情況下,item_completed()方法返回項目。
下面是一個圖片管道的完整例子,其方法如上所示:
import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item