Python3.X 爬虫实战(缓存与持久化)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yanbober/article/details/73368766

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

1 背景

不知不觉关于 Python 3.X 爬虫系列已经介绍了如下系列:

《正则表达式基础》
《Python3.X 爬虫实战(先爬起来嗨)》
《Python3.X 爬虫实战(静态下载器与解析器)》
《Python3.X 爬虫实战(并发爬取)》

可以看到,关于 Python 静态页面爬虫的相关核心基础其实已经介绍的差不多了,关于爬虫的 URL 管理器、下载器、解析器、输出器、并发爬取思想我们已经基本介绍了,但是到这里我们要学会思考一个棘手的问题——–缓存与持久化。简单说就是 Cache 或者 Persistence 了,这玩意和爬虫有啥关系呢?想象一下如果我们需要对同一个页面进行多次解析,我们前面的代码都会重新发起真实网络请求,这是不合理的,因为短期之内这个页面是不可能有更新的,我们重复拉取是没有意义的;其次我们很多时候爬虫的输出器其实就是需要把爬取的数据依据需求多元化的持久化下来,所以我们有必要先掌握常见的爬虫相关缓存及持久化。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

2 Python3.X 缓存与持久化

这里之所以把爬虫的常见缓存与持久化放在一起讨论是因为爬虫这里的缓存与持久化相对来说是比较相似的,缓存算是持久化的一个子集,但是缓存又有自己的过期策略和缓存级别,而持久化基本无过期策略之说。缓存与持久化并不是 Python 爬虫特有的,其他语言都有涉及,所以我们下面既然说要把缓存和持久化放在一起说是建立在持久化缓存的基础上,因为多级缓存策略的内存缓存等不在我们这篇的讨论范畴,大家一定要自己明确区分,简单理解就是我们本篇倾向于持久化,只是谈到的一些持久化方式在缓存中可用而已。

2-1 常见本地磁盘文件型

Python3.X 常见本地磁盘文件型数据持久化主要包括普通文件、DBM文件、Pickle序列化对象存储、shelve键值序列化对象存储,对于我们编写爬虫程序来说缓存的设计或者持久化方式我们可以自己依据自己的需求进行合适的评估选择,下面给出常见的本地磁盘文件型持久化样例:

[本实例 demo_local_disk_file_persistence.py 源码点我获取]

import dbm
import pickle
import shelve
'''
Python3 常用本地磁盘文件型持久化演示
'''

class NormalFilePersistence(object):
    '''
    普通文件持久化或者缓存持久化
    '''
    def save(self, data):
        with open('NormalFilePersistence.txt', 'w') as open_file:
            open_file.write(data)

    def load(self):
        with open('NormalFilePersistence.txt', 'r') as open_file:
            return open_file.read()


class DBMPersistence(object):
    '''
    DBM字符串键值对持久化或者缓存持久化
    '''
    def save(self, key, value):
        try:
            dbm_file = dbm.open('DBMPersistence', 'c')
            dbm_file[key] = str(value)
        finally:
            dbm_file.close()

    def load(self, key):
        try:
            dbm_file = dbm.open('DBMPersistence', 'r')
            if key in dbm_file:
                result = dbm_file[key]
            else:
                result = None
        finally:
            dbm_file.close()
        return result


class PicklePersistence(object):
    '''
     Pickle把复杂对象序列化到文件持久化或者缓存持久化
    '''
    def save(self, obj):
        with open('PicklePersistence', 'wb') as pickle_file:
            pickle.dump(obj, pickle_file)

    def load(self):
        with open('PicklePersistence', 'rb') as pickle_file:
            return pickle.load(pickle_file)


class ShelvePersistence(object):
    '''
    Shelve为DBM和Pickle的结合,以键值对的方式把复杂对象序列化到文件持久化或者缓存持久化
    '''
    def save(self, key, obj):
        try:
            shelve_file = shelve.open('ShelvePersistence')
            shelve_file[key] = obj
        finally:
            shelve_file.close()

    def load(self, key):
        try:
            shelve_file = shelve.open('ShelvePersistence')
            if key in shelve_file:
                result = shelve_file[key]
            else:
                result = None
        finally:
            shelve_file.close()
        return result


if __name__ == '__main__':
    t_normal = NormalFilePersistence()
    t_normal.save('Test NormalFilePersistence')
    print('NormalFilePersistence load: ' + t_normal.load())

    t_dbm = DBMPersistence()
    t_dbm.save('user', 'GJRS')
    t_dbm.save('age', 27)
    print('DBMPersistence load: ' + str(t_dbm.load('user')))
    print('DBMPersistence load: ' + str(t_dbm.load('address')))

    t_pickle = PicklePersistence()
    obj = {'name': 'GJRS', 'age': 27, 'skills':['Android', 'C', 'Python', 'Web']}
    t_pickle.save(obj)
    print('PicklePersistence load: ' + str(t_pickle.load()))

    t_shelve = ShelvePersistence()
    obj1 = {'name': 'WL', 'age': 27, 'skills': ['Test', 'AutoTest']}
    obj2 = {'name': 'GJRS', 'age': 27, 'skills': ['Android', 'C', 'Python', 'Web']}
    t_shelve.save('obj1', obj1)
    t_shelve.save('obj2', obj2)
    print('ShelvePersistence load: ' + str(t_shelve.load('obj1')))
    print('ShelvePersistence load: ' + str(t_shelve.load('objn')))

关于这些方式的持久化爬虫实例我们在该系列文章的前几篇都有介绍,这里不再给出单独的爬虫实例,感兴趣可以自己摸索,没啥复杂的,主要是策略的设计,譬如 LRU 算法等,真正持久化其实是非常简单的,但也是非常重要的。

2-2 常见数据库方式

上面介绍了常见本地磁盘文件型的持久化,我们学习完一定会有疑惑,如果我的数据量巨大巨复杂怎么办,如果还是使用本地磁盘文件型的持久化那得多蛋疼啊,是的,所以我们现在来讨论关于 Python 爬虫的另一类缓存持久化方式 —— 数据库持久化。

2-2-1 Sqlite 持久化

首先我们要看的就是 Python3.X 中 SQLite3 的使用(单机型),从 Python2.5 开始的版本就默认自带了该模块,所以我们不用重新安装。下面给出 Python3.X 中 SQLite3 的使用例子:

[本实例 demo_sqlite3_persistence.py 源码点我获取]

'''
Python3 sqlite3数据库持久化演示
'''
import sqlite3

class Sqlite3Persistence(object):
    def __init__(self):
        self.db = None

    def connect(self):
        try:
            self.db = sqlite3.connect("Sqlite3Persistence.db")
            sql_create_table = """CREATE TABLE IF NOT EXISTS `DemoTable` (
                                    `id` INTEGER PRIMARY KEY AUTOINCREMENT,
                                    `name` CHAR(512) NOT NULL,
                                    `content` TEXT NOT NULL)"""
            self.db.execute(sql_create_table)
        except Exception as e:
            print("sqlite3 connect failed." + str(e))

    def close(self):
        try:
            if self.db is not None:
                self.db.close()
        except BaseException as e:
            print("sqlite3 close failed."+str(e))

    def insert_table_dict(self, dict_data=None):
        if dict_data is None:
            return False
        try:
            cols = ', '.join(dict_data.keys())
            values = '"," '.join(dict_data.values())
            sql_insert = "INSERT INTO `DemoTable`(%s) VALUES (%s)" % (cols, '"'+values+'"')
            self.db.execute(sql_insert)
            self.db.commit()
        except BaseException as e:
            self.db.rollback()
            print("sqlite3 insert error." + str(e))
        return True

    def get_dict_by_name(self, name=None):
        if name is None:
            sql_select_table = "SELECT * FROM `DemoTable`"
        else:
            sql_select_table = "SELECT * FROM `DemoTable` WHERE name==%s" % ('"'+name+'"')
        cursor = self.db.execute(sql_select_table)
        ret_list = list()
        for row in cursor:
            ret_list.append({'id': row[0], 'name': row[1], 'content': row[2]})
        return ret_list


if __name__ == '__main__':
    t_sqlite3 = Sqlite3Persistence()
    t_sqlite3.connect()
    t_sqlite3.insert_table_dict({'name': 'Test1', 'content': 'XXXXXXXXXXXXX'})
    t_sqlite3.insert_table_dict({'name': 'Test2', 'content': 'vvvvvvvvvvvv'})
    t_sqlite3.insert_table_dict({'name': 'Test3', 'content': 'qqqqqqqqqqqq'})
    t_sqlite3.insert_table_dict({'name': 'Test4', 'content': 'wwwwwwwwwwwww'})
    print('Sqlite3Persistence get Test2: ' + str(t_sqlite3.get_dict_by_name('Test2')))
    print('Sqlite3Persistence get All: ' + str(t_sqlite3.get_dict_by_name()))
    t_sqlite3.close()

当然了,至于你的爬虫中是否选择 Sqlite3 进行持久化就要看你自己的需求了,毕竟关系型数据库有其自己的优劣;如果你需要再更加多的了解 Sqlite,那可以看下以前我写的《Sqlite全面学习(一)》《Sqlite全面学习(二)》《Sqlite全面学习(三)》这几篇文章,你会发现,其实新学任何一门语言需要关注的是语言本身的语法和性能实现等,而无需关注公共的东西,因为公共的东西都是相同的,所以这也是为啥我们学应用型技术越学越快的原因。

2-2-2 MySQL 持久化

不扯了,介绍完单极型高效迷你关系型数据库 Sqlite 的持久化后我们再来看看大型服务器关系型数据库 MySQL,在 Python 3.X 中使用 MySQL 需要依赖 pymysql 模块,当然咯,你还得有台安装 MySQL 的数据库服务器和可视化管理工具,怎么安装就不多说了,如下给出一个使用案例(我的 MySQL用的是本地安装的):

[本实例 demo_mysql_persistence.py 源码点我获取]

'''
Python3 MySQL数据库持久化演示
'''
import pymysql


class MySQLPersistence(object):
    def __init__(self):
        self.db = None
        self.cursor = None

    def connect(self):
        try:
            self.db = pymysql.connect("localhost", "yanbober", "TQJJtaJWNbGAMU44", "database_yan_php")
            self.db.set_charset('utf8')
            self.cursor = self.db.cursor()

            sql_create_table = """CREATE TABLE IF NOT EXISTS `StudentTable` (
                                    `id` int(11) NOT NULL AUTO_INCREMENT,
                                    `name` varchar(512) COLLATE utf8_bin NOT NULL,
                                    `content` TEXT COLLATE utf8_bin NOT NULL,
                                    PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
                                    AUTO_INCREMENT=1"""
            self.cursor.execute(sql_create_table)
        except Exception as e:
            print("mysql connect failed." + str(e))

    def close(self):
        try:
            if self.db is not None:
                self.db.close()
            if self.cursor is not None:
                self.cursor.close()
        except BaseException as e:
            print("mysql close failed."+str(e))

    def insert_table_dict(self, dict_data=None):
        if self.db is None or self.cursor is None:
            print('Please ensure you have connected to mysql server!')
            return False
        if dict_data is None:
            return False
        try:
            cols = ', '.join(dict_data.keys())
            values = '"," '.join(dict_data.values())
            sql_insert = "INSERT INTO `StudentTable`(%s) VALUES (%s)" % (cols, '"'+values+'"')
            self.cursor.execute(sql_insert)
            self.db.commit()
        except BaseException as e:
            self.db.rollback()
            print("mysql insert error." + str(e))
        return True

    def get_dict_by_name(self, name=None):
        if self.db is None or self.cursor is None:
            print('Please ensure you have connected to mysql server!')
            return None
        if name is None:
            sql_select_table = "SELECT * FROM `StudentTable`"
        else:
            sql_select_table = "SELECT * FROM `StudentTable` WHERE name=%s" % ('"'+name+'"')
        self.cursor.execute(sql_select_table)
        ret_list = list()
        for item in self.cursor.fetchall():
            ret_list.append({'id': item[0], 'name': item[1], 'content': item[2]})
        return ret_list

if __name__ == '__main__':
    t_mysql = MySQLPersistence()
    t_mysql.connect()
    t_mysql.insert_table_dict({'name': 'Test1', 'content': 'XXXXXXXXXXXXX'})
    t_mysql.insert_table_dict({'name': 'Test2', 'content': 'vvvvvvvvvvvv'})
    t_mysql.insert_table_dict({'name': 'Test3', 'content': 'qqqqqqqqqqqq'})
    t_mysql.insert_table_dict({'name': 'Test4', 'content': 'wwwwwwwwwwwww'})
    print('MySQLPersistence get Test2: ' + str(t_mysql.get_dict_by_name('Test2')))
    print('MySQLPersistence get All: ' + str(t_mysql.get_dict_by_name()))
    t_mysql.close()

可以看见,MySQL 关系型数据库使用起来和 Sqlite 很相似,其实是这样的,他们本来都来自 SQL 家族,只是各自有一些细微的区别而已;通过上面代码我们就将我们的数据持久化到了 localhost 这台数据库服务器上面,使用数据时直接从这台服务器获取即可,很是方便。

2-2-3 MongoDB 持久化

上面我们主要介绍了 python3.X 中关系型数据库 mysql、sqlite 的使用,下面我们继续介绍 Python3.X 爬虫中常用的非关系型数据库,先要介绍的是 MongoDB,它是一个基于分布式文件存储的数据库,是为 WEB 应用提供可扩展的高性能数据存储而诞生的,是一个介于关系数据库和非关系数据库之间的东西,也是非关系数据库中功能最丰富、最像关系数据库的数据库。关于 MongoDB 数据库和可视化管理工具的安装配置这里就不介绍了,具体用法实例如下(我的 MongoDB 是本地的,运行下面代码前请下保证已经启动 MongoDB,譬如mongod.exe --dbpath D:\developer\MongoDB\Server\data\db):

[本实例 demo_mongodb_persistence.py 源码点我获取]

import pymongo
'''
Python3 MongoDB数据库持久化演示
'''

class MongoDBPersistence(object):
    def __init__(self):
        self.conn = None
        self.database = None

    def connect(self, database):
        try:
            self.conn = pymongo.MongoClient('mongodb://localhost:27017/')
            self.database = self.conn[database]
        except Exception as e:
            print("MongoDB connect failed." + str(e))

    def close(self):
        try:
            if self.conn is not None:
                self.conn.close()
        except BaseException as e:
            print("MongoDB close failed."+str(e))

    def insert_table_dict(self, dict_data=None):
        if self.conn is None or self.database is None:
            print('Please ensure you have connected to MongoDB server!')
            return False
        if dict_data is None:
            return False
        try:
            collection = self.database['DemoTable']
            collection.save(dict_data)
        except BaseException as e:
            print("MongoDB insert error." + str(e))
        return True

    def get_dict_by_name(self, name=None):
        if self.conn is None or self.database is None:
            print('Please ensure you have connected to MongoDB server!')
            return None
        collection = self.database['DemoTable']
        if name is None:
            documents = collection.find()
        else:
            documents = collection.find({"name": name})
        document_list = list()
        for document in documents:
            document_list.append(document)
        return document_list


if __name__ == '__main__':
    t_mysql = MongoDBPersistence()
    t_mysql.connect("DemoDatabase")
    t_mysql.insert_table_dict({'name': 'Test1', 'content': 'XXXXXXXXXXXXX'})
    t_mysql.insert_table_dict({'name': 'Test2', 'content': 'vvvvvvvvvvvv'})
    t_mysql.insert_table_dict({'name': 'Test3', 'content': 'qqqqqqqqqqqq'})
    t_mysql.insert_table_dict({'name': 'Test4', 'content': 'wwwwwwwwwwwww'})
    print('MongoDBPersistence get Test2: ' + str(t_mysql.get_dict_by_name('Test2')))
    print('MongoDBPersistence get All: ' + str(t_mysql.get_dict_by_name()))
    t_mysql.close()

就这样咯,关于 Python 爬虫持久化的 MongoDB 存储基本套路就是这样的,其他类似 SQL 的增删查找等策略语法一样需要我们自己去积累,这里不可能全部说明,不懂的话一样类似 SQL 去先多看看用用 MongoDB,然后对于各种语言下的使用基本区别就是 Driver API 了,原理不变。

2-2-4 其他持久化

之所以叫其他,其实真的有太多的持久化方式,除过上面我们介绍的磁盘文件、常见数据库持久化以外我们其实还有其他的选择,譬如我们对于 NoSQL 的选择除过上面介绍的文档数据存储 MongoDB 以外还可以选择列数据存储 (HBase)、键值对存储(Redis)、图形数据库(Neo4j)等,这就取决于我们爬虫的数据需求了;其次我们还可以选择一些 ORM 框架来进行持久化,譬如 Django 或者 SQLAlchemy,这就取决于我们自己咯。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 爬虫持久化选型及总结

Tips:缓存持久化前我们可以对缓存比较大的文本数据先进行压缩等处理再存储,这样可以节约存储。

通过上面常见的 Python3.X 各种持久化方式介绍我们至少应该知道在爬虫需要缓存持久化时我们可以有很多种选择,至于如上所有持久化如何选型其实是依赖于我们自己爬虫需求来决定的,不同的需求可能需要用不同的持久化类型,不过还是有一些参考策略来指导我们进行爬虫持久化选型的,即我们需要认清上面那些持久化各自的优劣点。

对于本地文件型持久化其实优劣点是很明显的,譬如上面介绍的有些支持序列化存储,有些支持同一文件下多 key-value 对存储,但是数据规模一旦庞大,本地文件存储不仅效率低下,还容易出现数据故障,备份十分麻烦,总之只适用于轻量级本地单一数据格式存储,也就是比较适合我们自己编写的一些小爬虫程序。

对于 Sqlite 数据库存储来说基本上只能认为是本地文件型存储的一个关系型升级,有效的改善了本地磁盘文件存储关系型数据的诟病,但是因为其为单机型迷你数据库,在数据存储量级和数据故障方面也是有瓶颈限制的,至于在本地文件型存储和 Sqlite 的选型时我觉得重点要衡量爬虫有用数据的关系,日后数据间关联紧密,需要互相依赖查找的情况使用 Sqlite 似乎更胜一筹。

对于 MySQL 等关系型数据库存储和 MongoDB 等非关系型数据库存储的优劣比较其实在网上已经有很多文章谈论多年了,不过在爬虫时到底如何选择其实还是取决于我们自己的需求定位,对于关系型数据库存储其具备高结构化数据、结构化查询语言、数据和关系都存储在单独的表中,而对于非关系型数据库存储其具备高可用、高性能、高伸缩性、没有声明性查询语言、使用键值对、列、文档、图形等存储、存储数据不可预知及无结构化可言。我们很多时候的爬虫需求都是爬取某一垂直需求下的海量数据来进行建模数据分析的,对于这种情况其实更加适合使用 MongoDB 来进行爬虫数据存储;而又有些时候我们爬虫数据可能具备高度的结构化封装和关联,我们想将爬取数据用来提供给其他平台进行 API 接口访问,在这种情况下似乎使用 MySQL 是一个不错的选择。

总之,Python3.X 爬虫缓存与持久化选型是需要依据我们需求来决定的,甚至有些情况下可能会出现多种持久化组合使用的情况,我们需要做到的是掌握和知道爬虫持久化可以有哪些选择,只有这样才能不变应万变。

^-^当然咯,看到这如果发现对您有帮助的话不妨扫描二维码赏点买羽毛球的小钱(现在球也挺贵的),既是一种鼓励也是一种分享,谢谢!
这里写图片描述
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

猜你喜欢

转载自blog.csdn.net/yanbober/article/details/73368766