zoukankan      html  css  js  c++  java
  • Scrapy 数据格持久化

    爬取到的数据想要进行保存的话, 首先要对数据进行格式化话,这样数据格式统一才方便进行保存

    数据格式化

    item.py

    在我们创建的爬虫项目中 item.py 这个文件就是负责进行格式化数据的

    import scrapy
    
    # 这里我们可以创建多个class,每个对象中的字段就是我们要格式化的数据
    # 当然一个对象也可以有多个字段,但只能是scrapy.Field()
    
    # 特殊的字典结构 可以在scrapy中传递数据
    class MztCrawlItem(scrapy.Item):
        # define the fields for your item here like:
        name = scrapy.Field()
        pass
    

    在爬虫文件中的应用

    import scrapy
    
    from ..items import MztCrawlItem
    
    
    class MztSpider(scrapy.Spider):
        name = 'mzt'
        # allowed_domains = ['www.laotuzi.com/meizitu/']
        start_urls = ['http://www.laotuzi.com/meizitu/']
    
        def parse(self, response):
            # 实例化一个item对象
            item = MztCrawlItem()
            item['name'] = response.text
            # 当yield的是一个item对象,就会触发pipeline中的process_item方法
            yield item
    
    

    多个 item 处理

    # item.py 
    
    class SpiderItem1(scrapy.Item):
    	pass
    
    class SpiderItem2(scrapy.Item):
    	pass
    
    class SpiderItem3(scrapy.Item):
    	pass
    
    
    # pipelines.py
    # 注意导入路径从当前项目开始
    from spider1.spider1.items import Spider1Item, Spider2Item, Spider3Item,
    
    def process_item(self, item, spider):
      # 使用isinstance然后通过if判断方式进行items类别筛选
    
      if isinstance(item,  Spider1Item):
        pass
      elif isinstance(item,  Spider2Item):
        pass
      elif isinstance(item,  Spider3Item):
        pass
      return item
    

    数据持久化

    在项目中 pipeline.py 文件就是专门用来对接收到的 item 对象进行数据持久化的,在本文件中,一个 Pipeline 类就对应一种数据持久化的方案,比如你可以将数据保存成 csv 文件,你可以写一个 pipeline 类,你要保存到 mysql 数据库,你也可以写一个类,一般比较重要有价值的数据都会进行多种形式的持久化。

    因为这些类中的方法都是一样的,只不过方法的功能不同,这里我们就举一个pipeline类,进行剖析

    pipeline 方法解读

    # 在pipeline类中,可以写这5个方法,可以少,但不可以多。有就执行,没有的话就不执行
    # 通常情况下默认只有一个process_item(self, item, spider):方法
    
    class Spider1Pipeline:
        def __init__(self):
            print('实例化一个对象')
            pass
    
        def process_item(self, item, spider):
            # 当爬虫文件中回调函数每yield一个item对象时会自动调用一次这个方法
            # 对item对象中的数据进行持久化操作
            d = dict(item)
            print(d)
            return item
    
        @classmethod
        def from_crawler(cls, crawler):
            # 先会检测是否存在这个方法,存在就调用,不存在的话就会直接实例化对象
            print('正在执行from_crawler方法')
            return cls()
    
        def open_spider(self, spider):
            # 爬虫开启时会自动调用的方法
            print('爬虫{}开启'.format(spider.name))
    
        def close_spider(self, spider):
            # 爬虫结束时会自动调用的方法
            print('爬虫{}关闭'.format(spider.name))
    

    process_item(self, item, spider)

    item: 爬虫文件中 yield 的对象

    spider: 当前运行的爬虫

    def process_item(self, item, spider):
        d = dict(item)
        # 将数据写入文件
        self.f.write(','.join(d.values()) + '
    ')
        print('{}正在执行中....'.format(spider.name))
        return item
    

    为这么要 return item ?

    因为如果 pipelines.py 中有多个类对爬取的数据进行持久化,,那么就会涉及到 item 对象的传递, 这里的return item 就是将 item 对象传递给下一个 pipeline 类中的 process_item 方法,进行数据的持久化操作。

    如果不写 return item 或者 return 空 ,传递不会被打断,只不过下一级的 process_item 接收的 item 对象是

    None , 如果你想终止传递的话 需要在 process_item 抛出一个异常

    from_crawler(cls, crawl)

    当爬虫开始的时候,先会检测 Spider1Pipeline 类中是否有 from_crawler 方法,通过 hasattr(反射)检测,如

    果有那么就会 Spider1Pipeline.Spider1Pipeline()这个方法,这个方法会返回当前类的一个实例化对象,那么

    crawler 有什么用呢?他可以通过 crawler.settings.get ( "名字" ) 取到配置文件中的内容,比如配置文件中取到连

    接的数据库的配置信息 然后封装到当前对象中, 在其他方法中应用。

    注意这里的settings是一个对象,是 settings.py 这个文件实例化形成的对象,在爬虫启动时,存在放在当前内存中

    # settings.py 
    
    
    @classmethod
    def from_crawler(cls, crawler):
        # 先会检测是否存在这个方法,存在就调用,不存在的话就会直接实例化对象
        print('正在执行from_crawler方法')
        
        # cls().host = crawler.settings.get ( "HOST" )
        # cls().port = crawler.settings.get ( "PORT" )
        
        return cls()
    

    如果在当前类中没有from_crawler(cls, crawler)方法,那么就会直接实例化对象

    open_spider(self, spider)

    当 pipeline 类实例化一个对象之后就会自动调用该方法,而且这个方法是在爬虫开启之前执行的方法,如果你想针对你的爬虫做一下开始爬取之前的自定义的话,可以写在这个方法中,比如连接数据库,或者打开文件,因为在整个爬虫的过程中这个方法只会执行一次,但是千万不要忘记在爬虫执行完毕后关闭文件或者断开数据库的连接。

    如果你在process_item这个方法中连接数据库或者打开文件的话会造成重复连接数据库或打开文件

    def open_spider(self, spider):
        print('爬虫{}开启'.format(spider.name))
        # 打开文件
        self.f = open('douban.csv', 'w', encoding='utf-8')
    

    close_spider(self, spider)

    当爬虫运行结束的时候,会自动调用该函数一般用来关闭文件或者断开与数据库的连接

    def close_spider(self, spider):
        # 关闭文件
        self.f.close()
        print('爬虫{}关闭'.format(spider.name))
    

    保存到数据库

    class Spider2Pipeline:
        def __init__(self, db, cur):
            print('实例化一个对象')
            # 封装数据库, 游标
            self.db = db
            self.cur = cur
    
        @classmethod
        def from_crawler(cls, crawler):
            print('正在执行from_crawler方法')
            # 从配置文件中获取 HOST, USER, PASSWORD, DATABASE
            host = crawler.settings.get('HOST')
            user = crawler.settings.get('USER')
            password = crawler.settings.get('PASSWORD')
            database = crawler.settings.get('DATABASE')
        	# 利用pymysql连接mysql数据库
            db = pymysql.connect(host=host, user=user, password=password,
                                 database=database, )
            # 创建一个游标
            cur = db.cursor()
            # 把数据库和游标封装到当前对象中
            obj = cls(db, cur)
            return obj
    
        def open_spider(self, spider):
            print('爬虫{}开启'.format(spider.name))
            # 连接数据库
    
        def process_item(self, item, spider):
            d = dict(item)
     		# 将数据通过游标cur写入连接的数据库中
            self.cur.execute('insert into movies_info (title,comment_num,score) values(%s,%s,%s)', list(d.values()))
            print('{}正在执行中....'.format(spider.name))
            return item
    
        def close_spider(self, spider):
            # 提交数据到数据库
            self.db.commit()
            # 关闭数据库连接
            self.db.close()
            print('爬虫{}关闭'.format(spider.name))
    

    ImagePipeline

    scrapy 专门封装了一个下载图片的 ImagesPipeline , 使用 ImagesPipeline for图像文件的优点是,您可以配置一些额外的功能,例如生成缩略图和根据图像大小过滤图像。

    有时候可能会采集图片资源,Scrapy 帮我们实现了图片管道文件,很方便保存图片

    # spider爬虫文件
    
    class VmgirlsSpider(scrapy.Spider):
      name = 'vmgirls'
      allowed_domains = ['vmgirls.com']
      start_urls = ['https://www.vmgirls.com/12985.html']
    
      def parse(self, response: scrapy.Selector):
        item = PictureItem()
        item['title'] = response.css('h1::text').extract_first()
        item['img_s'] = response.css('.post-content img::attr(data-src)').extract()
        print(item['img_s'])
        yield item
    
    • get_media_requests() 是用来发送请求的,需要传入图片的网址。
    • file_path() 是用来指定保存的文件的名字。
    • item_completed () 当请求完成后进行的操作

    除了编写图片管道文件,还要在配置环境中激活,以及指定图片的存储位置

    # settings.py
    
    # 图片存储的路径
    IMAGES_STORE = './images'
    
    class DownloadPicturePipeline(ImagesPipeline):
        
      def get_media_requests(self, item, info):
        for image_url in item['img_src']:
            # 获取图片的url,并发请求
            yield scrapy.Request(image_url, meta={'filename': item['title']})
        
      def file_path(self, request, response=None, info=None):
        # 重命名,若不重写这函数,图片名为哈希
        # 获取图片的文件名
        filename = request.meta.get('filename')
        # 获取图片url中的文件名字
        image_guid = request.url.split('/')[-1]
        # request 对象请求到的图片的保存名字
        return os.path.join(filename, image_guid)
    
      def item_completed(self, results, item, info):
            # 最小的操作单位
            # 图片下载完成之后做的一些操作
            # true 下载成功
    
            # results 结构是什么东西
            # l = [
            #     (True, {'url': 'http://pic1.win4000.com/mobile/2020-05-25/5ecb68ff747b7_130_170.jpg',
            #             'path': '清纯金发美女高清手机壁纸\5ecb68ff747b7_130_170.jpg',
            #             'checksum': 'f68c281392671dfb3a949811caad6435'}),
            #     (True, {'url': 'http://pic1.win4000.com/mobile/2020-05-25/5ecb69003d850_130_170.jpg',
            #             'path': '清纯金发美女高清手机壁纸\5ecb69003d850_130_170.jpg',
            #             'checksum': 'd29a0f4d30f689d0742802e50d2b26cf'}),
            #     ]
        # 保存图片下载的路径
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem("Item contains no images")
        item['image_paths'] = image_paths
        return item
    

    注册

    pipeline ( 管道 ) ,首先你要想应用你写的管道 , 在运行爬虫之前要在 settings.py 中注册

    ITEM_PIPELINES = {
       'spider1.pipelines.Spider1Pipeline': 299,  # 项目名称.pipelines.类名
       'spider1.pipelines.Spider2Pipeline': 300,
    }
    
    # 可以注册多个管道, 数值小的优先级高,先执行,一般放在上面
    

    执行流程

    1. 检测 pipeline 类中是否存在 from_crawler (cls, crawler) 方法
      • 有就执行
      • 否则就 _ _ init _ _ (self) 实例化对象
    2. 调用 open_spider(self, spider) 方法
    3. 执行爬虫文件 遇到 yield item 反复执行 process_item(self, item, spider)
    4. 当爬虫执行完毕的话 调用 close_spider(self, spider) 方法

    如果存在多个 pipeline 类,会按照注册的优先级在第一个 pipeline 执行完 from_crawler (cls, crawler) 方法后

    依次执行其他 pipeline 类中的 from_crawler (cls, crawler) 方法,然后依次执行 open_spider(self, spider)

    再依次执行 process_item(self, item, spider)。但是 close_spider(self, spider) 方法则是优先级低的先执行。

    注意:pipeline默认是全局的,无论是那个爬虫文件都会走所有的 pipeline ,如果你想对某个爬虫文件自定义

    的话,可以通过 spider.name == 'xxx' , 做选择

    数据去重

    当我们的 item 对象中可能存在重复时,这个时候我们就需要对 item 对象进行检测是否存在重复。

    这里我的思路是:在管道中维护一个列表,每次接收到item对象时先检测是否已经存在我们维护的列表中

    # 爬虫程序重新启动一次,过滤就会再来一次
    # 爬虫结束之前把去重结果保存一次(写到一个临时文件),下次启动时,把去重结果加载一遍
    # 不需要保存过的结果,重新再保存一次
    
    # 维护这个列表
    filter_set = []
    
    
    class Qd04EnglishPipeline(object):
        def __init__(self):
            self.f = open('eng.csv', mode='w', encoding='utf-8', newline="")
            self.csv_write = csv.writer(self.f)
    
        # 结果去重
         def process_item(self, item, spider):
             """需要保存的数据与已经保存的数据有没有重复的
             读取文件,然后再判断文件里面的内容
             """
             d = dict(item)
             h = hashlib.md5(json.dumps(d).encode()).hexdigest()
             if h not in filter_set:
                 # 如果这个结果是第一次出现,就先保存,并且记录下来
                 filter_set.append(h)
                 self.csv_write.writerow(d.values())
                 return item
    

    这里简单提一下,最好不要将 item 对象直接放到列表,如果每个 item 对象中封装很多字段或者有很多 item 对象,那么这个列表中就会有很多内容,当数据内容非常多是,判断就会占用很多的内存空间,这样每次都要查询的话,会很浪费时间,降低效率。

    所以我们采用摘要算法,对 item 对象进行摘要,对象摘要的结果是一个字符串,而且相同的对象摘要出来的字符串是一样的。所以我们可以摘要之后才放到列表中。

    摘要算法就是通过摘要函数对任意长度的数据 data 计算出固定长度的摘要,目的是为了发现原始数据是否被人篡

    改过。

    摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算哈希很容易,但通过

    digest 反推 data 却非常困难。而且,对原始数据做一个 bit 的修改,都会导致计算出的摘要完全不同。

    如果你想详细了解摘要算法可以自行百度

    import hashlib
    import json
    
    md5 = hashlib.md5()
    d = {'name':'asd'}
    d_str = json.dumps(d)
    md5.update(d_str.encode())
    
    h = md5.hexdigest()
    print(h) # h就是对象摘要出来的结果
    
  • 相关阅读:
    【BZOJ】2100: [Usaco2010 Dec]Apple Delivery(spfa+优化)
    【BZOJ】2101: [Usaco2010 Dec]Treasure Chest 藏宝箱(dp)
    【BZOJ】3404: [Usaco2009 Open]Cow Digit Game又见数字游戏(博弈论)
    【BZOJ】3403: [Usaco2009 Open]Cow Line 直线上的牛(模拟)
    【BZOJ】3402: [Usaco2009 Open]Hide and Seek 捉迷藏(spfa)
    【BZOJ】3400: [Usaco2009 Mar]Cow Frisbee Team 奶牛沙盘队(dp)
    【BZOJ】3399: [Usaco2009 Mar]Sand Castle城堡(贪心)
    【BZOJ】3392: [Usaco2005 Feb]Part Acquisition 交易(spfa)
    【BZOJ】2020: [Usaco2010 Jan]Buying Feed, II (dp)
    【BZOJ】2015: [Usaco2010 Feb]Chocolate Giving(spfa)
  • 原文地址:https://www.cnblogs.com/kai-/p/14853541.html
Copyright © 2011-2022 走看看