一:基于终端的持久化存储
1 :功能
只可以将parse方法的返回值存储到本地的文本文件中。
2 :代码实例
存储命令格式:
scrapy crawl 爬虫名称 -o 存储文件路径
我这里将上一篇中数据解析的代码重新利用一下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2022/3/31 18:01
# @Author : camellia
# @Email : 805795955@qq.com
# @File : beike.py
# @Software: PyCharm
import time
import scrapy
import asyncio
import requests
import random
# 解析贝壳二手房页面的房源图片及标题
class BeikeSpider(scrapy.Spider):
name = 'beike'
# allowed_domains = ['www.beike.com']
start_urls = ['https://dl.ke.com/ershoufang/']
def parse(self, response):
divList = response.xpath('/html/body/div[1]/div[4]/div[1]/div[4]/ul/li[1]')
title = []
for item in divList:
temp = {}
temp['title'] = item.xpath('//ul[@class="sellListContent"]//img[@class="lj-lazy"]/@title').extract()
title.append(temp)
print(title)
return title
执行存储命令:
scrapy crawl beike -o ./data.json
上方代码输出:
[{'title': ['马栏广场没坡,精装修两室,辽师家属楼质量好,2号线', '店长推荐!婚房装修,全明户型保持好拎包入住!',……]}]
查看项目目录,发现多了一个data.json文件,如下图所示:
这个时候,我就很好奇了,我可以把他存在json中,我是不是也可以将他存在txt文本中呢?
试一下。
执行:
scrapy crawl beike -O ./data.txt
果然报错了,报错信息如下:
Usage
=====
scrapy crawl [options] <spider>
crawl: error: Unrecognized output format 'txt'. Set a supported one (('json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle')) after a colon at the end of the output URI (i.e. -o/-O <URI>:<FORMAT>) or as a file extension.
报错信息显示,持久化存储只能是 'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle' 格式
最后,总结一下基于终端指令的持久化存储的优缺点:
优点:简洁高效便捷
缺点:有较强的局限性(数据只可以存储到指定后缀的文本文件中)
二:基于管道的持久化存储
1 :编码流程
(1):数据解析
(2):在item类中定义相关的属性
(3):将解析的数据封装存储到item类型的对象
(4):将item类型的对象提交给管道进行持久化存储
(5):在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
(6):在配置文件中开启管道
接下来,我们用上方的的小例子修改一下,使用管道来持久化存储。
Beike.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2022/3/31 18:01
# @Author : camellia
# @Email : 805795955@qq.com
# @File : beike.py
# @Software: PyCharm
import time
import scrapy
import asyncio
import requests
import random
from scrpayProject.items import ScrpayprojectItem
# 解析贝壳二手房页面的房源图片及标题
class BeikeSpider(scrapy.Spider):
name = 'beike'
# allowed_domains = ['www.beike.com']
start_urls = ['https://dl.ke.com/ershoufang/']
def parse(self, response):
divList = response.xpath('/html/body/div[1]/div[4]/div[1]/div[4]/ul/li[1]')
title = []
for div in divList:
temp = {}
title = div.xpath('//ul[@class="sellListContent"]//img[@class="lj-lazy"]/@title').extract()
for it in title:
item = ScrpayprojectItem()
item['author'] = it # author属性必须在items.py中声明
# item['author'] = author
yield item # 将item提交给管道
接下来我们需要用到两个框架中的文件items.py以及pipelines.py,那么这两个文件在哪里呢?如下图所示:
Items.py
import scrapy
class ScrpayprojectItem(scrapy.Item):
# define the fields for your item here like:
author = scrapy.Field()
pass
pipelines.py
class ScrpayprojectPipeline:
fp = None
# 重写父类的一个方法,该方法只在开始爬虫的时候调用一次
def open_spider(self,spider):
print('开始爬虫……')
self.fp = open('./data.txt','w',encoding='utf-8')
# 该方法每接到一个item就会调用一次
def process_item(self, item, spider):
if item is not None:
author = item['author']
self.fp.write(author + ':' + author + '\n')
return item
# 重写父类的一个方法,该方法只在结束爬虫的时候调用一次
def close_spider(self,spider):
print('结束爬虫……')
self.fp.close()
到这里为止,代码部分就结束了,对,你没看错,就是这么简单。
然后,我们需要在settings.py中将部分代码解开注释:
ITEM_PIPELINES = {
'scrpayProject.pipelines.ScrpayprojectPipeline': 300,
}
这里的300代表代码优先级,数字越小,优先级越大。
到这里,基于管道的持久化存储就已经完成,我这里测试一下,打开终端,执行命令:
scrapy crawl beike
输出:
开始爬虫……
结束爬虫……
打开项目目录,我们可以发现多了一个data.txt。打开文件,发现其中内容,即可证明我们的持久化存储成功。
那么现在就有一个小问题,如果我想将爬取到的数据,一份存储到本地的文件中,一份存储到数据库之中,该如何利用管道来实现这个功能呢?
解决方式很直接,就是在pipelines.py中再定义一个管道类
代码如下所示:
import pymysql
class mysqlPipeline:
conn = None
cursor = None
# 重写父类的一个方法,该方法只在开始爬虫的时候调用一次
def open_spider(self,spider):
print('开始写入数据库……')
self.conn = pymysql.Connect(host="39.107.142.167",port=3306,user='root',password='root@mule123',db='test')
# 该方法每接到一个item就会调用一次
def process_item(self, item, spider):
self.cursor = self.conn.cursor()
try:
# 执行 插入语句
self.cursor.execute('insert into test values("%s")'%(item["author"]))
# 数据库提交
self.conn.commit()
except Exception as e:
print(e)
# 数据库回滚
self.conn.rollback()
return item
# 重写父类的一个方法,该方法只在结束爬虫的时候调用一次
def close_spider(self,spider):
print('结束写入数据库……')
self.conn.close()
self.cursor.close()
然后,我们需要在settings.py中配置一下新增的管道类:
ITEM_PIPELINES = {
'scrpayProject.pipelines.ScrpayprojectPipeline': 300,
'scrpayProject.pipelines.mysqlPipeline': 301,
}
这里配置的数据库管道类优先级要比上边的写入文件管道类低。那么这里就有一个小问题,
爬虫文件提交的item类型的对象最终会提交给哪一个管道类呢?
答案是,交给优先级较高的那个管道类。
那么,问题又来了,提交给优先级较高的管道类之后,如何在传递给优先级低的管道类呢?
这部分,python早已经为我们想好了。
# 该方法每接到一个item就会调用一次
def process_item(self, item, spider):
if item is not None:
author = item['author']
self.fp.write(author + ':' + author + '\n')
return item
注意代码中的return item,这句话的意思就是将item继续传递给优先级较低的管道类。
所以,我们在编写管道类的方法的时候,随手在代码结束的位置写一下return item
不然,出问题,排查起来也很麻烦。
至此,代码完成了,接下来我们运行一下代码,打开终端,输入:
scrapy crawl beike
输出:
开始爬虫……
开始写入数据库……
结束写入数据库……
结束爬虫……
打开数据库,发现test表中已写入数据:
我这里是从贝壳爬取的数据。
最后总结一下:
管道文件中的一个管道类对应的是将数据存储到一种平台。
爬虫文件提交的item只会给管道文件中第一个被执行的管道类接受
Process_item中的return item 标识将item传递给下一个即将被执行的管道类
以上大概就是 爬虫基于管道的持久化操作,慢慢理解,其实不难。
有好的建议,请在下方输入你的评论。