12.11 内置图片和文件下载方式
有时在爬取产品的同时也想保存对应的图片,Scrapy为下载Item中包含的文件提供了一个可重用的Item Pipeline。这些Pipeline有些共同的方法和结构,我们称之为MediaPipeline。一般来说你会使用FilesPipeline或者ImagesPipeline。这两种Pipeline都实现了以下特性:
·避免重新下载最近已经下载过的数据。
·指定存储的位置和方式。
此外,ImagesPipeline还提供了额外特性:
·将所有下载的图片转换成通用的格式(JPG)和模式(RGB)。
·缩略图生成。
·检测图像的宽/高,确保它们满足最小限制。
这个管道也会为那些当前安排好要下载的图片保留一个内部队列,并将那些到达的包含相同图片的项目连接到该队列中,这可以避免多次下载几个Item共享的同一个图片。
当使用FilesPipeline时,典型的工作流程如下所示:
1)在一个爬虫里,抓取一个Item,把其中文件的URL放入file_urls组内。
2)Item从爬虫内返回,进入Item Pipeline。
3)当Item进入FilesPipeline,file_urls组内的URL将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载,如果优先级更高,会在其他页面被抓取前处理。Item会在这个特定的管道阶段保持“locker”的状态,直到完成文件的下载(或者由于某些原因未完成下载)。
4)当文件下载完后,另一个字段(files)将被更新到结构中。这个组将包含一个字典列表,其中包括下载文件的信息,比如下载路径、源抓取地址(从file_urls组获得)和图片的校验码(checksum)。files列表中的文件顺序将和源file_urls组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在files组中。
当使用ImagesPipeline时,典型的工作流程如下所示:
1)在一个爬虫里,抓取一个Item,把其中图片的URL放入images_urls组内。
2)项目从爬虫内返回,进入Item Pipeline。
3)当Item进入ImagesPipeline,images_urls组内的URL将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载,如果优先级更高,会在其他页面被抓取前处理。项目会在这个特定的管道阶段保持“locker”的状态,直到完成文件的下载(或者由于某些原因未完成下载)。
4)当文件下载完后,另一个字段(images)将被更新到结构中。这个组将包含一个字典列表,其中包括下载文件的信息,比如下载路径、源抓取地址(从images_urls组获得)和图片的校验码(checksum)。images列表中的文件顺序将和源images_urls组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在images组中。
Pillow用来生成缩略图,并将图片归一化为JPEG/RGB格式,因此为了使用ImagesPipeline,你需要安装这个库。Python Imaging Library(PIL)在大多数情况下是有效的,但众所周知,在一些设置里会出现问题,因此我们推荐使用Pillow而不是PIL,使用pip install pillow可以安装这个模块。
1.使用FilesPipeline
使用FilesPipeline非常简单,只需要三个步骤即可:
1)在settings.py文件的ITEM_PIPELINES中添加一条’scrapy.pipelines.files.FilesPipeline‘:1。
2)在item中添加两个字段,比如:
file_urls = scrapy.Field() files = scrapy.Field()
3)在settings.py文件中添加下载路径FILES_STORE、文件url所在的item字段FILES_URLS_FIELD和文件结果信息所在的item字段FILES_RESULT_FIELD,比如
FILES_STORE = 'D:\\cnblogs' FILES_URLS_FIELD = 'file_urls' FILES_RESULT_FIELD = 'files'
使用FILES_EXPIRES设置文件过期时间,示例如下:
FILES_EXPIRES = 30# 30天过期
2.使用ImagesPipeline
使用ImagesPipeline的基本步骤和FilesPipeline一样,不过针对的是图片下载,又添加了一些新的特性。基本步骤如下:
1)在settings.py文件的ITEM_PIPELINES中添加一条’scrapy.pipelines.images.Images Pipeline‘:1。
2)在item中添加两个字段,比如:
image_urls = scrapy.Field() images = scrapy.Field()
3)在settings.py文件中添加下载路径IMAGES_STORE、文件url所在的item字段IMAGES_URLS_FIELD和文件结果信息所在的item字段IMAGES_RESULT_FIELD,比如
IMAGES_STORE = 'D:\\cnblogs' IMAGES_URLS_FIELD= 'image_urls' IMAGES_RESULT_FIELD = 'images'
可以在settings.py中使用IMAGES_THUMBS制作缩略图,并设置缩略图尺寸大小。使用IMAGES_EXPIRES设置文件过期时间,示例如下:
IMAGES_THUMBS = { 'small': (50, 50), 'big': (270, 270), } IMAGES_EXPIRES = 30# 30天过期
如果想过滤特别小的图片可以使用IMAGES_MIN_HEIGHT和IMAGES_MIN_WIDTH来设置图片的最小高和宽。
通过上面讲解的内容,我们可以给cnblogs爬虫添加下载每篇文章中图片的功能。首先按照ImagesPipeline基本步骤进行配置。
settings.py文件中的设置如下:
ITEM_PIPELINES = { 'cnblogSpider.pipelines.CnblogspiderPipeline': 300, 'scrapy.pipelines.images.ImagesPipeline':1 } IMAGES_STORE = 'D:\\cnblogs' IMAGES_URLS_FIELD = 'cimage_urls' IMAGES_RESULT_FIELD = 'cimages' IMAGES_EXPIRES = 30 IMAGES_THUMBS = { 'small': (50, 50), 'big': (270, 270), }
items.py文件代码如下,添加了cimage_url和cimages字段:
class CnblogspiderItem(scrapy.Item): # define the fields for your item here like: url = scrapy.Field() time = scrapy.Field() title = scrapy.Field() content = scrapy.Field() cimage_urls = scrapy.Field() cimages = scrapy.Field()
cnblogs_spider.py文件代码如下,添加了parse_body方法,用于提取文章正文中的图片链接,同时还用到了Request的meta属性,用来将Item示例进行暂存,统一提交:
def parse(self, response): # 实现网页的解析 # 首先抽取所有的文章 papers = response.xpath(".// *[@class='day']") # 从每篇文章中抽取数据 for paper in papers: url = paper.xpath(".// *[@class='postTitle']/a/@href").extract()[0] title = paper.xpath(".// *[@class='postTitle']/a/text()").extract()[0] time = paper.xpath(".// *[@class='dayTitle']/a/text()").extract()[0] content = paper.xpath(".// *[@class='postTitle']/a/text()").extract()[0] item = CnblogspiderItem(url=url,title=title,time=time,content=content) request = scrapy.Request(url=url,callback=self.parse_body) request.meta['item'] = item# 将item暂存 yield request next_page = Selector(response).re(u'<a href="(\S*)">下一页</a>') if next_page: yield scrapy.Request(url=next_page[0],callback=self.parse) def parse_body(self,response): item = response.meta['item'] body = response.xpath(".// *[@class='postBody']") item['cimage_urls'] = body.xpath('.// img// @src').extract()# 提取图片链接 yield item
最后在命令行中切换到项目目录下,运行scrapy crawl cnblogs,开始爬取数据。爬取到的图片所在的目录结构如下:
cnblogs ├─full │ 0215c0dfe13fa7da8ed07467e94bc62d7f6e7583.jpg │ 059a17af502b604ea825d164d799d59406e9e573.jpg │ 09b23ca063bee1dc713cc37e023c7fd9709effac.jpg └─thumbs ├─big │ 0215c0dfe13fa7da8ed07467e94bc62d7f6e7583.jpg │ 059a17af502b604ea825d164d799d59406e9e573.jpg │ 09b23ca063bee1dc713cc37e023c7fd9709effac.jpg └─small 0215c0dfe13fa7da8ed07467e94bc62d7f6e7583.jpg 059a17af502b604ea825d164d799d59406e9e573.jpg 09b23ca063bee1dc713cc37e023c7fd9709effac.jpg
大家肯定会发现图片的名称很奇怪,图片名称是图片下载链接经过SHA1哈希后的值,由Scrapy自行处理。
以上讲解了Scrapy内置的FilesPipeline和ImagesPipeline,那么如何定制我们自己的FilesPipeline或者ImagesPipeline呢?我们需要继承FilesPipeline或者ImagesPipeline,重写get_media_requests和item_completed()方法。下面以ImagesPipeline为例进行讲解。
1.get_media_requests(item,info)方法
在工作流程中可以看到,管道会得到图片的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)
这些请求将由管道处理,当它们完成下载后,结果results将以2-元素的元组列表形式传送到item_completed()方法,结果类似如下的形式:
[(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(...))]
返回结果的格式解释如下:
·success是一个布尔值,当图片成功下载时为True,因为某个原因下载失败为False。
·如果success为True,image_info_or_error是一个包含下列关键字的字典,如果出问题时则为Twisted Failure。
·url是图片下载的url。这是从get_media_requests()方法返回的请求的url。
·path是图片存储的路径(类似IMAGES_STORE)。
·checksum是图片内容的MD5hash。
2.item_completed(results,items,info)方法
当一个单独项目中的所有图片请求完成时(要么完成下载,要么因为某种原因下载失败),ImagesPipeline.item_completed()方法将被调用。其中results参数就是get_media_requests下载完成之后返回的结果。item_completed()方法需要返回一个输出,其将被送到随后的ItemPipelines,因此你需要返回或者丢弃项目,这和之前在ItemPipelines中的操作一样。
以下是item_completed()方法的例子,其中我们将下载的图片路径存储到item中的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
以下是一个定制ImagesPipeline的完整例子,代码如下:
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论