爬虫存储海量数据太麻烦? 换个姿势试一试!

爬虫存储海量数据太麻烦? 换个姿势试一试!

爬虫存储海量数据太麻烦? 换个姿势试一试!

这两个星期刚开始我的实习生涯,到今天才算完全适应了,进公司里写代码和在家里自己吭哧吭哧写的感觉完全不一样,不仅节奏更紧凑了,而且对于规范和质量也有了更多的要求

存储数据的好伙伴:数据库

刚开始我们抓取到的数据的数量级非常小
所以为了省事,会将数据存储到文本文件
想要的时候再通过读文件的方式来获取
面对几十/几百条数据的时候还能对付勉强
但一但到了上万条数据的时候,就显得十分力不从心了!
PS: 我公司的每天都要下载几百万的数据...
随便找了一张表数据,你们感受一下:

这个时候我们就要用到数据库
可能有小伙伴不太明白什么是数据库

通俗一点的说,数据库就功能更加强大的excel表格

里面的数据大概是长这样的:
图为我抓的豆瓣电影Top250
不知道为啥上次发出来被知乎删了???
想看怎么实现的可以去我的blog看:
ehcoblog.ml/post/89/

数据库的选择

现阶段比较流行的数据库有:

  • Mysql 个人/公司用的都很多 免费
  • MongoDB 一款最近比较流行的NoSql数据库
  • Redis 由于各种原因一般拿来做中间件/缓存
  • Sqlite 超轻量级的数据库 缺点是不能分布式
  • .....

总之各种数据库都有适合自己的地方,
爬虫抓取数据的话一般存在Mysql/Mongo里都是不错的选择

如何学习数据库?

就像我们学习Python的语法来和Python的解释器进行交互一样
和数据库的交互也是需要特殊的语法:SQL语句

SQL指结构化查询语言,它使得我们有能力访问数据库,SQL 是一种 ANSI 的标准计算机语言

想要愉快的是使用数据库,学习SQL是必不可少的一环
我们可以去W3school/菜鸟教程找到一份不错的资料:

这样真的足够好么?

假设我们需要从豆瓣数据里检索:排名在前50且评分大于8的电影

我们就需要写这样的SQL语句:

SELECT * FROM `EhcoTestDb`.`DoubanTop250` WHERE `ranking` < '50' AND `score` > '8' LIMIT 0, 1000
 

看到这么一大串莫名其妙的sql语句,是不是莫名觉得心累呢?
是不是只要改一个条件(比如排名在100名之外的)
我们得重新再写一遍这样「恶心」的代码呢?

不知道你们是不是能忍受,反正我在写了两个月的Django之后,
习惯了使用里面的ORM,就再也忍受不了「空手撸SQL」了

ORM:对象关系映射(英语:Object Relational Mapping,简称ORM),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。在这里的应用是:让我们像操作对象一样操作数据库

举个例子:
假设我们像查询排名前十的电影我们可以这样写:

# 导入对数据库操作的封装和配置文件
from stroe import DbToMysql
import config

# 初始化组件
store = DbToMysql(config.EHCO_DB)

# 数据查询
res = store.find_by_sort('DoubanTop250', 'ranking', 10, 'ASC')

for data in res:
    print(data['name'])

是不是很方便,也很符合人类的思考方式呢?
结果是这样的:

Python 操纵数据库 :pymysql

各种语言都有帮助我们操作数据库的包
Python中我一直比较喜欢用:
pymysql:github.com/PyMySQL/PyMy

这个包用起来还是十分简单的
具体的用法是这样:

import pymysql.cursors

# 连接到数据库
connection = pymysql.connect(host='localhost',
                             user='user',
                             password='passwd',
                             db='db',
                             charset='utf8mb4',
                             cursorclass=pymysql.cursors.DictCursor)
try:
    with connection.cursor() as cursor:
        # 增加一条记录
        sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)"
        cursor.execute(sql, ('webmaster@python.org', 'very-secret'))

    # 手动保存刚才的添加操作
    connection.commit()

    with connection.cursor() as cursor:
        # 读取一条记录
        sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s"
        cursor.execute(sql, ('webmaster@python.org',))
        result = cursor.fetchone()
        print(result)
finally:
    connection.close()

虽然用起来已经十分简洁了
但和上文中的方式还是有比较大的差异
这是因为还需要我们手写sql语句。

这个时候我们可以将逻辑再封装一层:

import pymysql.cursors
import config

class DbToMysql():
    '''封装对数据库的操作'''

    def __init__(self, configs):
        self.con = pymysql.connect(
            host=configs['host'],
            user=configs['user'],
            password=configs['password'],
            db=configs['db'],
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )

    def close(self):
        '''关闭数据库链接'''
        self.con.close()

    def save_one_data(self, table, data,):
        '''
        将一条记录保存到数据库
        Args:
            table: 表名字 str
            data:  记录 dict
        return:
            成功: dict 保存的记录
            失败: -1
        每条记录都以一个字典的形式传进来
        '''
        key_map = {}
        if len(data) == 0:
            return -1
        fields = ''
        values = ''
        datas = {}
        for k, v in data.items():
            # 防止sql注入
            datas.update({k: pymysql.escape_string(v)})
        for d in datas:
            fields += "`{}`,".format(str(d))
            values += "'{}',".format(str(data[d]))
        if len(fields) <= 0 or len(values) <= 0:
            return -1
        # 生成sql语句
        sql = "insert ignore into {}({}) values({})".format(
            table, fields[:-1], values[:-1])

        try:
            with self.con.cursor() as cursor:
                # 执行语句
                cursor.execute(sql)
                self.con.commit()
                res = cursor.fetchone()
                return res
        except:
            print('数据库保存错误')
            return -1
        finally:
            self.close()
    
    def find_by_field(self, table, field, field_value):
        '''
        从数据库里查询指定条件的记录
        Args:
            table: 表名字 str
            field: 字段名
            field_value: 字段值
        return:
            成功: [dict] 保存的记录
            失败: -1
        '''
        try:
            with self.con.cursor() as cursor:
                sql = "select * from {} where {} = '{}'".format(
                    table, field, field_value)
                cursor.execute(sql)
                res = cursor.fetchall()
                return res
        except:
            print('数据查询存错误')
            return -1
        finally:
            self.close()

由于文章长度的限制

我只把封装查询/存储的逻辑写了上来
实际上大家可以针对自己的需求来进行各种各样的魔改啦

这次主要介绍了数据库的基本使用
希望能帮助到大家hhh

每天的学习记录都会 同步更新到:
微信公众号: findyourownway

知乎专栏:zhuanlan.zhihu.com/Ehco
blog : www.ehcoblog.ml
Github: github.com/Ehco1996/Pyt

编辑于 2017-11-10

文章被以下专栏收录