持久化存储操做分为两类:磁盘文件和数据库。
而磁盘文件存储方式又分为:基于终端指令和基于管道html
Scrapy是经过 scrapy 命令行工具进行控制的。 这里咱们称之为 “Scrapy tool” 以用来和子命令进行区分。 对于子命令,咱们称为 “command” 或者 “Scrapy commands”。python
改写parse方法,让方法返回值为迭代器。mysql
class QiubaiproSpider(scrapy.Spider): name = 'qiubaipro' # allowed_domains = ['www.qiushibaike.com/text'] # 图片等信息可能不属于指定域名之下 start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默认协议头 def parse(self, response): # 建议使用xpath来执行指定内容的解析(Scrapy已经集成好了xpath解析的接口) # 段子的内容和做者 div_list = response.xpath('//div[@id="content-left"]/div') # 存储解析到的页面数据 data_list = [] for div in div_list: author = div.xpath('./div/a[2]/h2/text()').extract_first() # './'表示解析当前局部div; a[2]表示第二个a标签 content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # './/'表示当前局部全部元素;@class匹配类属性 dict = { 'author': author, 'content': content } data_list.append(dict) # parse方法的返回值必须是迭代器或空 return data_list
执行输出指定格式进行存储:将爬取到的数据写入不一样格式的文件中进行存储。
经常使用文件格式:json、xml、csv、txt等。web
# scrapy crawl 爬虫文件名称 -o 磁盘文件.后缀 $ scrapy crawl qiubaipro -o qiubai.csv --nolog $ ls firstBlood qiubai.csv readme.md scrapy.cfg
scrapy框架中已经为咱们专门集成好了高效、便捷的持久化操做功能,咱们直接使用便可。redis
想使用scrapy的持久化操做功能,咱们首先来认识以下两个文件:sql
items.py:数据结构模板文件,定义数据属性。存储解析到的页面数据 pipelines.py:管道文件。接收数据(items),进行持久化存储的相关操做。
在爬虫文件qiubaipro.py中引入了项目的items.py文件中的FirstbloodItem类。数据库
# -*- coding: utf-8 -*- import scrapy from firstBlood.firstBlood.items import FirstbloodItem class QiubaiproSpider(scrapy.Spider): name = 'qiubaipro' # allowed_domains = ['www.qiushibaike.com/text'] # 图片等信息可能不属于指定域名之下 start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默认协议头 def parse(self, response): div_list = response.xpath('//div[@id="content-left"]/div') # 存储解析到的页面数据 data_list = [] for div in div_list: author = div.xpath('./div/a[2]/h2/text()').extract_first() content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # 一、将解析到的数据值(author和content)存储到items对象(存储前先声明item属性) item = FirstbloodItem() item['author'] = author item['content'] = content
注意:
1)解析的数据存储到items对象前要先声明item属性。
2)items.py文件内容配置以下:json
import scrapy class FirstbloodItem(scrapy.Item): # 必须听从以下属性声明规则 # name = scrapy.Field() # 声明item属性 author = scrapy.Field() # 存储解析到的做者 content = scrapy.Field() # 存储解析到的内容信息
qiubaipro.py文件中:bash
import scrapy from firstBlood.items import FirstbloodItem class QiubaiproSpider(scrapy.Spider): def parse(self, response): """省略代码""" for div in div_list: """省略代码""" # 二、yield将item对象提交给管道进行持久化存储操做 yield item
class FirstbloodPipeline(object): fp = None def open_spider(self, spider): """ 该方法只会在爬虫开始爬数据的时候被调用一次 :param spider: :return: """ print("开始爬虫") # 在该方法中打开文件 self.fp = open('./qiubai_pipe.txt', 'w', encoding='utf-8') def process_item(self, item, spider): """ 接收爬虫文件中提交的item对象,并对item对象中存储的页面数据进行持久化存储 每当爬虫文件向管道提交一次item,porcess_item方法就会执行一次 :param item: 表示接收到的item对象 :param spider: :return: """ # 取出Item对象中存储的数据值 author = item['author'] content = item['content'] # 将item对象中存储的数据进行持久化存储 # with open('./qiubai_pipe.txt', 'w', encoding='utf-8') as fp: self.fp.write(author + ":" + content+ '\n\n\n') # 写入数据 return item def close_spider(self, spider): """ 该方法只会在爬虫结束时被调用一次 :param spider: :return: """ print("爬虫结束") # 关闭文件 self.fp.close()
注意:
1)每当爬虫文件向管道提交一次item,process_item方法就会执行一次,所以最后输出的爬虫结果只保存了最后一次打开写入的数据,前面的数据均被覆盖。
2)使用open_spider方法特性:只会在爬虫开始爬数据的时候被调用一次。可解决屡次打开文件的问题。
3)使用close_spider方法特性:只会在爬虫结束时被调用一次。可在爬虫结束后关闭文件。数据结构
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'firstBlood.pipelines.FirstbloodPipeline': 300, # 优先级 }
$ scrapy crawl qiubaipro --nolog
1)建立爬虫项目和爬虫应用:
# 建立项目 $ scrapy startproject qiubaiDB # 切换到项目目录,建立爬虫应用 $ scrapy genspider qiubaiMysql www.qiushibaike.com/text $ scrapy genspider qiubaiRedis www.qiushibaike.com/text
2)settings.py配置文件(同pip):
# Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' # 假装请求载体身份 # Obey robots.txt rules ROBOTSTXT_OBEY = False # 不听从门户网站robots协议,避免某些信息爬取不到 # Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'qiubaiDB.pipelines.QiubaidbPipeline': 300, # 注意这里须要取消注释(不能替换) }
3)items.py中声明属性:
import scrapy class QiubaidbItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 声明属性 author = scrapy.Field() content = scrapy.Field()
# 登陆数据库 $ mysql -uroot -p1234 # 建立qiubai数据库 mysql> create database qiubai; mysql> use qiubai Database changed # 建立qiubai表 mysql> create table qiubai( -> author varchar(50), -> content varchar(1000) -> ); Query OK, 0 rows affected (0.13 sec)
# -*- coding: utf-8 -*- import scrapy from qiubaiDB.items import QiubaidbItem class QiubaimysqlSpider(scrapy.Spider): name = 'qiubaiMysql' # allowed_domains = ['www.qiushibaike.com/text'] # 图片等信息可能不属于指定域名之下 start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默认协议头 def parse(self, response): # 段子的内容和做者 div_list = response.xpath('//div[@id="content-left"]/div') # 使用xpath来执行指定内容的解析 for div in div_list: # 经过xpath解析到的指定内容被存储到了selector对象中 # 须要经过extract()方法来提取selector对象中存储的数据值 author = div.xpath('./div/a[2]/h2/text()').extract_first() # './'表示解析当前局部div; a[2]表示第二个a标签 content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # './/'表示当前局部全部元素;@class匹配类属性 # 建立item对象 item = QiubaidbItem() # 数据值写入item对象中 item['author'] = author item['content'] = content # 提交给管道(循环几回就提交几回) yield item
import pymysql class QiubaidbPipeline(object): conn = None # 链接对象声明为全局属性 cursor = None # 游标对象 def open_spider(self, spider): print("开始爬虫") # 链接数据库 self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='1234', db='qiubai') # 注意端口是整数不是字符串 def process_item(self, item, spider): """ 编写向数据库中存储数据的相关代码 :param item: :param spider: :return: """ # 一、链接数据库:open_spider() # 二、执行sql语句 sql = 'insert into qiubai values("%s", "%s")' % (item['author'], item['content']) self.cursor = self.conn.cursor() # 建立游标对象 try: self.cursor.execute(sql) # 执行sql语句 # 三、提交事务 self.conn.commit() except Exception as e: # 出现错误的时候:打印错误并回滚 print(e) self.conn.rollback() return item def close_spider(self, spider): print("爬虫结束") self.cursor.close() # 关闭游标 self.conn.close() # 关闭链接对象
$ scrapy crawl qiubaiMysql --nolog # 查看数据库 $ mysql -uroot -p1234 mysql> use qiubai mysql> select * from qiubai;
import scrapy from qiubaiDB.items import QiubaidbItem class QiubairedisSpider(scrapy.Spider): name = 'qiubaiRedis' allowed_domains = ['www.qiushibaike.com/text'] start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默认协议头 def parse(self, response): # 段子的内容和做者 div_list = response.xpath('//div[@id="content-left"]/div') # 使用xpath来执行指定内容的解析 for div in div_list: # 经过xpath解析到的指定内容被存储到了selector对象中 # 须要经过extract()方法来提取selector对象中存储的数据值 author = div.xpath('./div/a[2]/h2/text()').extract_first() # './'表示解析当前局部div; a[2]表示第二个a标签 content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # './/'表示当前局部全部元素;@class匹配类属性 # 1、建立item对象 item = QiubaidbItem() # 数据值写入item对象中 item['author'] = author item['content'] = content # 2、提交给管道(循环几回就提交几回) yield item
import redis import json class QiubaidbPipeline(object): conn = None # 声明全局链接对象 def open_spider(self, spider): print("开始爬虫") # 链接redis数据库 self.conn = redis.Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): """编写向redis中存储数据的相关代码""" dic = { # dic中封装item对象中获取的页面数据 'author': item['author'], 'content': item['content'] } dic_str = json.dumps(dic) # 转为字符串(执行时,要求转化为byte\string\number) # redis数据库写入 self.conn.lpush('data', dic_str) # 每一次获取的值追加到列表当中 return item
redis的安装部署和服务启动方法详见:
Redis介绍
$ scrapy crawl qiubaiRedis --nolog # 启动redis客户端 $ pwd /Users/hqs/redis-5.0.2 $ src/redis-cli 127.0.0.1:6379> lrange data 0 -1 # 返回列表中指定区间内的元素;START:0 表示列表的第一个元素;END:-1 表示列表的最后一个元素
若是最终须要将爬取到的数据一份存储到磁盘文件,一份存储到redis数据库,一份保存在mysql数据库中。
须要在管道文件中编写对应平台的管道类:
# Mysql版本 import pymysql class QiubaiMysqlPipeline(object): conn = None # 链接对象声明为全局属性 cursor = None # 游标对象 def open_spider(self, spider): print("开始爬虫") # 链接数据库 self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='1234', db='qiubai') def process_item(self, item, spider): """ 编写向数据库中存储数据的相关代码 :param item: :param spider: :return: """ # 一、链接数据库:open_spider() # 二、执行sql语句 sql = 'insert into qiubai values("%s", "%s")' % (item['author'], item['content']) self.cursor = self.conn.cursor() # 建立游标对象 try: self.cursor.execute(sql) # 执行sql语句 # 三、提交事务 self.conn.commit() except Exception as e: # 出现错误的时候:打印错误并回滚 print(e) self.conn.rollback() return item def close_spider(self, spider): print("爬虫结束") self.cursor.close() # 关闭游标 self.conn.close() # 关闭链接对象 # redis版本 import redis import json class QiubaidbPipeline(object): conn = None # 声明全局链接对象 def open_spider(self, spider): print("开始爬虫") # 链接redis数据库 self.conn = redis.Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): """编写向redis中存储数据的相关代码""" dic = { # dic中封装item对象中获取的页面数据 'author': item['author'], 'content': item['content'] } dic_str = json.dumps(dic) # 转为字符串 # redis数据库写入 # lpush:从左往右添加元素。在key对应list的头部添加字符串元素 self.conn.lpush('data', dic_str) # 每一次获取的值追加到列表当中 return item # 文件保存 class QiubaiByFilesPipeline(object): """实现将数据值存储到本地磁盘中""" fp = None def open_spider(self, spider): print("开始爬虫") # 在该方法中打开文件 self.fp = open('./qiubai_pipe.txt', 'w', encoding='utf-8') def process_item(self, item, spider): # 取出Item对象中存储的数据值 author = item['author'] content = item['content'] # 持久化存储 self.fp.write(author + ":" + content+ '\n\n\n') # 写入数据 return item def close_spider(self, spider): print("爬虫结束") # 关闭文件 self.fp.close()
在配置文件中对自定义的管道类进行生效操做:
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { # 数值多少无所谓,数值大小表明优先级顺序: 500>400>300 'qiubaiDB.pipelines.QiubaidbPipeline': 300, # redis 'qiubaiDB.pipelines.QiubaiMysqlPipeline': 500, # mysql 'qiubaiDB.pipelines.QiubaiByFilesPipeline': 400 # 文件 }
需求:将糗事百科全部页码的做者和段子内容数据进行爬取切持久化存储。
需求分析:每个页面对应一个url,则scrapy工程须要对每个页码对应的url依次发起请求,而后经过对应的解析方法进行做者和段子内容的解析。
$ pwd /Users/hqs/ScrapyProjects $ scrapy startproject qiubaiByPages New Scrapy project 'qiubaiByPages', using template directory '/Users/hqs/anaconda3/lib/python3.7/site-packages/scrapy/templates/project', created in: /Users/hqs/ScrapyProjects/qiubaiByPages You can start your first spider with: cd qiubaiByPages scrapy genspider example example.com $ cd qiubaiByPages/ $ scrapy genspider qiubai www.qiushibaike.com/text Created spider 'qiubai' using template 'basic' in module: qiubaiByPages.spiders.qiubai
(1)爬虫文件:qiubai.py
import scrapy from qiubaiByPages.items import QiubaibypagesItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.qiushibaike.com/text'] # 容许的域名(不少网页不在域名下) start_urls = ['https://www.qiushibaike.com/text/'] # 默认消息头是http,这里手动调整 def parse(self, response): div_list = response.xpath('//*[@id="content-left"]/div') for div in div_list: author = div.xpath('./div[@class="author clearfix"]/a[2]/h2/text()').extract_first() # selector对象 content = div.xpath('.//div[@class="content" ]/span/text()').extract_first() # 将解析的数据值存储到items对象中 item = QiubaibypagesItem() item["author"] = author item["content"] = content # 将item对象提交给管道文件 yield item
(2)数据存储模板:items.py文件
import scrapy class QiubaibypagesItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() content = scrapy.Field()
(3)数据持久化处理:pipelines.py
class QiubaibypagesPipeline(object): fp = None def open_spider(self, spider): print("开始爬虫") self.fp = open('./qiubai.txt', 'w', encoding="utf-8") def process_item(self, item, spider): self.fp.write(item['author']+ ":" + item['content']) return item def close_spider(self, spider): self.fp.close() print("爬虫结束")
(4)配置文件:settings.py
# Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' # 假装请求载体身份 # Obey robots.txt rules ROBOTSTXT_OBEY = False # Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { # 开启管道 'qiubaiByPages.pipelines.QiubaibypagesPipeline': 300, }
修改爬虫文件:qiubai.py
import scrapy from qiubaiByPages.items import QiubaibypagesItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.qiushibaike.com/text'] # 容许的域名(不少网页不在域名下) start_urls = ['https://www.qiushibaike.com/text/'] # 默认消息头是http,这里手动调整 # 设计通用的url模板 url = 'https://www.qiushibaike.com/text/page/%d' # 分页的通用格式 pageNum = 1 def parse(self, response): div_list = response.xpath('//*[@id="content-left"]/div') for div in div_list: author = div.xpath('./div[@class="author clearfix"]/a[2]/h2/text()').extract_first() # selector对象 content = div.xpath('.//div[@class="content" ]/span/text()').extract_first() # 将解析的数据值存储到items对象中 item = QiubaibypagesItem() item["author"] = author item["content"] = content # 将item对象提交给管道文件 yield item # 请求的手动发送 if self.pageNum <= 13: # 递归终止条件(13是最后一页的页码) print('爬取到了第%d页的页面数据' % self.pageNum) self.pageNum += 1 # 从第二个页码开始手动请求 new_url = format(self.url % self.pageNum) # 'https://www.qiushibaike.com/text/page/2/' # scrapy.Request对指定url发请求 # callback:将请求获取到的页面数据进行解析 yield scrapy.Request(url=new_url, callback=self.parse)
注意:
1)第一页仍是使用的起始url列表这个机制来进行请求发送,从第二页开始采用手动请求发送。
2)执行请求手动发送必须结合 yield 和 Request 函数一块来使用。
3)在调用scrapy.Request函数时,有一个参数叫 callback 。这是一个回调函数,会进行递归的调用。为了防止无限循环,须要设置递归终止条件。
$ scrapy crawl qiubai --nolog 开始爬虫 爬取到了第1页的页面数据 爬取到了第2页的页面数据 爬取到了第3页的页面数据 爬取到了第4页的页面数据 爬取到了第5页的页面数据 爬取到了第6页的页面数据 爬取到了第7页的页面数据 爬取到了第8页的页面数据 爬取到了第9页的页面数据 爬取到了第10页的页面数据 爬取到了第11页的页面数据 爬取到了第12页的页面数据 爬取到了第13页的页面数据 爬虫结束