一、为什么需要持久化存储?
持久化存储是指将数据在程序结束后仍然保持在存储介质中的能力。这种存储方式对于许多应用程序和系统至关重要,原因如下:
-
数据持久性: 持久化存储确保数据在程序关闭后不会丢失。这对于需要长期保存数据的应用程序至关重要,如数据库系统、文件存储等。
-
状态保存: 对于需要保存应用程序状态的情况,持久化存储可以确保用户下次访问时可以恢复到之前的状态,提供更好的用户体验。
-
数据共享: 通过持久化存储,不同的应用程序或用户可以共享数据,从而实现数据的持久性和共享性。
-
数据恢复: 在系统或应用程序发生故障或意外关闭时,持久化存储可以帮助恢复数据,避免数据丢失。
-
离线访问: 持久化存储使得应用程序可以在离线状态下访问数据,提高了应用程序的灵活性和可用性。
-
数据分析和历史记录: 持久化存储允许数据长期保存,以便进行数据分析、生成历史记录或用于回顾和审计等目的。
总的来说,持久化存储对于确保数据的安全性、可靠性和持续性至关重要,使得应用程序能够更好地处理数据、管理状态,并提供更好的用户体验。
二、如何进行持久化存储?
进行持久化存储通常涉及以下几种常见的方法:
-
数据库系统:
- 关系型数据库: 如MySQL、PostgreSQL、SQL Server等,通过SQL语言进行数据操作。
- 非关系型数据库: 如MongoDB、Redis、Cassandra等,适用于不同类型的数据存储需求。
-
文件系统:
- 将数据以文件的形式存储在磁盘上,可以是文本文件、JSON文件、XML文件等。
- 可以通过文件读写操作来实现数据的持久化存储。
-
键值存储:
- 使用键值对的方式存储数据,如Redis、Memcached等。
- 适用于需要快速读写和简单数据结构的场景。
-
对象存储:
- 将数据以对象的形式存储在云存储服务中,如AWS S3、Google Cloud Storage等。
- 适用于需要大规模存储和访问的场景。
-
日志文件:
- 通过记录应用程序的操作日志,可以实现数据的持久化存储和恢复。
- 适用于需要追踪操作历史和进行故障恢复的场景。
-
ORM(对象关系映射):
- 将对象和数据库之间的映射关系抽象出来,简化数据持久化操作。
- ORM框架如Hibernate、Sequelize等可以帮助开发者进行持久化存储。
-
缓存:
- 使用缓存技术如Redis、Memcached等,可以将数据暂时存储在内存中,提高数据访问速度。
- 缓存可以与持久化存储结合使用,提高系统性能和响应速度。
选择合适的持久化存储方式取决于应用程序的需求、数据量、访问模式等因素。开发人员需要根据具体情况选择最适合的持久化存储方案,并确保数据的安全性、一致性和可靠性。
三、Scrapy数据持久化
1、方法简介
- Scrapy内建了Item Pipeline机制,可以通过编写自定义Pipeline实现数据的持久化存储。
- 在Pipeline中可以调用内置方法将Item对象序列化为JSON或CSV格式,并写入文件。
2、parse 函数返回值
在Scrapy中,parse
函数是用于处理下载的响应并返回提取的数据以及跟进的URL的函数。parse
函数可以返回两种类型的对象:
(1)字典(Dictionary)或 Item 对象
- 最常见的做法是从响应中提取数据,然后将数据以字典或 Scrapy 的 Item 对象的形式返回。
- 这些数据将会被传递到 Item Pipeline 进行后续处理,比如数据清洗、验证和持久化存储。
def parse(self, response):
data = {
'title': response.css('h1::text').get(),
'content': response.css('p::text').getall(),
}
return data
(2)Request 对象
parse
函数还可以返回一个或多个 Request 对象,用于继续爬取其他页面。- 返回的 Request 对象可以指向新的URL,并指定一个回调函数来处理该URL对应的响应。
def parse(self, response):
# 提取页面中的链接并跟进
for link in response.css('a::attr(href)').getall():
yield response.follow(link, callback=self.parse_details)
def parse_details(self, response):
# 处理详情页面的数据
data = {
'title': response.css('h1::text').get(),
'content': response.css('p::text').getall(),
}
return data
在以上示例中,parse
函数返回了一个 Request 对象,让 Scrapy 继续爬取链接并调用 parse_details
函数处理响应。
总的来说,parse
函数的返回值可以是字典、Item 对象或 Request 对象。根据需求,可以选择返回不同类型的对象来实现数据提取、处理和跟进链接的功能。
3、基于终端指令的存储
Scrapy提供了命令行工具来执行数据的导出操作,无需修改代码即可完成。
以下是一些常用的命令行参数和对应的存储方式:
(1)存储为 JSON 文件
- 使用
-o
参数可以将提取的数据存储为 JSON 文件。 - 例如,将提取的数据存储为
data.json
文件:scrapy crawl spider_name -o data.json
(2)存储为 CSV 文件
- 使用
-o
参数可以将提取的数据存储为 CSV 文件。 - 例如,将提取的数据存储为
data.csv
文件:scrapy crawl spider_name -o data.csv
(3)存储为 XML 文件
- 使用
-o
参数可以将提取的数据存储为 XML 文件。 - 例如,将提取的数据存储为
data.xml
文件:scrapy crawl spider_name -o data.xml
(4)存储为 JSON Lines 文件
- 使用
-o
参数可以将提取的数据存储为 JSON Lines 文件。 - 例如,将提取的数据存储为
data.jl
文件:scrapy crawl spider_name -o data.jl
总的来说,这些命令会在项目运行结束后,将爬取到的数据自动按照指定格式保存到本地文件中。这样不仅便于后期数据分析和处理,也方便与其他系统进行数据集成。
4、基于管道的持久化存储
在Scrapy中,可以通过编写自定义的Item Pipeline来实现持久化存储数据的逻辑。以下是实现基于管道的持久化存储的一般步骤:
(1)创建自定义的Item Pipeline
在Scrapy项目中的pipelines.py文件中定义一个自定义的Item Pipeline类,该类需要包含处理数据的方法,例如process_item(self, item, spider)。在这个方法中,你可以编写逻辑来处理和存储数据。
(2)配置Item Pipeline
在Scrapy项目的settings.py文件中,找到ITEM_PIPELINES设置,并为你的自定义Item Pipeline指定一个优先级。优先级是一个整数,数字越小,优先级越高。例如:
ITEM_PIPELINES = {
'myproject.pipelines.MyCustomPipeline': 300,
}
(3)实现数据存储逻辑
在自定义的Item Pipeline类中,实现process_item方法来处理数据。你可以在这个方法中将数据存储到文件、数据库或其他地方。例如,将数据存储到数据库可以使用ORM框架或者直接执行SQL语句。
(4)数据存储的示例
下面是一个简单的示例,展示如何将提取的数据存储到文件中:
class MyCustomPipeline:
def process_item(self, item, spider):
with open('data.txt', 'a') as f:
f.write(f"{item}\n")
return item
(5)注意事项
- 在处理数据时,记得处理可能出现的异常情况,确保数据存储的稳定性和完整性。
- 在编写自定义的Item Pipeline时,可以根据需要添加数据处理、清洗、验证等逻辑。
通过编写自定义的Item Pipeline,可以根据需求实现特定的数据存储逻辑,将提取的数据持久化存储到文件、数据库或其他存储介质中。
5、总结
基于终端指令的存储和基于管道的持久化存储各有优缺点,具体如下:
(1)基于终端指令的存储
优点:
-
简单直接: 使用终端指令进行存储通常是直接的方法,不需要编写额外的代码或配置。
-
快速操作: 通过终端指令可以快速将数据存储到文件或其他目标中,适用于一次性的简单存储需求。
缺点:
-
有限灵活性: 终端指令的存储通常是静态的,难以实现复杂的数据处理逻辑或自定义存储需求。
-
不易扩展: 难以实现数据的实时处理、筛选、清洗等操作,不适用于需要复杂数据处理的场景。
(2)基于管道的持久化存储
优点:
-
灵活性高: 可以编写自定义的Item Pipeline来实现复杂的数据处理逻辑,满足各种存储需求。
-
可扩展性强: 可以根据需求扩展和定制数据处理流程,方便实现数据清洗、验证、存储等功能。
-
结构清晰: 使用Item Pipeline可以将数据处理逻辑模块化,使代码结构更清晰,易于维护和扩展。
缺点:
-
复杂性: 编写自定义的Item Pipeline需要一定的开发经验和技能,对于初学者可能有一定的学习曲线。
-
维护成本: 需要花费一定的时间和精力来编写和维护自定义的Item Pipeline,特别是在处理复杂数据处理逻辑时。
综上所述,基于终端指令的存储适用于简单、快速的存储需求,而基于管道的持久化存储适用于需要复杂数据处理和定制化存储逻辑的场景。根据具体需求和项目规模,选择合适的存储方式可以提高效率和代码质量。
四、完整示例
1、修改settings.py 主要参数
#关闭robot.txt协议
ROBOTSTXT_OBEY = False
#页面延迟下载,我这里测试,可以先不设置
DOWNLOAD_DELAY = 1
# 是否启用Cookie
COOKIES_ENABLED = True
#请求头
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/106.0.0.0 Safari/537.36'
}
#打开下载器
DOWNLOADER_MIDDLEWARES = {
'ScrapyDemmo.middlewares.ScrapydemmoDownloaderMiddleware': 543,
}
#打开优先级,并添加自己编写的图片下载管道
ITEM_PIPELINES = {
'ScrapyDemmo.pipelines.ScrapydemmoPipeline': 300,
'ScrapyDemmo.pipelines.ImageDownloadPipeline': 300,
}
#添加下载储存目录
IMAGES_STORE = 'D:\Python\pic'
# 文件保存时间
#IMAGES_EXPIRES = 90
2、定义Item字段(Items.py)
本项目用于下载图片,因此可以仅构建图片名和图片地址字段。
import scrapy
class ScrapydemmoItem(scrapy.Item):
#图片下载链接
image_url = scrapy.Field()
#图片名称
image_name = scrapy.Field()
3、编写爬虫文件(spiders目录下)
这里文件名为:image_download.py
以前用requests库和BeautifulSoup库下载图片,这里就不需要了,scrapy自带相关函数和方法。
scrapy元素定位,提供三种方式,正则、Xpath表达式、css。
我这里有xpath定位方式。
import scrapy
import re
from ..items import ScrapydemmoItem
class ImageSpider(scrapy.Spider):
name = 'image_download'
allowed_domains = ['pic.netbian.com']
start_urls = ['https://pic.netbian.com/index.html']
def parse(self, response):
# 导入Items.py字段
items = ScrapydemmoItem()
# 获取所有链接列表
lists = response.xpath('//*[@id="main"]/div[3]/ul/li')
# 点位元素循环获取图片链接和图片名称
try:
for i in lists:
# 图片名称
image_name = i.xpath('./a/span/img/@alt').get()
print(image_name)
# 图片链接
image_url = 'https://pic.netbian.com/'+ i.xpath('./a/span/img/@src').get()
items['image_url'] = image_url.replace('.278.154.jpg', '')
# 图片格式类型
image_type = re.sub(r'h.*\d+.', '', items['image_url'])
# 拼接文件名,图片名称+图片格式
items['image_name'] = '{}.{}'.format(image_name, image_type)
yield items
except:
pass
# 循环跳转下一页,并重复返回数据,这里测试先下载1页的图片,总共23页。
for i in range(2, 3):
next_url = 'https://desk.3gbizhi.com/deskMV/index_{}.html'.format(i)
yield scrapy.Request(next_url, callback=self.parse)
关于 yield 的理解,⾸先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它⾸先是个return。
最主要的不同在于yield在返回值后还可以继续运行接下来的代码,使用的函数会返回一个生成器,而return在返回后就不在执行代码。
以上两个yield:
-
yield items:这里我们通过 yield 返回的不是 Request 对象,而是一个 ScrapydemmoItem 对象。
- scrap有框架获得这个对象之后,会将这个对象传递给 pipelines.py来做进一步处理。
- 我们将在 pipelines.py里将传递过来的 scrapy.Item 对象保存到数据库里去。
-
yield scrapy.Request:这里是在爬取完一页的信息后,我们在当前页面获取到了下一页的链接,然后通过 yield 发起请求,并且将 parse 自己作为回调函数来处理下一页的响应。
4、修改管道文件pipelines.py用于下载图片
除了爬取文本,我们可能还需要下载文件、视频、图片、压缩包等,这也是一些常见的需求。scrapy提供了FilesPipeline和ImagesPipeline,专门用于下载普通文件及图片。
继承 Scrapy 内置的 ImagesPipeline,只需要重写get_media_requests 和item_completed函数即可。
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
from scrapy import Request
class ScrapydemmoPipeline:
def process_item(self, item, spider):
return item
class ImageDownloadPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
# 下载图片,如果传过来的是集合需要循环下载
# meta里面的数据是从spider获取,然后通过meta传递给下面方法:file_path
yield Request(url=item['image_url'], meta={'filename': item['image_name']})
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
def file_path(self, request, response=None, info=None):
# 接收上面meta传递过来的图片名称
file_name = request.meta['filename']
return file_name
- **get_media_requests(): **它的第一个参数 item 是爬取生成的 Item 对象。我们将它的 url 字段取出来,然后直接生成 Request 对象。此 Request 加入调度队列,等待被调度,执行下载。
- **item_completed(): **它是当单个 Item 完成下载时的处理方法。因为可能有个别图片未成功下载,所以需要分析下载结果并剔除下载失败的图片。该方法的第一个参数 results 就是该 Item 对应的下载结果,它是一个列表形式,列表每一个元素是一个元组,其中包含了下载成功或失败的信息。这里我们遍历下载结果找出所有成功的下载列表。如果列表为空,那么说明该 Item 对应的图片下载失败了,随即抛出异常DropItem,该 Item 忽略。否则返回该 Item,说明此 Item 有效。
以上两个函数即可下载图片了,图片名称为自动已哈希值命名,如:0db6e07054d966513f0a6f315b687f205c7ced90.jpg 这种命名方式不友好,所以我们需要重写 file_path函数,自定义图片名称。
- file_path():它的第一个参数 request 就是当前下载对应的 Request 对象。这个方法用来返回保存的文件名,接收上面meta传递过来的图片名称,将图片以原来的名称和定义格式进行保存。
5、编写执行文件run.py运行
在项目下新建run.py作为执行文件
from scrapy import cmdline
#cmdline.execute('scrapy crawl image_download --nolog'.split())
cmdline.execute('scrapy crawl image_download'.split())
6、小结
除了 ImagesPipeline 处理图片外,还有 FilesPipeline 可以处理文件,使用方法与图片类似,事实上 ImagesPipeline 是 FilesPipeline 的子类,因为图片也是文件的一种。
标签:存储,持久,image,Item,scrapy,数据 From: https://www.cnblogs.com/xiao01/p/18116251