Scrapy框架-图片下载功能

其实文件下载也差不多

前言

在日常爬取的过程中,图片下载还是挺多的,有时候可能纯粹是爬取图片,比如妹子图、动态图、表情包、封面图等,还有些时候是要进行验证码识别,所以需要用到图片下载功能。很高兴的是Scrapy为用户提供了图片下载功能,具体使用方法这里记录一下,它的逻辑是:

spider获取图片url --> 交给item进行处理 --> 然后根据setting的配置(开启图片下载以及设置路径)进行下载
具体的代码实现为:
Spider代码获取url,传递到item:

        telnums = response.css('.address tr:first-child img::attr(src)').extract_first("")
        loaders.add_value("image_urls", telnums)  # 企业联系电话(下载图片)

item对值进行预处理:

default_output_processor = TakeFirst()  # itemloader

image_urls = scrapy.Field()  #item

然后根据settings.py的配置:

IMAGES_URLS_FIELD = "image_urls"  # 对应item里面设定的字段,取到图片的url
prodir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(prodir,"images")  # 设置图片保存path

进行下载,即可完成下载功能。其中IMAGES_URLS_FIELD是固定写法,通过它来获取item中设定的图片url;IMAGES_STORE也是固定写法,设置图片保存路径。


这是一个坑

保存运行后得到一个报错:

Traceback (most recent call last):
  File "/home/ranbo/anaconda3/envs/pspiders/lib/python3.6/site-packages/twisted/internet/defer.py", line 653, in _runCallbacks
    current.result = callback(current.result, *args, **kw)
  File "/home/ranbo/anaconda3/envs/pspiders/lib/python3.6/site-packages/scrapy/pipelines/media.py", line 79, in process_item
    requests = arg_to_iter(self.get_media_requests(item, info))
  File "/home/ranbo/anaconda3/envs/pspiders/lib/python3.6/site-packages/scrapy/pipelines/images.py", line 155, in get_media_requests
    return [Request(x) for x in item.get(self.images_urls_field, [])]
  File "/home/ranbo/anaconda3/envs/pspiders/lib/python3.6/site-packages/scrapy/pipelines/images.py", line 155, in <listcomp>
    return [Request(x) for x in item.get(self.images_urls_field, [])]
  File "/home/ranbo/anaconda3/envs/pspiders/lib/python3.6/site-packages/scrapy/http/request/__init__.py", line 25, in __init__
    self._set_url(url)
  File "/home/ranbo/anaconda3/envs/pspiders/lib/python3.6/site-packages/scrapy/http/request/__init__.py", line 62, in _set_url
    raise ValueError('Missing scheme in request url: %s' % self._url)
ValueError: Missing scheme in request url: h

网上查资料得知:

 return [Request(x) for x in item.get(self.images_urls_field, [])]

scrapy下载图片的类传参数是list,而不是'http://www.baidu.com/123.jpg'这种str格式,在item传递的时候默认是所有数据都是list,但是之前在itemloaders加了TakeFirst:

class GxrcItemLoader(ItemLoader):
    """ 广西人才网企业信息itemloader """
    default_output_processor = TakeFirst()

所以才会被处理成str。


脱坑

现在只需要将它变成list即可,在item编写一个预处理的方法:

def keep_list(value):
    """
    scrapy的图片下载url默认处理列表list格式,str会报错
        由于下方itemloaders设置了default_output_processor为TakeFirst
        所以这里需要定义一个直接返回的函数来覆盖output_processor,这样就可以保持list格式
    """
    return value

然后在item代码里覆盖output_processor:

image_urls = scrapy.Field(output_processor=MapCompose(keep_list))

同时spider也得到解放,代码改成:

loaders.add_css("image_urls", ".address tr:first-child img::attr(src)")  # 企业联系电话(下载图片)

就可以下载图片了,而且代码风格有条理,舒服。


文件名和路径

下载后要获得图片的文件名和存放路径,毕竟下载完了之后不能不管。

scrapy下载的时候默认用hash加密图片url得到固定长度的值作为文件名,我要是想获得的话有两个方式,第一是自己加密图片url,生成文件名,路径就用setting里面配置的路径即可;第二个方式是重载ImagePipeline,在生成的时候就进行处理,这里演示第二种:
这个ImagePipeline写在pipelines.py文件中

它重载的是:

from scrapy.pipelines.images import ImagesPipeline

并且里面有很多的方法可以让我们在settings中进行配置,比如过滤图片的格式、图片大小等(文档有记录,以后翻文档)。

在pipelines.py文件中编写代码:

class ImagePathPipeline(ImagesPipeline):
    """
    继承ImagesPipeline,重载方法
        以实现处理图片下载路径的目的
    """
    def item_completed(self, results, item, info):

        pass   # (这里打断点,等下调试)

然后到settings.py中将之前使用的默认ImagesPipeline替换成自定的:

ITEM_PIPELINES = {
    #'scrapy.contrib.pipeline.images.ImagesPipeline': 1,
    'rspider.pipelines.ImagePathPipeline': 1,# 开启图片下载
}

然后debug运行,进行调试,看看 item_completed方法里面的results是什么样的数据结构:

<class 'list'>: [(True, {'url': 'http://vip.gxrc.com/Public/Phone/F4D1D4B0-DABB-4E83-BC54-5153BC44AA33', 'path': 'full/82cf733279ebc25c4de892e531018644e77aed67.jpg', 'checksum': 'b14c3fcdeb93209bc835d2d3dfdefb57'})]

可以看到,results是一个list,然后里面嵌套了一个tuple,再嵌套一个dict,dict存放有三个值,url/path/checksum,其中的path就是文件的路径,所以整个代码为:

class ImagePathPipeline(ImagesPipeline):
    """
    继承ImagesPipeline,重载item_completed方法
        以实现处理图片下载路径的目的
    """
    def item_completed(self, results, item, info):
        """ 循环results,将里面的path取出,然后赋值到item中的image_path字段,最后返回item """
        for ok, value in results:
            image_path = value["path"]
        item["image_path"] = image_path
        return item

总而言之

要给Scrapy增加图片下载功能,只需要编写Pipelines并且在Settings.py文件中开配置item中对应的图片url字段、设置图片保存地址以及开启pipeliens即可

上一篇:Python 给字符串进行加密,生成唯一固定长度字符串


下一篇:我想监控爬虫状况怎么办?Scrapy邮件发送功能及爬虫停止信息 。