因为目标站点返回的数据是json格式,一方面为了学习新知识,另一方面是想偷个懒,所以直接使用Mongdb保存爬虫结果。
最近有个新需求:使用Data-V对这些存量数据制作数据大屏,但目前Data-V支持的数据库只有关系型数据库(当然也可以写API来给Data-V投喂数据,但是如此好像更麻烦些),因此需要将数据从Mongodb 迁移到 MySQL。
最朴素的想法,当然是用Python写一段脚本,从Mongodb读取需求的字段,然后再逐条插入MySQL中。这个方法肯定可以获得预期的效果,预计100行以内的代码都能完成。但是,MongoDB和MySQL既然已经是很成熟的产品了,对数据迁移应该有更好的解决方案,自己再造一遍轮子实在不是个好主意。
寻找新方法
之前查看MongoDB的根目录mongodb安装路径\bin
发现有一个叫做mongoexport.exe
的文件,第一眼就觉得是想要找的功能。
> mongoexport.exe --help
> Usage:
mongoexport <options>
Export data from MongoDB in CSV or JSON format.
See http://docs.mongodb.org/manual/reference/program/mongoexport/ for more information.
利用这个工具可以将查询结果导出为CSV
或者JSON
格式,MySQL的数据导入工具刚好可以直接导入以上两种格式的数据。看来这应该就是我们需要的路径了。
mongoexport.exe的参数挺多,按照上一段代码的方法可以仔细查看,我使用的几个参数如下:
> mongoexport.exe /uri:mongodb://username:password@host:port/database /c collection /f field /o resultPATH /jsonArray
/uri 导出数据的mongodb的uri,username填用户名,password密码,host IP, port 端口,database 验证用户名、密码的数据库。
/c 想要导出的Collections
/f 需要导出的字段
/o 导出文件的路径及文件名
/jsonArray 导出json数组而非jsonline
如果需要查询,可以使用 \q
参数输入查询语句。接下来,使用MySQL的文件导入工具即可将数据导入到MySQL中,速度还是挺有保障的。
这种方法针对形如{ "_id" : 0, "nref_date" : "20190318-20190416", "nname" : "未知", "nvalue" : 1 }
的数据是比较有效的,但涉及到拥有嵌套数组的数据就显得有些难以处理了。
例如:
如何将上图中list[0]的item_list中的5对(key, value)添加ref_date存储成MySQL中的5条记录?
新的挑战
之前看文档的时候了解到,MongoDB有聚合操作(aggregate),也支持管道(pipeline)操作。将数组解包使用的是"$unwind"操作,嵌套的数组用点号分隔即可,还是先回顾下常用的一些聚合操作吧:
$project:修改输入文档结构,可用来新增、删除域,或者构建新结果。
$match:输出符合条件的文档,可以用来过滤树。
$limit:用来限制管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$group:将集合中的文档分组。
$sort:排序。
$out:将管道聚合操作返回的结果另存成一个collections,必须位于管道的最后一个操作。
本例中可以使用将list.item_list解包,注意因为list也是数组,所以使用了两次$unwind
:
db.collections.aggregate([{"$unwind": "$list"}, {"$unwind": "$list.item_list"})
执行过后,得到如下形状的5个文档:
{
"_id" : ObjectId("5cb595a2923d072acc8bdf8b"),
"ref_date" : "20181129",
"list" : {
"index" : "access_source_session_cnt",
"item_list" : {
"key" : 36,
"value" : 19
}
}
}
但输出的形式,和我们想要的结果还不太一样,如果直接使用MySQL的导入工具还是无法找到对应字段的映射关系。
接下来使用$project
修改返回文档的结构,直接在上面的管道中新增
db.collections.aggregate([{"$unwind": "$list"}, {"$unwind": "$list.item_list"}, {"$project": {"newkey": "$list.item_list.key", "newvalue": "$list.item_list.value", "new_ref_date": "$ref_date"}}])
输出的第一个元素变成以下结构:
{
"_id" : ObjectId("5cb595a2923d072acc8bdf8b"),
"newkey" : 29,
"newvalue" : 17,
"new_ref_date" : "20181129"
}
如果使用的MongoDB支持将查询结果导出成JSON
,那到这里已经可以完成任务了!遗憾的是我用的Robot 3T貌似并不支持,不过也没有关系,我们使用$out
操作符将管道的聚合结果另存成一个Collections即可。
最终的管道聚合操作有四步操作:
db.collections.aggregate([
{"$unwind": "$list"},
{"$unwind": "$list.item_list"},
{"$project": {"newkey": "$list.item_list.key", "newvalue": "$list.item_list.value", "new_ref_date": "$ref_date"}},
{"$out": "outputjson"}
])
接下来使用mongoexport.exe
工具将outputjson
导出成JSON
格式即可导入MySQL了!
MongoDB多层嵌套脑壳疼、查个文档又尽是英文、阿里云Data-v居然都不支持,以后不会再用了!!!真香!
卧槽原来可以这样!