zoukankan      html  css  js  c++  java
  • Scarpy框架持久化存储

    一、介绍

      持久化存储操作分为两类:磁盘文件数据库
      而磁盘文件存储方式又分为:基于终端指令基于管道

    二、基于终端指令的持久化存储

      Scrapy是通过 scrapy 命令行工具进行控制的。 这里我们称之为 “Scrapy tool” 以用来和子命令进行区分。 对于子命令,我们称为 “command” 或者 “Scrapy commands”。

    1、保证parse方法返回一个可迭代类型的对象(存储解析到的页面内容)

      改写parse方法,让方法返回值为迭代器。

    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
    

    2、使用终端指令完成数据存储到指定磁盘文件中

      执行输出指定格式进行存储:将爬取到的数据写入不同格式的文件中进行存储。
      常用文件格式:json、xml、csv、txt等。

    # scrapy crawl 爬虫文件名称 -o 磁盘文件.后缀
    $ scrapy crawl qiubaipro -o qiubai.csv --nolog
    $ ls
    firstBlood	qiubai.csv	readme.md	scrapy.cfg
    

    3、csv持久化存储效果

    csv表格

    三、基于管道的持久化存储

      scrapy框架中已经为我们专门集成好了高效、便捷的持久化操作功能,我们直接使用即可。

    1、持久化需要的项目文件

      想使用scrapy的持久化操作功能,我们首先来认识如下两个文件:

    items.py:数据结构模板文件,定义数据属性。存储解析到的页面数据
    pipelines.py:管道文件。接收数据(items),进行持久化存储的相关操作。
    

    2、持久化实现流程

    1. 爬虫文件爬取到数据后,需要将数据封装到items对象中。
    2. 使用yield关键字将items对象提交给pipelines管道进行持久化操作。
    3. 在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码将item对象中存储的数据进行持久化存储
    4. settings.py配置文件中开启管道

    3、代码示例

    (1)将解析到的数据值(author和content)存储到items对象(存储前先声明item属性)

      在爬虫文件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() 
                
                # 1、将解析到的数据值(author和content)存储到items对象(存储前先声明item属性)
                item = FirstbloodItem()
                item['author'] = author
                item['content'] = content
    

    注意:
      1)解析的数据存储到items对象前要先声明item属性。
      2)items.py文件内容配置如下:

    import scrapy
    
    class FirstbloodItem(scrapy.Item):
        # 必须遵从如下属性声明规则
        # name = scrapy.Field()
    
        # 声明item属性
        author = scrapy.Field()  # 存储解析到的作者
        content = scrapy.Field()  # 存储解析到的内容信息
    

    (2)yield将item对象提交给管道进行持久化存储操作

      qiubaipro.py文件中:

    import scrapy
    from firstBlood.items import FirstbloodItem
    
    class QiubaiproSpider(scrapy.Spider):
        def parse(self, response):
            """省略代码"""
            for div in div_list:
                """省略代码"""
    
                # 2、yield将item对象提交给管道进行持久化存储操作
                yield item
    

    (3)在管道文件(pipelines.py)中编写代码完成数据存储的操作

    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+ '
    
    
    ')  # 写入数据
            return item
    
        def close_spider(self, spider):
            """
            该方法只会在爬虫结束时被调用一次
            :param spider:
            :return:
            """
            print("爬虫结束")
            # 关闭文件
            self.fp.close()
    

    注意:
      1)每当爬虫文件向管道提交一次item,process_item方法就会执行一次,因此最后输出的爬虫结果只保存了最后一次打开写入的数据,前面的数据均被覆盖。
      2)使用open_spider方法特性:只会在爬虫开始爬数据的时候被调用一次。可解决多次打开文件的问题。
      3)使用close_spider方法特性:只会在爬虫结束时被调用一次。可在爬虫结束后关闭文件。

    (4)在配置文件(settings.py)中开启管道操作

    # Configure item pipelines
    # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
        'firstBlood.pipelines.FirstbloodPipeline': 300,      # 优先级
    }
    

    (5)执行爬虫操作

    $ 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()
    

    1、基于数据库编码流程

    1. 将解析到的页面数据存储到items对象
    2. 使用yield关键字将items提交给管道文件进行处理
    3. 在管道文件中编写代码完成数据存储的操作
    4. 在配置文件中开启管道操作

    2、基于mysql的管道存储

    (1)创建数据库

    # 登录数据库
    $ 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)
    

    (2)编写qiubaiMysql.py:

    # -*- 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
    

    (3)编写pipelines.py:

    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:
            """
            # 1、连接数据库:open_spider()
            # 2、执行sql语句
            sql = 'insert into qiubai values("%s", "%s")' % (item['author'], item['content'])
            self.cursor = self.conn.cursor()  # 创建游标对象
            try:
                self.cursor.execute(sql)   # 执行sql语句
                # 3、提交事务
                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()    # 关闭连接对象
    

    (4)执行爬虫程序

    $ scrapy crawl qiubaiMysql --nolog
    
    # 查看数据库
    $ mysql -uroot -p1234
    mysql> use qiubai
    mysql> select * from qiubai;
    

    3、基于redis数据库存储

    (1)编写爬虫文件:qiubaiRedis.py

    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匹配类属性
    
                # 一、创建item对象
                item = QiubaidbItem()
                # 数据值写入item对象中
                item['author'] = author
                item['content'] = content
    
                # 二、提交给管道(循环几次就提交几次)
                yield item
    

    (2)重新编写pipelines.py

    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)   # 转为字符串(执行时,要求转化为bytestring
    umber)
            # redis数据库写入
            self.conn.lpush('data', dic_str)  # 每一次获取的值追加到列表当中
            return item
    

    (3)执行爬虫查看结果

      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中(管道高级操作)

      如果最终需要将爬取到的数据一份存储到磁盘文件,一份存储到redis数据库,一份保存在mysql数据库中。

    1、修改配置管道文件pipelines.py

      需要在管道文件中编写对应平台的管道类:

    # 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:
            """
            # 1、连接数据库:open_spider()
            # 2、执行sql语句
            sql = 'insert into qiubai values("%s", "%s")' % (item['author'], item['content'])
            self.cursor = self.conn.cursor()  # 创建游标对象
            try:
                self.cursor.execute(sql)   # 执行sql语句
                # 3、提交事务
                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+ '
    
    
    ')  # 写入数据
            return item
    
        def close_spider(self, spider):
            print("爬虫结束")
            # 关闭文件
            self.fp.close()
    

    2、settings.py中开启管道操作

      在配置文件中对自定义的管道类进行生效操作:

    # 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   # 文件
    }
    

    六、递归爬取解析多页页面数据

    1、多页爬取需求分析

      需求:将糗事百科所有页码的作者和段子内容数据进行爬取切持久化存储。
      需求分析:每一个页面对应一个url,则scrapy工程需要对每一个页码对应的url依次发起请求,然后通过对应的解析方法进行作者和段子内容的解析。

    2、实现方案

    1. 将每一个页码对应的url存放到爬虫文件的起始url列表(start_urls)中。(不推荐)
    2. 使用Request方法手动发起请求。(推荐

    3、项目创建

    $ 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
    

    4、单url编码实现(准备工作)

    (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,
    }
    

    5、请求手动发送实现多url数据爬取

      修改爬虫文件: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 。这是一个回调函数,会进行递归的调用。为了防止无限循环,需要设置递归终止条件。

    6、执行验证

    $ scrapy crawl qiubai --nolog
    开始爬虫
    爬取到了第1页的页面数据
    爬取到了第2页的页面数据
    爬取到了第3页的页面数据
    爬取到了第4页的页面数据
    爬取到了第5页的页面数据
    爬取到了第6页的页面数据
    爬取到了第7页的页面数据
    爬取到了第8页的页面数据
    爬取到了第9页的页面数据
    爬取到了第10页的页面数据
    爬取到了第11页的页面数据
    爬取到了第12页的页面数据
    爬取到了第13页的页面数据
    爬虫结束
    
  • 相关阅读:
    android 使用AlarmManager定时启动service
    mac 删除文件夹里所有的.svn文件
    Eclipse配置SVN
    OSD锁定怎么解锁?
    如何用Mac远程桌面连接windows
    获取设备识别信息
    类库冲突问题
    如何将Eclipse中的项目迁移到Android Studio中
    python 日历(Calendar)模块
    python datetime处理时间(转)
  • 原文地址:https://www.cnblogs.com/xiugeng/p/10051913.html
Copyright © 2011-2022 走看看