那些年,我爬过的北科(五)——数据存储之使用MongoDB

介绍

在前面咱们介绍了如何编写爬虫,可是咱们的爬虫并无把数据保存下来,只是简单的显示在控制台中。在本节,咱们将简单学习一下数据库,以及如何在python中操做数据库。python

最后,咱们将修改上一节的爬虫框架,使其支持数据库插入。mongodb

注:若是读者已经了解mongodb,能够直接跳到最后一个部分:修改咱们的爬虫框架数据库

MongoDB数据库介绍

数据库其实也就是数据仓库,用来存储数据的地方。如下是数据库在维基百科中的解释:json

数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户能够对文件中的数据运行新增、截取、更新、删除等操做。数组

没有数据库,咱们可能会把爬取的数据存成一个json文件,插入的时候可能要先把整个json序列化成python的列表,而后再进行增删改查,并且数据操做的效率可能会比较低。有了数据库,数据库会给咱们提供方便的API接口,能够很容易对数据进行增删改查操做,而且高效。bash

MongoDB是一种文档型数据库,它属于非关系型数据库。服务器

MongoDB安装

若是没有安装过MongoDB,须要先对mongodb进行一下安装。并发

下载MongoDB

MongoDB有商业版也有社区版本,咱们下载免费的社区版本就行了。能够在此处www.mongodb.com/download-ce…看到各类操做系统的MongoDB安装包。框架

咱们能够选择一款适合本身的操做系统的进行下载安装。下面咱们分别以Mac和Windows系统举例来进行MongoDB的安装。函数

Mac版本安装与启动

下载安装包

首先咱们下载最新版本(4.0.4版本)的mongodb的包,能够看到下载下来是一个tgz的解压文件。

而后咱们对tgz文件进行解压, 并进入到解压后的目录

tar zvxf mongodb-osx-ssl-x86_64-4.0.4.tgz
cd mongodb-osx-x86_64-4.0.4
复制代码

进入目录后,咱们能够看到有一个bin目录文件,这里面就是mongodb的各类脚本了。

启动MongoDB服务器

而后咱们须要建立数据存放的目录,mongodb数据默认存放的路径是/data/db,若是这个目录不存在的话,须要本身建立。确保数据建立后,经过mongod命令便可启动mongodb服务。

./bin/mongod  # 启动mongodb服务
复制代码

若是不想用/data/db这个路径的话,能够经过--dbpath参数设置想要存放的位置。

mkdir test
./bin/mongod --dbpath ./test  # 数据存放在当前路径的test目录下
复制代码

使用MongoDB客户端

接下来咱们测测能不能连上服务器,这里能够用mongodb自带的客户端:mongo命令。在终端中输入如下命令,尝试链接mongodb服务。

./bin/mongo
复制代码

若是输入命令后,成功看到左下角有个代输入的光标,就说明安装成功了。

Windows安装与启动

下载安装包

首先咱们下载最新版本(4.0.4版本)的mongodb的包,咱们这个选择zip安装包。

下载后进行解压,而后进入解压后的目录,能够看到有一个bin文件夹,这里面就是mongodb的各类脚本。

启动MongoDB服务器

而后咱们须要建立数据存放的目录,mongodb数据默认存放的路径是C:\data\db,若是这个目录不存在的话,须要本身建立。

确保数据建立后,经过mongod.exe命令便可启动mongodb服务。

若是不想用C:\data\db这个路径的话,能够经过--dbpath参数设置想要存放的位置。

使用MongoDB客户端

接下来咱们测测能不能连上服务器,这里能够用mongodb自带的客户端。在终端中运行mongo.exe,尝试链接mongodb服务。

若是运行后,成功看到左下角有个代输入的光标,就说明安装成功了。

MongoDB的一些概念

MongoDB以BSON格式的文档(Documents)形式存储。Databases中包含集合(Collections),集合(Collections)中存储文档(Documents)。接下来咱们简单了解一下这几个概念。

Databases: 数据库

Databases是数据库,咱们通常会把不一样的项目划分红不一样的数据库。

咱们可使用show dbs查看已有的数据库,使用use db_name进入某个数据库。下面是咱们进入test数据库的截图。

Collections: 集合

Collections是集合,一个项目中,也会有不一样格式的数据。咱们通常会将同一种类型的数据放在一个集合里面。好比说咱们开发网站有新闻,可能会建立一个news集合;也须要有用户,再建立一个user集合。

集合的概念就如同关系型数据库里面的表同样。

Documents: 文档

Documents是文档,文档是由field和value对的结构组成,以下结构。

{
   field1: value1,
   field2: value2,
   field3: value3,
   ...
   fieldN: valueN
}
复制代码

其中field名是个字符串,而value值能够是任何BSON数据类型,包括:其余document,数字,和document数组。

在MongoDB中集合不须要建立,直接使用就能够,同理数据库也不须要建立,直接使用use就能够。

下面咱们在test数据库下面的news集合中插入一条数据看看。

这里能够看到咱们的数据以下:

{ 
    "_id" : ObjectId("5c0aa0b51b0eb2e557167a5b"), 
    "title" : "hello world" 
}
复制代码

除了咱们插入的title字段外,还有一个_id字段,这是一个索引字段,做为一个文档的惟一标识。咱们能够经过_id对某一个文档进行查找。

pymongo的使用

咱们这里是一个python的教程,因此主要要学习一下如何在python中操做mongodb。在了解前,先安装一下mongodb的python包:pymongo。

pip install pymongo
复制代码

链接数据库

在数据库操做前,咱们首先要链接数据库。这里链接数据库的代码以下:

import pymongo

client = pymongo.MongoClient(host="localhost", port=27017)
复制代码

上面咱们链接了咱们本地的mongodb数据库,mongodb默认使用的端口是27017。当咱们不使用数据库的时候,记得要把数据库的链接关闭掉。

client.close()  # 关闭数据库
复制代码

接下来,咱们就能够选择咱们须要操做的数据库和集合了。可使用字典或者点的方式拿到数据库和集合的实例。

# 使用字典的方式
db = client['test']
items = db["items"]

# 使用点的方式
db = client.test
items = db.items
复制代码

这里咱们使用test数据库下面的items集合进行示意。

插入数据

接下来,咱们先来插入几条数据到啊items集合。这里咱们先定义一个list_items函数用来列出items集合中全部的数据来。其实就是调用find方法,就能够直接找出全部items下的数据了,返回对象是个迭代器,能够经过for...in...拿到里面全部的元素。

def list_items():
    """ 列出全部数据 """
    for item in items.find():
        print(item)
复制代码

插入数据也很简单,和在mongo命令使用的基本上是同样的。就是一个insert方法,插入一个字典便可。

# 增
print("添加数据")
items.insert({"id": 1, "name": "test1"})
items.insert({"id": 2, "name": "test2"})
items.insert({"id": 3, "name": "test3"})
list_items()
复制代码

mongodb一个集合中的数据能够不彻底同样,好比说可能有的文档有name字段,而有的文档没有name字段。不过咱们最好不要这么作,虽然mongodb容许,由于这样可能会让本身容易混乱。

items.insert({"id": 4, "no_name": "test4"})
list_items()
复制代码

删除数据

在mongodb中删除数据直接使用remove方法就行了,remove的参数就是要删除元素的条件,好比下面是删除id为1的数据。

print("删除id为1")
items.remove({"id": 1})
list_items()
复制代码

不过上面演示的id实际上是假的id,由于它能够不是惟一的。在mongodb中使用的_id字段做为索引,这个索引是自动建立的,它是一个ObjectId类型。咱们能够经过下面的代码操做一个惟一的文档。

更新数据

更新数据的话是用的update方法,update方法接受多个参数,主要有三个。

  • 第一个参数:spec,指定要更新的数据。
  • 第二个参数:document,要修改的数据。
  • 第三个参数:multi,是否要更新多条数据,默认为False,也就是说默认只更新一条数据。

咱们先来看看更新一条数据。

print("修改id为2的name")
items.update({"id": 2}, {
    '$set': {'name': "test2_modified"}
})
list_items()
复制代码

这里第二个参数以下:

{
    '$set': {
        'field1': value1,
        'field2': value2,
        'field3': value3,
        ...
        'fieldN': valueN
    }
}
复制代码

这里能够看到咱们修改了id为2的name字段。

接下来再来试试更新多条数据。

print("修改全部数据,添加一个title字段")
items.update({}, {
    '$set': {'title': "update title"}
}, multi=True)
list_items()
复制代码

查找数据

最后是查找数据了,这里能够经过find方法找到多条数据,find_one方法找到一条数据。对于find_one方法,若是没有找到的话,会返回None。

print("查找id为2")
print(items.find_one({"id": 2}))
复制代码

修改咱们的爬虫框架

关于数据库的介绍就到这里了,若是读者对mongodb操做感兴趣能够查阅更多相关资料。

下面,咱们把mongodb数据融入到咱们的爬虫框架中,并经过框架把上一节的爬虫爬取的内容存入数据库中。这里其实在框架里面添加一行代码便可。

进程间锁

考虑一个场景,咱们在爬取数据的时候,确定不但愿有重复的数据添加到数据库里面,因此咱们可能须要在插入数据库以前,判断一下这条数据有没有插入过。判断插入再插入的代码以下:

insert_id = 2
if items.find_one({"id": insert_id}) is None:  # 步骤①:判断
    items.insert({{"id": insert_id}})  # 步骤②:插入
复制代码

可是,这里考虑一个场景,在多进程操做时候,咱们两个进程:进程1和进程2,同时使得insert_id为2。这个时候数据都尚未插入,因此find_one以后获得的都是None。可是下一时刻,进程1先插入了数据,这个时候进程2由于先前进行find_one也获得的是None,因此就会插入两条id为2的数据。

虽然这个可能性很是低,可是不能排除。这里咱们使用锁就行了。

lock = Manager().Lock()

with lock:
    if items.find_one({"id": insert_id}) is None:  # 步骤①:判断
        items.insert({{"id": insert_id}})  # 步骤②:插入
复制代码

在操做插入代码块的时候,进程1和进程2要获取锁,才能执行。好比说这个时候进程1拿到锁了,进程2没有拿到,那么进程2就会等待。进程1把步骤①和步骤②都完成以后,才会释放锁。进程②拿到锁,再进行步骤①和步骤②。

这样就不会出现上面的那种状况了。

因此,咱们能够在咱们的框架中也添加一个这样的锁。咱们须要再咱们的框架中添加这个锁,而后把这个锁传给worker,worker在须要数据库操做的时候再使用这个锁就行了。

爬虫实践

接下来咱们须要对上一节的worker进行稍微修改,首先添加lock参数。而后再把数据插入到数据库中。

以后运行爬虫,使用mongo客户端检验一下爬取的数据是否存入数据库。

在MongoDB自带的客户端中写代码查看有的时候有些麻烦,读者也能够装一个MongoDB的可视化客户端,我这里用的是Robo 3T,也是有免费版本的。

补充说明

咱们上面的操做其实至关于同一时间只会操做一条数据,方式比较简单粗暴。实际上,mongodb是能够支持多链接的,也就是说能够并发操做。

读者感兴趣的话,能够考虑在框架中添加一个pipeline,在pipeline中构建多个mongodb数据库的链接,用于专门操做数据库数据。

相关文章
相关标签/搜索