返回介绍

12.11 内置图片和文件下载方式

发布于 2024-01-26 22:39:51 字数 8123 浏览 0 评论 0 收藏 0

有时在爬取产品的同时也想保存对应的图片,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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文