Scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的 item pipelines . 这些pipeline有些共同的方法和结构(称之为media pipeline)。我们可以使用FilesPipeline和Images Pipeline来保存文件和图片,他们有以下的一些特点:
FilesPipeline的典型工作流程如下:
和FilesPipeline类似,除了默认的字段名不同,image_urls保存图片URL地址,images保存下载后的图片信息。当然,它还提供了一些拓展功能,比如图片的缩略图,过滤图片的尺寸。 注意:你需要安装Pillow 库来实现以上的拓展功能。
要想使用media pipeline,你需要在设置添加一些必要的信息。
# 同时启用图片和文件管道
ITEM_PIPELINES = {
'scrapy.pipelines.images.ImagesPipeline': 1,
'scrapy.pipelines.files.FilesPipeline': 2,
}
FILES_STORE = 'D:' # 文件存储路径
IMAGES_STORE = 'D' # 图片存储路径
# 避免下载最近90天已经下载过的文件内容
FILES_EXPIRES = 90
# 避免下载最近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
其中, 是定义在 IMAGES_STORE 设置里的文件夹,我们设置的是D盘,full 是用来区分图片和缩略图(如果使用的话)的一个子文件夹,这个文件夹scrapy会自动生成。
下面我们以ImagesPipeline为例来自定义ImagesPipeline,需要重写以下两个方法:
可以改写自己的文件名的保存方式
class MyFilesPipeline(FilesPipeline): def file_path(self, request, response=None, info=None): split_url = str(request.url).split('/') kind_name = split_url[-2] file_name = split_url[-1] return '%s/%s'%(kind_name,file_name) # path = urlparse(request.url).path # return join(basename(dirname(path)),basename(path))
在settings中设置
ITEM_PIPELINES = { #'scrapy.pipelines.files.FilesPipeline':1 'download_files.pipelines.MyFilesPipeline':1 # 'download_files.pipelines.DownloadFilesPipeline': 300, }
也可以编写自己的文本格式来保存:
class ExcelItemExporter(BaseItemExporter): def __init__(self,file,**kwargs): self._configure(kwargs) self.file = file self.row = 0 self.wb = xlwt.Workbook() self.sheet = self.wb.add_sheet('books') def finish_exporting(self): self.wb.save(self.file) def export_item(self, item): items = self._get_serialized_fields(item) for col,v in enumerate(x for _,x in items): self.sheet.write(self.row,col,v) self.row +=1
然后在settings中配置:
FEED_EXPORTERS = {'excel':'douban_books.my_exporter.ExcelItemExporter'} FEED_EXPORT_FIELDS = ['url','ISBN','name','author','publish','publish_time','subtitle','price','pages','series','about_author','content']
FEED_EXPORT_FIELDS 可以设置保存数据字段的顺序
下面我们用上面学习到的知识来下载一些图片。 我们以http://jandan.net/ooxx为例,把页面上的图片下载下来,并产生缩略图 我们新建一个项目,名为jiandan,各个文件内容如下。
item.py
import scrapy
class JiandanItem(scrapy.Item):
image_urls = scrapy.Field()#图片的链接
images = scrapy.Field()
jiandan_spider.py
import scrapy
from jiandan.items import JiandanItem
class 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
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
import scrapy
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline #内置的图片管道
class JiandanPipeline(ImagesPipeline):#继承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,你会发现,图片已经下载好了,如下图所示。
方法一:
item.py文件:定义item字段
import scrapy class PicPhotoPeopleItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 存放url的下载地址 image_urls = scrapy.Field() # 图片下载路径、url和校验码等信息(图片全部下载完成后将信息保存在images中) images = scrapy.Field() # 图片的本地保存地址 image_paths = scrapy.Field()
spider.py文件:编写爬虫文件,解析源码,得到图片的url下载路径
# -*- coding: utf-8 -*- import scrapy from ..items import PicPhotoPeopleItem class PeopleSpider(scrapy.Spider): name = 'people' allowed_domains = ['699pic.com'] start_urls = ['http://699pic.com/people.html'] base_url = 'http://699pic.com/' def parse(self, response): # 获取全部的图片地址 list_imgs = response.xpath('//div[@class="swipeboxEx"]/div[@class="list"]//img/@data-original').extract() if list_imgs: item = PicPhotoPeopleItem() item['image_urls'] = list_imgs # 保存到item的image_urls里 yield item # 返回item给pipeline文件处理 # 获取下一页url next_url = response.xpath('//a[@class="downPage"]/@href').extract()[0] \ if len(response.xpath('//a[@class="downPage"]/@href')) else None if next_url: yield scrapy.Request(url=self.base_url + next_url,callback=self.parse)
pipelines.py文件:对item里的数据进行处理,这里是调用调度器和下载器下载图片。重写一些ImagesPileline类的方法。
需要重写的方法有:get_media_requests(item, info)和item_completed(results, items, info)
from scrapy.pipelines.images import ImagesPipeline from scrapy.exceptions import DropItem from scrapy.http import Request class PicPhotoPeoplePipeline(ImagesPipeline): def file_path(self, request, response=None, info=None): """ 重写ImagesPipeline类的file_path方法 实现:下载下来的图片命名是以校验码来命名的,该方法实现保持原有图片命名 :return: 图片路径 """ image_guid = request.url.split('/')[-1] # 取原url的图片命名 return 'full/%s' % (image_guid) def get_media_requests(self, item, info): """ 遍历image_urls里的每一个url,调用调度器和下载器,下载图片 :return: Request对象 图片下载完毕后,处理结果会以二元组的方式返回给item_completed()函数 """ for image_url in item['image_urls']: yield Request(image_url) def item_completed(self, results, item, info): """ 将图片的本地路径赋值给item['image_paths'] :param results:下载结果,二元组定义如下:(success, image_info_or_failure)。 第一个元素表示图片是否下载成功;第二个元素是一个字典。 如果success=true,image_info_or_error词典包含以下键值对。失败则包含一些出错信息。 字典内包含* url:原始URL * path:本地存储路径 * checksum:校验码 :param item: :param info: :return: """ 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
settings.py文件:配置setting文件。
import os # 配置数据保存路径,为当前工程目录下的 images 目录中 project_dir = os.path.abspath(os.path.dirname(__file__)) IMAGES_STORE = os.path.join(project_dir, 'images') # 过期天数 IMAGES_EXPIRES = 90 # 90天内抓取的都不会被重抓 # IMAGES_MIN_HEIGHT = 100 # 图片的最小高度 # IMAGES_MIN_WIDTH = 100 # 图片的最小宽度
结果:
方法二:
item.py和spider.py同上;pipeline.py此处不需要自己编写,直接引用现成的ImagePileline;
修改settings.py文件即可,代码如下:
import os IMAGES_URLS_FIELD = 'image_urls' # 要保存的字段,即在 Item 类中的字段名为 image_urls # 配置数据保存路径,为当前工程目录下的 images 目录中 project_dir = os.path.abspath(os.path.dirname(__file__)) IMAGES_STORE = os.path.join(project_dir, 'images') # 过期天数 IMAGES_EXPIRES = 90 # 90天内抓取的都不会被重抓 # IMAGES_MIN_HEIGHT = 100 # 图片的最小高度 # IMAGES_MIN_WIDTH = 100 # 图片的最小宽度 ITEM_PIPELINES = { 'scrapy.pipelines.images.ImagesPipeline': 1, }
结果同上。
注意点:在写爬虫文件解析response时,获取图片的下载地址,一开始写的xpath是@src,但是爬取到的image_urls里面全部是http://static.699pic.com/images/blank.png
后来排查代码,观察网页源代码,发现源代码中@src的值是http://static.699pic.com/images/blank.png。刚才element中的src是经过渲染之后的值,所以最后采用的@data-original。这也就证实了爬虫获取到的response是网页的源码,爬取之前需要先确认源码和element中的元素和值是否一致,只有一致了才可以直接使用element中的元素和值。
转自:https://blog.csdn.net/loner_fang/article/details/81111879