在上篇文章中我们学习了关于scrapy中的数据解析以及在scrapy框架中的日志配置方法。那么在今天的文章中我们就来看关于scrapy中对于数据的存储是如何实现的。
在前面的文章中我们介绍到scrapy中的五大组件,其中专门用于存储阶段的组件就是管道,所以今天主要呢也是对围绕着管道来对scrapy中的存储进行学习。
一、数据存储的方式
在scrapy中管道是实现数据存储的组件,但是并不是说我们在保存数据的时候只能够在管道中进行(注意:只是在管道实现这个过程,管道本身是不存储数据的),也可以在爬虫文件中实现、或者在中间件中实现都是可以的。但是在管道中存储存在以下几种优势。
1.实时性:管道能提供实时的数据传输,并不用等待所有的数据处理完成之后才输出。
2.单向性:管道只允许数据单向流动,可以避免数据出现混淆或者重复的情况。
3.节省空间:管道不需要存储数据,因此节省了存储空间。
4.高效性:管道的本质就是一个内存缓存区,所以读写速度比硬盘块,并且没有磁盘访问时间,同时管道具有可并发性,能够在多个进程只见实现并发传输数据。
5.灵活性:管道可以在不进行额外的配置或安装的情况下配合不同的程序使用。
那么接下来我们就来看scrapy中的终端存储方式与管道存储方式的简单应用。
1.1 基于终端指令实现持久化存储
继续以上篇文章中的素材网为例,此次我们将图片的标题进行存储。
基于终端指令的存储首先要知道,保存的时候在爬虫文件中需要有返回值,也就是说我们需要在爬虫文件的parse方法中return出一个结果,然后才能够在终端实现这个返回结果的存储。代码如下:
import scrapy
class Spider01Spider(scrapy.Spider):
name = "spider01"
allowed_domains = ["chinaz.com"]
start_urls = ["https://sc.chinaz.com/tupian/siwameinvtupian.html"]
page = 1
def parse(self, response):
titles = response.xpath('/html/body/div[3]/div[2]/div/img/@alt').extract()
dic = {} # 声明用于存储标题的字典
for i in range(1, len(titles)+1):
dic[str(i)] = titles[i-1] # 注意使用终端存储时字典中的键不要是整数类型
print(dic)
return dic
然后在终端输入命令
scrapy crawl spider01 -o result.json
执行结束之后就可以在项目中看到生成了一个result.json的文件,其中就有我们保存的数据。
但是要知道这种存储的方式是有一定的局限性的,其保存的文件类型只能够为:‘json’,‘jsonlines’,‘jl’,‘csv’,‘xml’,'marshal ',‘pickle’。虽然使用方式比较方便,不过相对其局限性而言就不太够看了,所以这种方式只适合数据量较少且结构分明的数据。
1.2 基于管道实现持久化存储
基于管道实现持久化存储的话我们要直到其在scrapy框架中的一个编码流程,不能够说随随便便想到哪里就写哪里,要知道框架中是有很多个文件的,没有一定的逻辑去写的话最后你都不知道你写到了哪一个文件之中了。
1.2.1 编码思路(仅供参考)
1.完成数据提取
2.在item类中定义相关属性
3.将解析到的数据封装到item中
4.将item提交到管道进行存储操作
5.在管道类中的process_item方法中将接收到的item对象进行持久化的存储操作。
6.在配置中开启管道
接下来按照该流程(从第2步开始)来对刚刚提取出来的图片标题进行存储。
1.2.2 构建字段
打开items.py
文件开始构建
字段的构建方式在文件中是直接给出的,所以我们就严格按照提示来进行即可。构建字段title
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class Myproject01Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
index = scrapy.Field()
title = scrapy.Field()
pass
1.2.3 封装item及提交
在爬虫文件中进行item的封装
import scrapy
from myproject01.items import Myproject01Item
class Spider01Spider(scrapy.Spider):
name = "spider01"
allowed_domains = ["chinaz.com"]
start_urls = ["https://sc.chinaz.com/tupian/siwameinvtupian.html"]
page = 1
def parse(self, response):
titles = response.xpath('/html/body/div[3]/div[2]/div/img/@alt').extract()
items = Myproject01Item() # 实例化items
for i in range(1, len(titles)+1):
items['title'] = titles[i-1] # 逐个添加到items中
items['index'] = str(i)
yield items # 将items提交到管道中
1.2.4 编辑管道类存储方法
打开pipelines.py
文件
在process_item中实现存储,可以是任何可交互存储媒介,在此处的item
是一个类字典对象但不是一个字典,虽然在输出的时候在终端上看到的是一个字典结构的数据,但是其本质并不是一个字典,大家可以输出其type验证。此处存储以MongoDB为例。
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import pymongo
class Myproject01Pipeline:
def process_item(self, item, spider):
conn = pymongo.MongoClient(host='mongodb://localhost', port=27017)
mydb = conn['zzsc']
mycol = mydb['titles']
mycol.insert_one({item['index']: item['title']})
return item
1.2.5 启动管道
启动管道只需要在配置文件中取消默认的这个管道类的注释即可。如下:
然后便可以开始执行任务了。待执行结束之后在MongoDB中进行查看。
可以看到数据就成功的保存在数据库之中了。但是细心点小伙伴就会发现一个问题,循环这么多次,每一次循环都要去提交到管道,也就意味着process_item方法调用了很多次,那么与mongo的连接就连接了很多次,这样不是很浪费资源嘛?没错,所以这个地方在scrapy框架中也是考虑到了的,因此其是提供了一个在任务开始的时候便会调用一次的方法,而在之后提交管道的时候是不会再次执行的,如下代码:
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import pymongo
class Myproject01Pipeline:
def open_spider(self, spider):
"""在爬虫任务开始时调用一次"""
self.conn = pymongo.MongoClient(host='mongodb://localhost', port=27017)
self.mydb = self.conn['zzsc']
self.mycol = self.mydb['titles']
def process_item(self, item, spider):
self.mycol.insert_one({item['index']: item['title']})
return item
def close_spider(self, spider):
"""在爬虫任务结束时被调用一次"""
self.conn.close()
这样代码就重写好啦,就不会出现每一次提交的时候都去连接数据库的情况了。
二、基于管道的图片存储
相较于文本类型数据而已,图片的存储在scrapy中是有着一个完善的封装的,准确的说文件的存储在scrapy中都是封装好的功能来实现文件的快速保存。接下来我们就以图片保存为例来简单了解一下在scrapy中封装好的关于文件的存储类。
首先是关于字段的设置,items
中必须要有的字段是文件所在的地址,也就是我们必须将文件的url解析出来。
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class Myproject01Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
index = scrapy.Field()
title = scrapy.Field()
url = scrapy.Field()
pass
此时的爬虫文件中的代码如下:
import scrapy
from myproject01.items import Myproject01Item
class Spider01Spider(scrapy.Spider):
name = "spider01"
allowed_domains = ["chinaz.com"]
start_urls = ["https://sc.chinaz.com/tupian/siwameinvtupian.html"]
page = 1
def parse(self, response):
titles = response.xpath('/html/body/div[3]/div[2]/div/img/@alt').extract()
urls = ['https:'+url for url in response.xpath('/html/body/div[3]/div[2]/div/img/@data-original').extract()]
for i in range(1, len(titles)+1):
items = Myproject01Item() # 实例化items
items['title'] = titles[i-1] # 逐个添加到items中
items['index'] = str(i)
items['url'] = urls[i-1]
yield items # 将items提交到管道中
然后来到管道文件中创建一个类,名字自定义,但是需要继承固定的scrapy提供的父类。
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import scrapy
from scrapy.pipelines.images import ImagesPipeline
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
yield scrapy.Request(item['url'])
def file_path(self, request, response=None, info=None, *, item=None):
imgName = item['title'] + '.jpg' # 图片名保存时的名称
return imgName
def item_completed(self, results, item, info):
return item
再将此管道类注册到项目之中。
最后再配置文件中去指定一下图片文件的保存路径。
注意如果没有imgs文件夹的话需要手动创建!!!!同时要保证自己的环境中存在着pillow库,否则代码运行会出BUG!
pip install pillow
代码执行结果:
关于代码中的任何疑惑点或者其他问题,大家可私聊本人或者进群讨论。