12月的第一天,祝所有小伙伴儿的12月都能够被温柔以待。
能在学校悠哉写推送的日子所剩不多了,为了珍惜剩下所剩不多的推送机会,打算12月写一些实践性强一些的内容,比如数据库(包括关系型的和noSQL)。
前段时间一直在探索数据抓取的内容,那么现在问题来了,抓完数据如何存储呢?
保存成本地文件是一种方案,但是借助关系型数据库或者noSQL数据库,我们可以给自己获取的数据提供一个更为理想的安身之所。
今天这一篇粗浅的聊一聊非结构化数据存储,以及R语言和Python与mongoDB之间的通讯。
写这一篇是因为之前在写web数据抓取的时候,涉及大量的json数据,当然我们可以直接将json转换为R语言(dataframe/list)或者Python(dict/DataFrame)中的内置数据对象,但是保存原始数据往往也很重要,即便是list或者dict,如果不能转化为关系型表格,通常也需要在本地保存成json格式的数据源。
那么通过mongoDB这种专业的noSQL数据库来保存非结构化数据,可以完成批量保存、批量读取、条件查询和更新,这样可以集中维护,显得更具有安全性、便利性、专业性。
mongo数据库的数据对象是bson,这种数据结构相当于json标准的扩展,R语言中的list可以与json互转,Python中的dict本身就与json高度兼容。
R语言
在R语言中,通常通过rmongodb包来进行非结构化数据存储。(当然有替代的包,只是这个包资料相对较多一些!)
代码语言:javascript复制###下载:
devtools::install_github("mongosoup/rmongodb")
library("rmongodb")
创建/断开连接
代码语言:javascript复制mongo <- mongo.create(host = "localhost")
mongo.is.connected(mongo) #检查是否连接成功
mongo.destroy(mongo) #断开连接
关于如何在系统中启动mongodb服务,网络上有很多此类教程,照葫芦画瓢就好,如果你想使用一个类似MySQL的navicat那样的可视化操作界面,可以考虑安装Robo可视化界面,这样基本就可以手动操作mongodb中的数据对象了。
mongodb中的数据对象,与MySQL中的数据对象略有不同,不过从层级上来看,仍然是分成数据库 》集合(表) 》key-value.
一个数据库中可以有很多个集合(相当于表),每一个集合中又包含很多的documents结构。每一个documents作为一条记录,相当于SQL中的一行,而documents内是键值对结构,且允许包含嵌套结构。一个documents对象内嵌套的同一层级key-value对象,被称为fileds,可以近似理解为SQL中的column。
mongodb的数据对象叫做bson,是Binary JSON Serialization的缩写简称,关于详细的json和bson的概念及其内含关系,可以查阅相关资料,或者通过W3C网站了解。
接下来进入R语言与mongodb链接的操作讲解。
以上已经建立了一个名为mongo的链接(mongo.is.connected结果可以用于测试连接是否成功!)。
代码语言:javascript复制###查看本地数据库文件
mongo.get.databases(mongo) #查看本地数据库名称
mongo.get.database.collections(mongo, db = "pymongo_test") #查看pymongo_test数据库内的各个集合名称
mongo.count(mongo, ns = "pymongo_test") #查看pymongo_test数据库内的集合数量
mongo.rename(mongo, "pymongo_test.posts", "pymongo_test.post") #修改pymongo_test数据库内posts表名称
删除操作
代码语言:javascript复制mongo.drop.database(mongo, db = "database")
#移除数据库及其内部所有集合
mongo.drop(mongo, ns = "database.collection")
#仅删除数据库内全部集合(collection)
mongo.drop(mongo, ns = "rmongo_test.mydata1")
#移除数据集合内的某一特定表
mongo.remove(mongo, ns, criteria = mongo.bson.empty())
#移除集合内选定条件的记录
其中ns是命名空间参数,格式为“数据库名称.集合名称”。
rmongodb内没有专门创建数据库或者在数据库中创建集合的函数,想要创建的话仅需在插入数据时指定一个不存在的ns参数即可。
R语言中的非结构化数据对象是list,因为list结构与json或者bson差别比较大,在插入mongo之前需要使用特定函数进行list/json与bson之间的相互转化。
涉及转化的函数有两个:
代码语言:javascript复制mongo.bson.from.JSON #将json对象转换为mongodb中的bson对象。
mongo.bson.from.list #将list对象转换为mongodb中的bson对象。
使用json格式数据插入mongo
代码语言:javascript复制#新建一个json对象
json <- '{"A":1,"B":2,"C":{"D":3,"E":4}}'
[1] "{"A":1,"B":2,"C":{"D":3,"E":4}}"
#如果你不想手写json,也可以使用jsonlite包中的toJSON函数(一定记得anto_unbox设置为RUE)
json <- jsonlite::toJSON(list("A"=1,"B"=2,"C"=list("D"=3,"E"=4)),auto_unbox = TRUE)
{"A":1,"B":2,"C":{"D":3,"E":4}}
#注:使用jsonlite::toJSON函数将一个list转为一个json字符串,这个字符串拥有一个名为json的类,
但是并未改变其内容,仅仅是添加了一个类,同时输出的外观优化了下。所以以上两种list转json的方法等价。
代码语言:javascript复制#将json对象转换为mongodb可识别的bson对象:
bson <- mongo.bson.from.JSON(json)
A : 16 1
B : 16 2
C : 3
D : 16 3
E : 16 4
#转化为basn后的数据结构内容未变,但是出现了树状层级结构。
插入mongo(注意这里的rmongo_test.mydata是数据库名 “.” 表名,而且数据库名和表明都是不存在的,这样会自动创建新数据库及表)
代码语言:javascript复制mongo.get.databases(mongo)
[1] "pymongo_test"
mongo.insert(mongo,ns="rmongo_test.mydata",bson)
[1] TRUE
mongo.get.databases(mongo)
[1] "pymongo_test" "rmongo_test"
使用list结构插入mongodb与使用json格式步骤差不多,不同的是要使用list转bson的转化函数。
代码语言:javascript复制list <- list(a=2, b=3, c=list(d=4, e=5))
bson <- mongo.bson.from.list(list)
mongo.insert(mongo,"rmongo_test.mydata",bson)
#使用之前的数据库 表名会将本次插入的记录添加到mydata已经存在的记录后面
mongo.insert(mongo,"rmongo_test.mydata1",bson)
#换一个表名则会在rmongo_test数据库中新建一个表
mongo.drop(mongo, ns = "rmongo_test.mydata1")
#移除数据集合内的某一特定表(删掉刚才新插的mydata1)
数据查询
查询其中一条记录(第一条),使用mongo.find.one函数。
代码语言:javascript复制tmp <- mongo.find.one(mongo, ns = "rmongo_test.mydata")
_id : 7 5a21346e5da941b6eb611cb7
A : 16 1
B : 16 2
C : 3
D : 16 3
E : 16 4
tmp对象是一个bson结构,需要转化为list才能得到内置数据结构。
代码语言:javascript复制tmp <- mongo.bson.to.list(tmp)
$`_id`
{ $oid : "5a21346e5da941b6eb611cb7" }
$A
[1] 1$B
[1] 2$C
$C$D
[1] 3$C$E
[1] 4
查询表中所有记录要使用mongo.find.all函数。
代码语言:javascript复制find_all <- mongo.find.all(mongo, ns = "pymongo_test.post")
#find_all直接是将post内的bson对象转化为一个list,很奇怪,
#为啥mongo.find.one输出的是一个bson,需要使用函数转为list,不是很理解设计的原因。
mongo.find函数可以支持条件查询:
代码语言:javascript复制#创建索引条件:
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.append(buf, name="gender", value="male")
query <- mongo.bson.from.buffer(buf)
代码语言:javascript复制构造查询:
cursor <- mongo.find(mongo,"pymongo_test.post",query)
[1] "mongo.cursor"
cursor对象类似SQL中的一个游标对象,不能直接查看内部结构,需要借助迭代函数进行输出
while (mongo.cursor.next(cursor)) #判断是否还有剩余迭代次数
print(mongo.cursor.value(cursor)) #打印当前迭代记录
mongo.cursor.destroy(cursor) #关闭查询游标
_id : 7 5a21238873a36810a8c4896a
id : 2 20170101
name : 2 Jordan
age : 16 20
gender : 2 male
_id : 7 5a2123d173a36810a8c4896b
id : 2 20170101
name : 2 Jordan
age : 16 20
gender : 2 male
_id : 7 5a2123d173a36810a8c4896c
id : 2 20170202
name : 2 Mike
age : 16 21
gender : 2 male
所以也可以把cursor当成是一个迭代器,想要提取里面的查询数据,需要构造循环与迭代函数,自行提取,而mongo.find.one函数和mongo.find.all函数相当于两个快捷函数,直接提取符合条件的记录或者所有记录。
rmongosb的mongo.find函数可以支持mongodb原生的复杂查询,支持很多高级符号函数,这一点儿我暂未深入了解,留待以后再做探讨。如果你想要详细的了解mongodb的用法, 最好参考关于mongodb的专业操作书,rmongodb内的函数与mongodb的原生函数相比,还有很多地方不完善,无法支持,不过对于平时的数据存储而言最够了,用的最频繁的就是插入、读取操作了。
Python:
代码语言:javascript复制from pymongo import MongoClient,ASCENDING, DESCENDING
import pymongo,json
之前说到过,因为Python中的dict与json高度兼容(并不代表一模一样),而bson结构又是基于json的扩展,所以在Python中可以直接将dict插入mongodb数据库,而基本无需做类型转换,这一点儿Python完胜R语言。
代码语言:javascript复制#连接MongDB:
client = MongoClient()
client = MongoClient(host='localhost',port= 27017)
client = MongoClient('mongodb://localhost:27017')
以上三种连接方法等价。
代码语言:javascript复制#连接数据库:
db = client.pymongo_test
db = client['pymongo_test']
以上两句等价,用于连接数据库,与Python中访问属性的操作相同。
代码语言:javascript复制#指定集合(相当于SQL中的table)
collection = db.post
collection = db['post']
以上两句等价,db的基础上连接mongodb中的集合(相当于表)。
使用本地的json数据,创建一个带插入的临时dict结构:
代码语言:javascript复制mydata = json.load(open("D:/R/File/indy.json"))
mydata = mydata['indy movies']
mydata1 = mydata[1];mydata1
mydata2 = mydata[-2:];mydata2
为了防止数据混乱,现将之前在R语言中添加的表记录删除:
代码语言:javascript复制collection.remove({})
collection.insert_one(mydata1)
results = collection.find_one()
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and the Temple of Doom', 'producers': ['Robert Watts'], 'year': 1984}
当然也可以一次插入多条记录,不过将记录构造成一个列表即可。
代码语言:javascript复制type(mydata2)
list
collection.remove({})
collection.insert_many(mydata2)
for item in collection.find():
print(item)
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}
{'_id': ObjectId('5a21451673a36810a8c48970'), 'name': 'Indiana Jones and the Last Crusade', 'year': 1989, 'actors': {'Indiana Jones': 'Harrison Ford', 'Walter Donovan': 'Julian Glover'}, 'producers': ['Robert Watts', 'George Lucas'], 'budget': 48000000, 'academy_award_ve': False}
查询函数可以直接提供给for循环进行记录的遍历。
mangodb不允许插入重复记录,还有一些保留字符要注意。(比如英文句点“.”)
查询则提供了更为丰富的函数及可选参数。
代码语言:javascript复制#查询一条记录:
results = collection.find_one({'budget': 28170000})
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and the Temple of Doom', 'producers': ['Robert Watts'], 'year': 1984}
条件查询:
代码语言:javascript复制results = collection.find({'year': 1984})
for result in results:
print(result)
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}
查询条件支持符号函数以及正则表达式:
代码语言:javascript复制results = collection.find({'budget': {'$gt':30000000}})
#budget大于30000000的记录
for result in results:
print(result)
{'_id': ObjectId('5a21451673a36810a8c48970'), 'name': 'Indiana Jones and the Last Crusade', 'year': 1989, 'actors': {'Indiana Jones': 'Harrison Ford', 'Walter Donovan': 'Julian Glover'}, 'producers': ['Robert Watts', 'George Lucas'], 'budget': 48000000, 'academy_award_ve': False}
布尔条件查询:
代码语言:javascript复制results = collection.find({'academy_award_ve': True})
for result in results:
print(result)
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}
正则表达式查询:
代码语言:javascript复制results = collection.find({'name': {'$regex': 'Doom$'}})
for result in results:
print(result)
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}
更新操作:
代码语言:javascript复制student = collection.find_one({'year':1984})
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and the Temple of Doom', 'producers': ['Robert Watts'], 'year': 1984}
collection.update_one({"year": 1984}, {"$set": {"name": "Indiana Jones and Doom"}})
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and Doom', 'producers': ['Robert Watts'], 'year': 1984}
删除操作:
代码语言:javascript复制result = collection.delete_one({'name': 'Indiana Jones and Doom'})
data1 = collection.find()
for result in data1:
print(result)
{'_id': ObjectId('5a21451673a36810a8c48970'), 'name': 'Indiana Jones and the Last Crusade', 'year': 1989, 'actors': {'Indiana Jones': 'Harrison Ford', 'Walter Donovan': 'Julian Glover'}, 'producers': ['Robert Watts', 'George Lucas'], 'budget': 48000000, 'academy_award_ve': False}
删除之后只剩一个记录了。
Python支持的符号运算符还有很多!
符号含义示例
代码语言:javascript复制{'age': {'$lt': 20}} #$lt小于
{'age': {'$gt': 20}} #$gt大于
{'age': {'$lte': 20}} #$lte小于等于
{'age': {'$gte': 20}} #$gte大于等于
{'age': {'$ne': 20}} #$ne不等于
{'age': {'$in': [20, 23]}} #$in在范围内
{'age': {'$nin': [20, 23]}} #$nin不在范围内
正则表达式含义:
代码语言:javascript复制{'name': {'$regex': '^M.*'}} #$regex,name以M开头
{'name': {'$exists': True}} #$exists,name属性存在
{'age': {'$type': 'int'}} #$type,age的类型为int
{'age': {'$mod': [5,0]}} #$mod数字模操作,年龄模5余0
{'$text': {'$search': 'Mike'}} #$text文本查询,text类型的属性中包含Mike字符串
{'$where': 'obj.fans_count == obj.follows_count'}#$where高级条件查询,自身粉丝数等于关注数
这些运算符号以及正则表达式可以用在查询、更新、删除等所有操作上。
最后吐槽一句,R语言的rmongodb包的查询函数实在是太麻烦了,很难用,Pymongo的函数设计就很友好。
以上便是R语言、Python与mongodb数据库通讯的基础操作,如果想要了解更为详细的高阶查询操作,可以参考关于mongodb的专业技术书籍及资料。
参考资料: https://docs.mongodb.com/manual/reference/operator/query/ http://api.mongodb.com/python/current/api/pymongo/collection.html http://api.mongodb.com/python/current/api/pymongo/ 往期案例数据请移步本人GitHub: https://github.com/ljtyduyu/DataWarehouse/tree/master/File