Scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的 item pipelines . 这些pipeline有些共同的方法和结构(称之为media pipeline)。 我们可以使用FilesPipeline和Images Pipeline来保存文件和图片,他们有以下的一些特点:
Files Pipeline
FilesPipeline的典型工作流程如下:
在一个爬虫里,你抓取一个项目,把其中图片的URL放入 file_urls 组内。
项目从爬虫内返回,进入项目管道。
当项目进入 FilesPipeline,file_urls 组内的URLs将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载,当优先级更高,会在其他页面被抓取前处理。项目会在这个特定的管道阶段保持“locker”的状态,直到完成文件的下载(或者由于某些原因未完成下载)。
当文件下载完后,另一个字段(files)将被更新到结构中。这个组将包含一个字典列表,其中包括下载文件的信息,比如下载路径、源抓取地址(从 file_urls 组获得)和图片的校验码(checksum)。 files 列表中的文件顺序将和源 file_urls 组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在 files 组中。
Images Pipeline
避免重新下载最近已经下载过的数据
指定存储路径
将所有下载的图片转换成通用的格式(JPG)和模式(RGB)
缩略图生成
检测图像的宽/高,确保它们满足最小限制
和FilesPipeline类似,除了默认的字段名不同,image_urls保存图片URL地址,images保存下载后的图片信息。当然,它还提供了一些拓展功能,比如图片的缩略图,过滤图片的尺寸。 注意:你需要安装Pillow 库来实现以上的拓展功能。
要想使用media pipeline,你需要在设置添加一些必要的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ITEM_PIPELINES = { 'scrapy.pipelines.images.ImagesPipeline' : 1 , 'scrapy.pipelines.files.FilesPipeline' : 2 , } FILES_STORE = 'D:' IMAGES_STORE = 'D' FILES_EXPIRES = 90 IMAGES_EXPIRES = 30 IMAGES_THUMBS = { 'small' : (50 , 50 ), 'big' : (250 , 250 ), } IMAGES_MIN_HEIGHT = 110 IMAGES_MIN_WIDTH = 110
你可能会好奇文件的命名,在当你启用media pipeline以后, 它的默认命名方式是这样的,文件以它们URL的 SHA1 hash 作为文件名。 例如, 对下面的图片URL:http://www.example.com/image.jpg
其SHA1 hash 值为:3afec3b4765f8f0a07b78f98c07b83f013567a0a 将被下载并存为下面的文件:<IMAGES_STORE>/full/3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg
下面我们以ImagesPipeline为例来自定义ImagesPipeline,需要重写以下两个方法:
get_media_requests(item, info) 在工作流程中可以看到,管道会得到图片的URL并从项目中下载。为了这么做,你需要重写 get_media_requests()
方法,并对各个图片URL返回一个Request:
1 2 3 def get_media_requests (self, item, info ): for image_url in item['image_urls' ]: yield scrapy.Request(image_url)
这些请求将被管道处理,当它们完成下载后,结果将以2元素的元组列表形式传送到 item_completed()
方法: 每个元组包含 (success, file_info_or_error)
: success 是一个布尔值,当图片成功下载时为 True ,因为某个原因下载失败为False file_info_or_error 是一个包含下列关键字的字典(如果成功为 True)或者出问题时为 Twisted Failure 。 url - 文件下载的url。这是从 get_media_requests() 方法返回请求的url。 path - 图片存储的路径(类似 IMAGES_STORE) checksum - 图片内容的 MD5 hash item_completed() 接收的元组列表需要保证与 get_media_requests() 方法返回请求的顺序相一致。下面是 results 参数的一个典型值:
1 2 3 4 5 6 [(True , {'checksum' : '2b00042f7481c7b056c4b410d28f33cf' , 'path' : 'full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg' , 'url' : 'http://www.example.com/files/product1.jpg' }), (False , Failure(...))]
该方法 必须返回每一个图片的URL。
item_completed(results, items, info) 当一个单独项目中的所有图片请求完成时,例如,item里面一共有10个URL,那么当这10个URL全部下载完成以后,ImagesPipeline.item_completed()
方法将被调用。默认情况下, item_completed()
方法返回item。
使用ImagesPipeline下载图片 下面我们用上面学习到的知识来下载一些图片。 我们以http://jandan.net/ooxx
为例,把页面上的图片下载下来,并产生缩略图 我们新建一个项目,名为jiandan,各个文件内容如下。 item.py
1 2 3 4 5 import scrapyclass JiandanItem (scrapy.Item): image_urls = scrapy.Field() images = scrapy.Field()
jiandan_spider.py
1 2 3 4 5 6 7 8 9 10 11 12 import scrapyfrom jiandan.items import JiandanItemclass jiandanSpider (scrapy.Spider): name = 'jiandan' start_urls = ["http://jandan.net/ooxx" ] def parse (self, response ): item = JiandanItem() item['image_urls' ] = response.xpath('//img//@src' ).extract() yield item
settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 BOT_NAME = 'jiandan' SPIDER_MODULES = ['jiandan.spiders' ] NEWSPIDER_MODULE = 'jiandan.spiders' DEFAULT_REQUEST_HEADERS = { 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' , 'Accept-Language' : 'en' , 'User-Agent' :"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36" , } ITEM_PIPELINES = { 'jiandan.pipelines.JiandanPipeline' :1 , } IMAGES_STORE='H:\\jiandan' IMAGES_THUMBS = { 'small' : (50 , 50 ), 'big' : (200 , 200 ), }
pipelinse.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import scrapyfrom scrapy.exceptions import DropItemfrom scrapy.pipelines.images import ImagesPipeline class JiandanPipeline (ImagesPipeline ): def get_media_requests (self, item, info ): for image_url in item['image_urls' ]: image_url = "http://" + image_url 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" ) return item
运行这个spider,你会发现,图片已经下载好了,如下图所示。
图片内容你可以自己慢慢看。
默认情况下,使用ImagePipeline组件下载图片的时候,图片名称是以图片URL的SHA1值进行保存的。 重写file_path
函数可以修改图片名称以及自定义图片保存路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class MyimgPipeline (ImagesPipeline ): def file_path (self, request, response=None , info=None ): image_guid = request.url.split('/' )[-1 ] if image_guid=='cover.jpg' : dir = request.url.split('/' )[-2 ] return '%s/%s' % (dir ,image_guid) else : dir1 = request.url.split('/' )[-3 ] dir2 = request.url.split('/' )[-2 ] return '%s/%s/%s' % (dir1,dir2,image_guid) def get_media_requests (self, item, info ): if isinstance (item, AnimeItem) or isinstance (item, ImageItem): for image_url in item['image_urls' ]: yield Request(image_url) def item_completed (self, results, item, info ): if isinstance (item, AnimeItem) or isinstance (item, ImageItem): 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
转自:Scrapy学习篇(九)之文件与图片下载 扩展阅读:Scrapy框架之利用ImagesPipeline下载图片