其实文件下载也差不多
前言
在日常爬取的过程中,图片下载还是挺多的,有时候可能纯粹是爬取图片,比如妹子图、动态图、表情包、封面图等,还有些时候是要进行验证码识别,所以需要用到图片下载功能。很高兴的是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
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是什么样的数据结构:
: [(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即可