yokon's blog

对MongoDB的封装

2018.03.17

写爬虫的时候,习惯性会把爬取到的数据存入mongodb。为了方便,也为了每次抓取完数据,不在后面接插入数据的生涩代码。最好的选择是简单的封装mongodb的增删改查功能,如果后面使用mongodb的时候可以直接调用它,一劳永逸。

pymongo

其实pymongo这个库已经为我们很好的封装了对数据库的操作,但是我总是很容易忘记或者搞混他的 api。我想实现如果创建数据库,只需要配置好数据库的namehostport,增加数据:put(data),删除数据:delete(data)

这样的话,每次需要使用mongodb的时候,只需要简单配置,不再需要谷歌他的文档 api,上手即用。

封装

首先我们创建一个MongodbClient类,在向类里面增加对应的功能。

# db.py
from pymongo import MongoClient, ASCENDING
# 我们把配置文件放入 config.py 文件内
from config import NAME, HOST, PORT


class MongodbClient(object):

    def __init__(self, table=TABLE, host=HOST, port=PORT):
        self.table = table
        self.client = MongoClient(host, port)
        self.db = self.client.NAME

    @property
    def get_counts(self):
        """获取表里的数据总数"""
        return self.db[self.table].count()

    def change_table(self, table):
        """切换数据库表"""
        self.table = table

    def set_num(self):
        """给每条数据设置唯一自增num"""
        nums = []
        datas = self.get_all()
        if datas:
            for data in datas:
                nums.append(data['num'])
            return max(nums)
        return 0

    def get_new(self):
        """获取最新一条数据"""
        data = self.get_all()[-1] if self.get_all() else None
        return data

    def get_data(self, num):
        """获取指定num的数据"""
        datas = self.get_all()
        if datas:
            data = [i for i in datas if i['num']==num][0]
            return data
        return None

    def get_all(self):
        """获取全部数据"""
        # 判断表里是否有数据
        if self.get_counts != 0:
            # 先排序
            self.sort()
            datas = [i for i in self.db[self.table].find()]
            return datas
        return None

    def put(self, data):
        """添加数据"""
        num = self.set_num() + 1
        # 判断是否重复
        if self.db[self.table].find_one(data):
            # 重复则删除,重新插入
            self.delete(data)
            data['num'] = num
            self.db[self.table].insert_one(data)
        else:
            data['num'] = num
            self.db[self.table].insert_one(data)

    def delete(self, data):
        """删除数据"""
        self.db[self.table].remove(data)

    def clear(self):
        """清空表"""
        self.client.drop_database(self.table)

    def sort(self):
        """按num键排序"""
        self.db[self.table].find().sort('num', ASCENDING)

常用的功能大致就这么多,如果不够用可以自由扩展。

简述

上面的封装其实主要是对pymongo库的封装,给每个数据插入自增的num键是我在抓取IP代理的时候加的,这样做并且基于num键排序。在IP代理池的爬虫项目中,实际上就是实现了一个栈的数据结构。添加代理进数据库,每次获取取最新添加的代理(get_new()),后进先出。当然我写的很多爬虫都是用的这个封装,方便就好。

在插入数据库的方法上,我选择先对它进行去重操作,其实如果重复可以直接选择不插入,而我是选择删除旧的插入新的。其实这里可以不用去判断整个数据是否重复,如果是某一类数据,比如所有数据不可以出现重复的名字,我们就需要判断名字是否和库中的某条数据重复。这样我们可以这么改:

def put(self, data):
    """添加数据"""
    if self.db[self.table].find_one({'name': data['name']}):
        
        ...

是不是很方便。

翻页数据

如果使用flask结合mongodb数据库使用,如果数据库中有50条数据,而我们只需要每页显示10条。这样我们可以对封装添加分页显示数据的操作:

def get_page(self, page, count):
    """
    分页数据
    :param page: 页数
    :param count: 每页条数
    :return: 每页数据
    """
    # 判断是否有数据
    if self.get_nums != 0:
        # 排序
        self.sort()
        # 如果整除count为0,就直接返回为第一页
        pages = self.get_nums // count + 1 if self.get_nums >= 10 else 1

        paginate = []
        for p in range(1, pages+1):
            if p > 1:
                datas = [i for i in self.db[self.table].find().limit(count).skip((p-1)*count)]
            else:
                datas = [i for i in self.db[self.table].find().limit(count)]
            paginate.append({'page': p, 'data': datas})
        return [j['data'] for j in paginate if j['page'] == page]
    return None

我们使用self.db[self.table].find().limit(10)来保证只显示10条数据,.skip(10)可以帮我们过滤掉前面的10条数据。这样是不是相当于显示第二页的数据了。

这是我写爬虫时对数据库操作进行的简单的封装,显然对于很多简单的项目已经很满足使用了,如果需要更多功能,添加起来也是很简单的。当然有很多可以改进的地方,比如列表生成式可以改用生成器,这样实现更好些。