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