在前面咱们介绍了如何编写爬虫,可是咱们的爬虫并无把数据保存下来,只是简单的显示在控制台中。在本节,咱们将简单学习一下数据库,以及如何在python中操做数据库。python
最后,咱们将修改上一节的爬虫框架,使其支持数据库插入。mongodb
注:若是读者已经了解mongodb,能够直接跳到最后一个部分:修改咱们的爬虫框架。数据库
数据库其实也就是数据仓库,用来存储数据的地方。如下是数据库在维基百科中的解释:json
数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户能够对文件中的数据运行新增、截取、更新、删除等操做。数组
没有数据库,咱们可能会把爬取的数据存成一个json文件,插入的时候可能要先把整个json序列化成python的列表,而后再进行增删改查,并且数据操做的效率可能会比较低。有了数据库,数据库会给咱们提供方便的API接口,能够很容易对数据进行增删改查操做,而且高效。bash
MongoDB是一种文档型数据库,它属于非关系型数据库。服务器
若是没有安装过MongoDB,须要先对mongodb进行一下安装。并发
MongoDB有商业版也有社区版本,咱们下载免费的社区版本就行了。能够在此处www.mongodb.com/download-ce…看到各类操做系统的MongoDB安装包。框架
咱们能够选择一款适合本身的操做系统的进行下载安装。下面咱们分别以Mac和Windows系统举例来进行MongoDB的安装。函数
首先咱们下载最新版本(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数据默认存放的路径是/data/db
,若是这个目录不存在的话,须要本身建立。确保数据建立后,经过mongod
命令便可启动mongodb服务。
./bin/mongod # 启动mongodb服务
复制代码
若是不想用/data/db
这个路径的话,能够经过--dbpath
参数设置想要存放的位置。
mkdir test
./bin/mongod --dbpath ./test # 数据存放在当前路径的test目录下
复制代码
接下来咱们测测能不能连上服务器,这里能够用mongodb自带的客户端:mongo
命令。在终端中输入如下命令,尝试链接mongodb服务。
./bin/mongo
复制代码
若是输入命令后,成功看到左下角有个代输入的光标,就说明安装成功了。
首先咱们下载最新版本(4.0.4版本)的mongodb的包,咱们这个选择zip安装包。
下载后进行解压,而后进入解压后的目录,能够看到有一个bin文件夹,这里面就是mongodb的各类脚本。
而后咱们须要建立数据存放的目录,mongodb数据默认存放的路径是C:\data\db
,若是这个目录不存在的话,须要本身建立。
确保数据建立后,经过mongod.exe
命令便可启动mongodb服务。
若是不想用C:\data\db
这个路径的话,能够经过--dbpath
参数设置想要存放的位置。
接下来咱们测测能不能连上服务器,这里能够用mongodb自带的客户端。在终端中运行mongo.exe
,尝试链接mongodb服务。
若是运行后,成功看到左下角有个代输入的光标,就说明安装成功了。
MongoDB以BSON格式的文档(Documents)形式存储。Databases中包含集合(Collections),集合(Collections)中存储文档(Documents)。接下来咱们简单了解一下这几个概念。
Databases是数据库,咱们通常会把不一样的项目划分红不一样的数据库。
咱们可使用show dbs
查看已有的数据库,使用use db_name
进入某个数据库。下面是咱们进入test数据库的截图。
Collections是集合,一个项目中,也会有不一样格式的数据。咱们通常会将同一种类型的数据放在一个集合里面。好比说咱们开发网站有新闻,可能会建立一个news集合;也须要有用户,再建立一个user集合。
集合的概念就如同关系型数据库里面的表同样。
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对某一个文档进行查找。
咱们这里是一个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方法接受多个参数,主要有三个。
咱们先来看看更新一条数据。
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数据库的链接,用于专门操做数据库数据。