python 全栈开发,Day126(创业故事,软件部需求,内容采集,显示内容图文列表,MongoDB数据导入导出JSON)
作业讲解
下载代码:
HBuilder APP和flask后端登录
链接:https://pan.baidu.com/s/1eBwd1sVXTNLdHwKRM2-ytg 密码:4pcw
如何打开APP和后端flask,请参数昨天的文章
进入flask后端程序目录,创建文件setting.py
设置MongoDB信息,以及json返回格式
import pymongo client = pymongo.MongoClient(host="127.0.0.1", port=27017) MONGO_DB = client["bananabase"] RET = { # 0: false 2: True "code": 0, "msg": "", # 提示信息 "data": {} }
修改后端的 manager.py
from flask import Flask, request,jsonify from setting import MONGO_DB from setting import RET app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/login',methods=["POST"]) def login(): username = request.form.get("username") password = request.form.get("password") if username == "xiao" and password == "123": return "欢迎登陆" return "登陆失败" @app.route('/reg',methods=["POST"]) def reg(): """ 注册 :return: {"code":0,"msg":"","data":""} """ try: username = request.form.get("username") password = request.form.get("password") age = request.form.get("age") nickname = request.form.get("nickname") gender = request.form.get("gender") phone = request.form.get("phone") user_info = { "username": username, "password": password, "age": age, "nickname": nickname, "gender": gender, "phone": phone } res = MONGO_DB.users.insert_one(user_info) user_id = str(res.inserted_id) RET["code"] = 0 RET["msg"] = "注册成功" RET["data"] = user_id except Exception as e: RET["code"] = 1 RET["msg"] = "注册失败" return jsonify(RET) if __name__ == '__main__': app.run("0.0.0.0", 9527, debug=True)
此时,项目结构如下:
./
├── manager.py
├── setting.py
├── static
└── templates
全局变量
由于多个html页面,需要引用同一个变量。这个时候,需要定义一个全局变量!如何定义呢?
默认包含了mui的html文件都导入mui.js文件。那么将变量写在mui.js中,就可以实现所有页面共享了!
由于开发环境的电脑的IP是自动获取的,隔一段时间,就需要修改一次。那么html中发送POST请求时,URL中的IP地址不能写死!
所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。
修改mui.js,定义全局变量,务必使用windows
* MUI核心JS * @type _L4.$|Function */ window.serv = "http://192.168.11.85:9527" window.styles = { top: "0px", bottom: "50px" }; ...
由于代码过多,用...省略了!
特别注意:虽然HBuilder,夜神模拟器,Flask后端,全部都在同一台电脑。但是window.serv的IP地址不能是127.0.0.1。
否则点击注册时完全没有反应,后端也收不到数据!因为它发给模拟器本身了!
md5.js
MD5.js是通过前台js加密的方式对密码等私密信息进行加密的工具
前端注册页面的密码,发送给后端时,需要用md5加密。防止密码在网络中被黑客截获!需要用到md5.js
打开bootcdn网页,搜索md5.js
https://www.bootcdn.cn/blueimp-md5/
下载md5.min.js,放到MyApp目录中的js文件夹中!
修改reg.html,导入js,并增加相关js代码,发送POST请求
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">用户注册</h1> </header> <div class="mui-content"> <form class="mui-input-group" style="margin-top: 15px;"> <div class="mui-input-row"> <label>用户名</label> <input type="text" class="mui-input-clear" placeholder="请输入用户名" id="username"> </div> <div class="mui-input-row"> <label>密码</label> <input type="password" class="mui-input-password" placeholder="请输入密码" id="pwd"> </div> <div class="mui-input-row"> <label>确认密码</label> <input type="password" class="mui-input-password" placeholder="请输入密码" id="repwd"> </div> <div class="mui-input-row"> <label>昵称</label> <input type="text" class="mui-input-clear" placeholder="请输入昵称" id="nickname"> </div> <div class="mui-input-row mui-radio mui-left"> <label>男</label> <input name="gender" type="radio" value="1"> </div> <div class="mui-input-row mui-radio mui-left"> <label>女</label> <input name="gender" type="radio" value="2" checked> </div> <div class="mui-input-row"> <label>年龄</label> <input type="text" class="mui-input-clear" placeholder="请输入年龄" id="age"> </div> <div class="mui-input-row"> <label>电话</label> <input type="text" class="mui-input-clear" placeholder="请输入电话" id="phone"> </div> <div class="mui-button-row"> <button type="button" class="mui-btn mui-btn-primary" id="reg">注册</button> <button type="button" class="mui-btn mui-btn-danger mui-action-back">返回</button> </div> </form> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <!--加载md5--> <script src="js/md5.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() // id为reg的标签绑定点击事件 document.getElementById("reg").addEventListener("tap",function(){ // 获取所有性别列表 var gender_list = document.getElementsByName("gender") var pwd = document.getElementById("pwd").value; //密码 if (pwd.length == 0){ mui.toast("密码不能为空") return } var repwd = document.getElementById("repwd").value; //确认密码 // 判断2次密码 if(pwd != repwd) { mui.toast("两次密码输入不一致") return } // md5方法为md5.min.js内置方法 pwd = md5(pwd); //使用md5方法对密码做md5 var username = document.getElementById("username").value; //用户名 var age = document.getElementById("age").value; //年龄 var nickname = document.getElementById("nickname").value; //昵称 var phone = document.getElementById("phone").value; //电话 var gender = null; //性别 // 遍历性别列表 for(var i = 0; i < gender_list.length; i++) { // checked表示选中,当标签被被选中时 if(gender_list[i].checked) { // 获取选中的性别,i表示索引 gender = gender_list[i].value; } } // 发送POST请求 mui.post( // window.serv + "/reg"表示 http://192.168.11.85:9527/reg window.serv + "/reg", { // 下面是需要发送的键值对 username: username, password: pwd, gender: gender, age: age, nickname: nickname, phone: phone }, function(data){ // 由于后端返回的是json,这里需要反序列化 console.log(JSON.stringify(data)) if (!data.code){ mui.toast(data.msg) }else{ mui.toast(data.msg) } } ) }) </script> </html>
使用模拟器访问
底部会有提示
使用客户端打开MongoDB,查看数据
查看HBuilder控制台输出:
{"code":0,"data":"5b9bb768e1253281608e96eb","msg":"注册成功"} at reg.html:114
由于后端的登录逻辑, 用户名和密码写死了,需要从MongoDB中获取
修改 manager.py
from flask import Flask, request,jsonify from setting import MONGO_DB from setting import RET app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/login',methods=["POST"]) def login(): """ 登陆验证 :return: settings -> RET """ try: RET["code"] = 1 RET["msg"] = "用户名或密码错误" RET["data"] = {} username = request.form.get("username") password = request.form.get("password") user = MONGO_DB.users.find_one({"username": username, "password": password}) if user: # 由于user中的_id是ObjectId对象,需要转化为字符串 user["_id"] = str(user.get("_id")) RET["code"] = 0 RET["msg"] = "欢迎登陆" RET["data"] = {"user_id": user.get("_id")} except Exception as e: RET["code"] = 1 RET["msg"] = "登陆失败" return jsonify(RET) @app.route('/reg',methods=["POST"]) def reg(): """ 注册 :return: {"code":0,"msg":"","data":""} """ try: username = request.form.get("username") password = request.form.get("password") age = request.form.get("age") nickname = request.form.get("nickname") gender = request.form.get("gender") phone = request.form.get("phone") user_info = { "username": username, "password": password, "age": age, "nickname": nickname, "gender": gender, "phone": phone } res = MONGO_DB.users.insert_one(user_info) user_id = str(res.inserted_id) RET["code"] = 0 RET["msg"] = "注册成功" RET["data"] = user_id except Exception as e: RET["code"] = 1 RET["msg"] = "注册失败" return jsonify(RET) if __name__ == '__main__': app.run("0.0.0.0", 9527, debug=True)
修改 login.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <h1 class="mui-title">登陆</h1> </header> <div class="mui-content"> <form class="mui-input-group"> <div class="mui-input-row" style="margin-top: 15px;"> <label>用户名</label> <input type="text" class="mui-input-clear" placeholder="请输入用户名" id="username"> </div> <div class="mui-input-row"> <label>密码</label> <input type="password" class="mui-input-password" placeholder="请输入密码" id="pwd"> </div> <div class="mui-button-row"> <button type="button" class="mui-btn mui-btn-red" id="login">登陆</button> <button type="button" class="mui-btn mui-btn-green" id="reg">注册</button> </div> </form> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <!--加载md5--> <script src="js/md5.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() document.getElementById("login").addEventListener("tap", function() { var uname = document.getElementById("username").value; var pwd = document.getElementById("pwd").value; pwd = md5(pwd); mui.post( window.serv + "/login", { username: uname, password: pwd }, function(data) { // 由于后端返回的是json,这里需要反序列化 console.log(JSON.stringify(data)) if (!data.code){ mui.toast(data.msg) }else{ mui.toast(data.msg) } } ); }); document.getElementById("reg").addEventListener("tap", function() { mui.openWindow({ url: "reg.html", id: "reg.html" }) }) </script> </html>
由于MongoDB中的用户表的密码字段,使用了md5加密。所以这里发送给后端的密码,也需要md5加密
重新登录,输入正确的用户名和密码
底部提示
作业要求,登录成功后,将用户ID返回给前端,并且由index页面打印欢迎{用户ID}登陆
这个时候,需要使用开火(fire)事件
修改 login.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <h1 class="mui-title">登陆</h1> </header> <div class="mui-content"> <form class="mui-input-group"> <div class="mui-input-row" style="margin-top: 15px;"> <label>用户名</label> <input type="text" class="mui-input-clear" placeholder="请输入用户名" id="username"> </div> <div class="mui-input-row"> <label>密码</label> <input type="password" class="mui-input-password" placeholder="请输入密码" id="pwd"> </div> <div class="mui-button-row"> <button type="button" class="mui-btn mui-btn-red" id="login">登陆</button> <button type="button" class="mui-btn mui-btn-green" id="reg">注册</button> </div> </form> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <!--加载md5--> <script src="js/md5.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() document.getElementById("login").addEventListener("tap", function() { var uname = document.getElementById("username").value; var pwd = document.getElementById("pwd").value; pwd = md5(pwd); mui.post( window.serv + "/login", { username: uname, password: pwd }, function(data) { // 由于后端返回的是json,这里需要反序列化 console.log(JSON.stringify(data)) if (!data.code){ // mui.toast(data.msg) // index页面的WebviewById为HBuilder var index = plus.webview.getWebviewById("HBuilder") // 触发fire事件,发送数据 mui.fire(index,"login",{msg:data.msg + data.data.user_id}) }else{ mui.toast(data.msg) } } ); }); document.getElementById("reg").addEventListener("tap", function() { mui.openWindow({ url: "reg.html", id: "reg.html" }) }) </script> </html>
修改 index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <script src="js/mui.js"></script> <link href="css/mui.min.css" rel="stylesheet" /> </head> <body> <nav class="mui-bar mui-bar-tab"> <a class="mui-tab-item mui-active" id="index"> <span class="mui-icon mui-icon-home"></span> <span class="mui-tab-label">首页</span> </a> <a class="mui-tab-item" id="phone"> <span class="mui-icon mui-icon-phone"></span> <span class="mui-tab-label">电话</span> </a> <a class="mui-tab-item"> <span class="mui-icon mui-icon-email"></span> <span class="mui-tab-label">邮件</span> </a> <a class="mui-tab-item" id="login"> <span class="mui-icon mui-icon-gear"></span> <span class="mui-tab-label">设置</span> </a> </nav> </body> <script type="text/javascript" charset="utf-8"> mui.init({ subpages: [{ url: "main.html", id: "main.html", styles: window.styles }] }); mui.plusReady(function() { console.log(JSON.stringify(plus.webview.currentWebview())) }); document.getElementById("phone").addEventListener("tap", function() { mui.toast("你點擊了電話按鈕"); mui.openWindow({ url: "phone.html", id: "phone.html", styles: window.styles, extras: { user_id: 123456 } }) }) document.getElementById("index").addEventListener("tap", function() { mui.openWindow({ url: "main.html", id: "main.html", styles: window.styles }) }) document.getElementById("login").addEventListener("tap", function() { mui.openWindow({ url: "login.html", id: "login.html", styles: window.styles }) }) document.addEventListener("login",function(data){ // fire事件接收消息,使用data.detail // index是为做显示区分 mui.toast("index"+data.detail.msg) }) </script> </html>
重新登录,底部提示,效果如下:
作业需求基本上,就完成了!
但是用户登录之后,应该跳转到用户主页才对!
Storage
Storage模块管理应用本地数据存储区,用于应用数据的保存和读取。应用本地数据与localStorage、sessionStorage的区别在于数据有效域不同,前者可在应用内跨域操作,数据存储期是持久化的,并且没有容量限制。通过plus.storage可获取应用本地数据管理对象。
方法:
- getLength: 获取应用存储区中保存的键值对的个数
- getItem: 通过键(key)检索获取应用存储的值
- setItem: 修改或添加键值(key-value)对数据到应用数据存储中
- removeItem: 通过key值删除键值对存储的数据
- clear: 清除应用所有的键值对存储数据
- key: 获取键值对中指定索引值的key值
参考链接:
http://www.html5plus.org/doc/zh_cn/storage.html
新建一个 user_info.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <h1 class="mui-title">用户信息</h1> </header> <div class="mui-content"> <ul class="mui-table-view"> <li class="mui-table-view-cell"><span>用户名</span><span id="username" class="mui-pull-right">111</span></li> <li class="mui-table-view-cell"><span>昵称</span><span id="nickname" class="mui-pull-right">22</span></li> </ul> <button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="logout">退出登陆</button> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() mui.back = function() {}; mui.plusReady(function() { // 当前web视图 var Sdata = plus.webview.currentWebview(); mui.post( window.serv + "/user_info", { // Sdata.user_id,这里面的user_id是由login.html传递过来的 user_id: Sdata.user_id }, function(data) { console.log(JSON.stringify(data)); // 修改页面的text属性 document.getElementById("username").innerText = data.username; document.getElementById("nickname").innerText = data.nickname; } ) }) document.getElementById("logout").addEventListener("tap", function() { // 删除storage里面的user属性 plus.storage.removeItem("user") // 跳转页面login.html mui.openWindow({ url: "login.html", id: "login.html", styles: window.styles }) }) </script> </html>
修改login.html,给user_info.html传值
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <h1 class="mui-title">登陆</h1> </header> <div class="mui-content"> <form class="mui-input-group"> <div class="mui-input-row" style="margin-top: 15px;"> <label>用户名</label> <input type="text" class="mui-input-clear" placeholder="请输入用户名" id="username"> </div> <div class="mui-input-row"> <label>密码</label> <input type="password" class="mui-input-password" placeholder="请输入密码" id="pwd"> </div> <div class="mui-button-row"> <button type="button" class="mui-btn mui-btn-red" id="login">登陆</button> <button type="button" class="mui-btn mui-btn-green" id="reg">注册</button> </div> </form> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <!--加载md5--> <script src="js/md5.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() document.getElementById("login").addEventListener("tap", function() { var uname = document.getElementById("username").value; var pwd = document.getElementById("pwd").value; pwd = md5(pwd); mui.post( window.serv + "/login", { username: uname, password: pwd }, function(data) { // 由于后端返回的是json,这里需要反序列化 console.log(JSON.stringify(data)) if (!data.code){ // mui.toast(data.msg) // index页面的WebviewById为HBuilder //var index = plus.webview.getWebviewById("HBuilder") // 触发fire事件,发送数据 //mui.fire(index,"login",{msg:data.msg + data.data.user_id}) mui.toast(data.msg + data.data.user_id); //window.location.Storage.setItem("user",data.data.user_id); //修改或添加键值(key-value)对数据到应用数据存储中 plus.storage.setItem("user", data.data.user_id); mui.openWindow({ url:"user_info.html", id:"user_info.html", styles:window.styles, //使用 extras实现页面间传值 extras:{ // 传输user_id user_id:data.data.user_id } }); }else{ mui.toast(data.msg) } } ); }); document.getElementById("reg").addEventListener("tap", function() { mui.openWindow({ url: "reg.html", id: "reg.html" }) }) </script> </html>
修改后端 manager.py,增加uesr_info
from flask import Flask, request,jsonify from setting import MONGO_DB from setting import RET from bson import ObjectId app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/login',methods=["POST"]) def login(): """ 登陆验证 :return: settings -> RET """ try: RET["code"] = 1 RET["msg"] = "用户名或密码错误" RET["data"] = {} username = request.form.get("username") password = request.form.get("password") user = MONGO_DB.users.find_one({"username": username, "password": password}) if user: # 由于user中的_id是ObjectId对象,需要转化为字符串 user["_id"] = str(user.get("_id")) RET["code"] = 0 RET["msg"] = "欢迎登陆" RET["data"] = {"user_id": user.get("_id")} except Exception as e: RET["code"] = 1 RET["msg"] = "登陆失败" return jsonify(RET) @app.route('/reg',methods=["POST"]) def reg(): """ 注册 :return: {"code":0,"msg":"","data":""} """ try: username = request.form.get("username") password = request.form.get("password") age = request.form.get("age") nickname = request.form.get("nickname") gender = request.form.get("gender") phone = request.form.get("phone") user_info = { "username": username, "password": password, "age": age, "nickname": nickname, "gender": gender, "phone": phone } res = MONGO_DB.users.insert_one(user_info) user_id = str(res.inserted_id) RET["code"] = 0 RET["msg"] = "注册成功" RET["data"] = user_id except Exception as e: RET["code"] = 1 RET["msg"] = "注册失败" return jsonify(RET) @app.route('/user_info', methods=["POST"]) def user_info(): user_id = request.form.get("user_id") # "password": 0 表示忽略密码字段 res = MONGO_DB.users.find_one({"_id": ObjectId(user_id)}, {"password": 0}) if res: res["_id"] = str(res.get("_id")) RET["code"] = 0 RET["msg"] = "" RET["data"] = res return jsonify(res) if __name__ == '__main__': app.run("0.0.0.0", 9527, debug=True)
使用模拟器重新登录,会自动跳转用户页面,效果如下:
底部有提示
总结:
mui的全局变量: 配置文件,使用windows 页面视图中,使用storage: plus.storage.setItem("key",value) # 设置storage全局变量 plus.storage.getItem("key") # 获取全局的storage 获取到了就是 value 获取不到就是 Null plus.storage.removeItem("key") # 清除storage
一、创业故事
背景人物
老张
老张,它是一个拥有十几年工作经验的销售,40岁左右。 籍贯是黑龙江哈尔滨,长子5岁,小女3岁。 在北京闯荡多年,长期与妻小分离。不想让孩子用手机沟通,因为有辐射。
突然想孩子了,怎么办呢?思考了一问题,留守儿童如何父母建议有效的沟通。对于孩子而言,玩具是陪伴时间最长的。如何让让玩具成为孩子伙伴帮助孩子成长,让玩具成为与父母沟通的桥梁?
那么玩具需要能发送语音消息,玩具还可以和周围的小伙伴沟通。形成一个良好的社交圈!
这个时候,打了一个电话给老李
老李
老李,做了几十年的产品,懂一点技术,37岁。籍贯是陕西西安,女儿4岁。在在北京闯荡多年,也是留守了自己的女儿在外地。 接到老张的电话之后,立马想到需要技术实现。
这个时候,想到了一个技术比较不错的闫帅。
闫帅
闫帅,29岁,做了8年的技术 ,曾经与老李是同事,接到老李电话,一拍即合。
故事剧情
老张从打电话到公司组建,花了3天的时间
老李,负责设计产品。
闫帅介入,想到涉及到硬件了。对于硬件不精通,需要请硬件老师。招兵买马,组件了技术团队!
公司成立
1.行政人力财务综合部 1个人
2.产品部:老李 + UI 小姐姐 2个人
3.软件部:闫帅 + 1前端 + 后端 3个人
4.硬件部:江老师 + 硬件工程师(1人)
5.营销部:老张 + 1 个运营
二、软件部需求
项目的需求明确
1.留守儿童,让玩具成为孩子伙伴帮助孩子成长,让玩具成为与父母沟通的桥梁(语音消息)
2.能让玩具成为一个朋友圈的沟通工具(社交圈)
细化需求
1.玩具:硬件方案--(皮毛(真皮)175,硬件135 ,人工硬件组装15,皮毛人工60,运费:12,包装35)定价:499,初期销售了20个!
2.让玩具成为孩子伙伴
3.帮助孩子成长
4.让玩具成为与父母沟通的桥梁(语音消息)
5.如果能让玩具成为一个朋友圈的沟通工具(社交圈)
如何开发
闫帅与老李沟通商讨如何开发,闫帅开始部署后端
1. 1个前端 + 闫帅 + 1个全栈工程师(先做后端在做前端)
2. 因为闫帅的疏忽严厉批评一次前端,前端离职了,全栈工程师包揽了前端的活
3. 全栈工程师经过8个月之后,成为了公司的产品的大拿
人工智能
为啥不做ai底层开发?150万上下,不足以支撑底层开发!
使用第三方:百度ai,体验不行!花钱买了科大讯飞的服务
三、内容采集
分析数据
主要是针对儿童的,需要采集一些儿歌。这里使用 喜马拉雅FM,官方地址:
点击儿童分类,这里主要采集 一千零一夜频道 ,因为声音比较好听。具有优秀的配音员团队。
https://www.ximalaya.com/zhubo/9216785/
选择 一千零一夜经典儿歌 专辑
https://www.ximalaya.com/ertong/424529/
先来访问 新年恰恰 的链接,在新窗口中打开
https://www.ximalaya.com/ertong/424529/7713678
点击分享
点击 展开获取声音链接
点击 微电台的链接
链接如下:
http://m.ximalaya.com/sound/7713678
打开浏览器工具,点击network
点击上面的播放按钮,它会发送网络请求,点击7713678.json
查看Preview,它是一个json数据,这个就是我们需要的。
往下拉,找到play_path,复制地址,网页直接访问!
这里的文件名,进行了加密。单这不重要,只要能访问就行!
http://audio.xmcdn.com/group12/M00/3B/B2/wKgDXFWcw12y8TanAAtkIsI9320251.m4a
网页就直接播放了
通过这一段json,我们需要5个信息
播放地址(play_path),图片地址(cover_url),简介(intro),昵称(nickname),标题(title)
查看Headers,它的请求地址是
http://m.ximalaya.com/tracks/7713678.json
和前面的7713678.json是一样的
和专辑页面的id也是一样的
那么通过这个id,拼接路径,就可以访问其他歌曲了,规律是:
http://m.ximalaya.com/tracks/歌曲id.json
采集数据
这里使用的是requests模块。请确保安装了
pip install requests
新建一个文件 xiaopapa.py
import requests XMLY_URL = "http://m.ximalaya.com/tracks/" # 从专辑列表获取的a标签的herf属性 content_url = "/ertong/424529/7713678" # rsplit从右向左寻找,以某个元素为中心将左右分割成两个元素并放入列表中 # rsplit("/",1) 这里面的1表示分割次数,只分割一次 # [-1] 取最后一个元素 pid = content_url.rsplit("/",1)[-1] # 获取歌曲id,也就是7713678 # 拼接url,也就是http://m.ximalaya.com/tracks/7713678.json xiaopapa_url = XMLY_URL + pid + ".json" content = requests.get(xiaopapa_url) # get方式访问url # 获取返回结果,使用content.content。由于是bytes类型,需要解码 print(content.content.decode("utf-8"))
执行输出,由于内容过长,这里省略了一部分
{"id":7713678,"play_path_64":"...","play_path_32":"...","play_path":"http://audio.xmcdn.com/group12/M00/3B/B2/wKgDXFWcw12y8TanAAtkIsI9320251.m4a","duration":92,"title":"新年恰恰","nickname":"一千零一夜频"...}
注意:此链接,不要频繁范围,否则会封锁IP的!
f-string
它是一种改进Python格式字符串的新方法。
好消息是,F字符串在这里可以节省很多的时间。他们确实使格式化更容易。他们自Python 3.6开始加入标准库。您可以在PEP 498中阅读所有内容。
也称为“格式化字符串文字”,F字符串是开头有一个f的字符串文字,以及包含表达式的大括号将被其值替换。表达式在运行时进行渲染,然后使用__format__
协议进行格式化。与往常一样,Python文档是您想要了解更多信息的最佳读物。
以下是f-strings可以让你的生活更轻松的一些方法。
简单例子
语法与str.format()
使用的语法类似,但是在一些细节部分,比较啰嗦。看看这是多么容易可读:
name = "Eric" age = 74 res = f"Hello, {name}. You are {age}." print(res)
执行输出:
Hello, Eric. You are 74.
使用大写字母F也是有效的:
res = F"Hello, {name}. You are {age}."
执行输出,效果同上!
如果你正好使用了Python 3.6,丢弃format(),用f()吧!
上面的xiaopapa.py,输出的是一个json数据,需要反序列化,获取需要的5个信息
import requests import json XMLY_URL = "http://m.ximalaya.com/tracks/" # 从专辑列表获取的a标签的herf属性 content_url = "/ertong/424529/7713678" # rsplit从右向左寻找,以某个元素为中心将左右分割成两个元素并放入列表中 # rsplit("/",1) 这里面的1表示分割次数,只分割一次 # [-1] 取最后一个元素 pid = content_url.rsplit("/",1)[-1] # 获取歌曲id,也就是7713678 # 拼接url,也就是http://m.ximalaya.com/tracks/7713678.json xiaopapa_url = XMLY_URL + pid + ".json" content = requests.get(xiaopapa_url) # get方式访问url # 获取返回结果,使用content.content。由于是bytes类型,需要解码 content_dict = json.loads(content.content.decode("utf8")) play_path = content_dict.get("play_path") # 播放地址 cover_url = content_dict.get("cover_url") # 图片地址 intro = content_dict.get("intro") # 简介 nickname = content_dict.get("nickname") # 昵称 title = content_dict.get("title") # 标题 print(play_path) print(cover_url) print(intro) print(nickname) print(title)
执行输出:
http://audio.xmcdn.com/group12/M00/3B/B2/wKgDXFWcw12y8TanAAtkIsI9320251.m4a http://fdfs.xmcdn.com/group9/M04/3B/E1/wKgDZlWcvRKwSOIMAAD3201gPxc590.jpg 【一千零一夜】经典儿歌 一千零一夜频道 新年恰恰
有了这些信息,就可以下载音频和图片了
新建目录audio和audio_img
此时目录结构如下:
./
├── audio
├── audio_img
├── manager.py
├── setting.py
├── static
├── templates
└── xiaopapa.py
写入文件
为了统一管理,将喜马拉雅FM的url和文件目录写入配置文件
修改setting.py
import pymongo client = pymongo.MongoClient(host="127.0.0.1", port=27017) MONGO_DB = client["bananabase"] RET = { # 0: false 2: True "code": 0, "msg": "", # 提示信息 "data": {} } XMLY_URL = "http://m.ximalaya.com/tracks/" # 喜马拉雅链接 # 文件目录 import os AUDIO_FILE = os.path.join(os.path.dirname(__file__), "audio") AUDIO_IMG_FILE = os.path.join(os.path.dirname(__file__), "audio_img")
修改manager.py,文件名使用uuid4,防止重名
import requests import json import os import setting from uuid import uuid4 XMLY_URL = "http://m.ximalaya.com/tracks/" # 从专辑列表获取的a标签的herf属性 content_url = "/ertong/424529/7713678" category = "erge" # 分类 # rsplit从右向左寻找,以某个元素为中心将左右分割成两个元素并放入列表中 # rsplit("/",1) 这里面的1表示分割次数,只分割一次 # [-1] 取最后一个元素 pid = content_url.rsplit("/",1)[-1] # 获取歌曲id,也就是7713678 # 拼接url,也就是http://m.ximalaya.com/tracks/7713678.json xiaopapa_url = XMLY_URL + pid + ".json" content = requests.get(xiaopapa_url) # get方式访问url # 获取返回结果,使用content.content。由于是bytes类型,需要解码 content_dict = json.loads(content.content.decode("utf8")) play_path = content_dict.get("play_path") # 播放地址 cover_url = content_dict.get("cover_url") # 图片地址 intro = content_dict.get("intro") # 简介 nickname = content_dict.get("nickname") # 昵称 title = content_dict.get("title") # 标题 file_name = f"{uuid4()}" # 随机文件名 audio = f"{file_name}.mp3" # 音频 image = f"{file_name}.jpg" # 图片 audio_file = requests.get(play_path).content # 访问音频链接,获取二进制数据 with open(os.path.join(setting.AUDIO_FILE, audio), "wb") as f: f.write(audio_file) # 写入文件 image_file = requests.get(cover_url).content # 访问图片链接,获取二进制数据 with open(os.path.join(setting.AUDIO_IMG_FILE, image), "wb") as f: f.write(image_file) # 写入文件
执行 manager.py,此时audio和audio_img分别多出一个文件
80c5fe21-de1a-4b59-a9c6-cbeb1c2691be.mp3
80c5fe21-de1a-4b59-a9c6-cbeb1c2691be.jpg
写入 MongoDB 数据库
谁知道,这2个文件,对应是 新年恰恰 呢?需要写入数据库,这里采用MongoDB
修改 manager.py
import requests import json import os import setting from uuid import uuid4 XMLY_URL = "http://m.ximalaya.com/tracks/" # 从专辑列表获取的a标签的herf属性 content_url = "/ertong/424529/7713678" category = "erge" # 分类 # rsplit从右向左寻找,以某个元素为中心将左右分割成两个元素并放入列表中 # rsplit("/",1) 这里面的1表示分割次数,只分割一次 # [-1] 取最后一个元素 pid = content_url.rsplit("/",1)[-1] # 获取歌曲id,也就是7713678 # 拼接url,也就是http://m.ximalaya.com/tracks/7713678.json xiaopapa_url = XMLY_URL + pid + ".json" content = requests.get(xiaopapa_url) # get方式访问url # 获取返回结果,使用content.content。由于是bytes类型,需要解码 content_dict = json.loads(content.content.decode("utf8")) play_path = content_dict.get("play_path") # 播放地址 cover_url = content_dict.get("cover_url") # 图片地址 intro = content_dict.get("intro") # 简介 nickname = content_dict.get("nickname") # 昵称 title = content_dict.get("title") # 标题 file_name = f"{uuid4()}" # 随机文件名 audio = f"{file_name}.mp3" # 音频 image = f"{file_name}.jpg" # 图片 audio_file = requests.get(play_path).content # 访问音频链接,获取二进制数据 with open(os.path.join(setting.AUDIO_FILE, audio), "wb") as f: f.write(audio_file) # 写入文件 image_file = requests.get(cover_url).content # 访问图片链接,获取二进制数据 with open(os.path.join(setting.AUDIO_IMG_FILE, image), "wb") as f: f.write(image_file) # 写入文件 content_db = { "title": title, "nickname": nickname, "avatar": image, "audio": audio, "intro": intro, "category":category, # 分类 "play_count": 0 # 播放次数 } setting.MONGO_DB.sources.insert_one(content_db) # 插入一条数据
执行 manager.py,使用mongodb客户端,查看数据。发现有一条数据,效果如下:
再录入几条数据,打开网页:
https://www.ximalaya.com/ertong/424529/
复制几首歌的id,比如这样
<a title="鱼儿水中游" href="/ertong/424529/7713768">鱼儿水中游</a> <a title="祝你圣诞快乐" href="/ertong/424529/7713763">祝你圣诞快乐</a> <a title="祖国祖国我们爱你" href="/ertong/424529/7713762">祖国祖国我们爱你</a> <a title="最美的图画" href="/ertong/424529/7713760">最美的图画</a>
修改 manager.py,修改最后的id,因为424529(专辑)是一样的
content_url = "/ertong/424529/7713678"
改成
content_url = "/ertong/424529/7713768"
它表示 鱼儿水中游这首歌曲,运行一次。
其他歌曲,依次类推。最后MongoDB中有5首歌曲
五、显示内容图文列表
后端接口
既然数据库有数据了,那么就需要展示到图文列表了。要替换这部分内容
进入flask后端目录,新建目录serv,在此目录下创建文件get_file.py,用来获取音频和图片
from flask import Blueprint, send_file from setting import AUDIO_FILE from setting import AUDIO_IMG_FILE import os getfile = Blueprint("getfile", __name__) @getfile.route("/get_audio/<filename>") def get_audio(filename): # 获取音频 sendfile = os.path.join(AUDIO_FILE, filename) return send_file(sendfile) @getfile.route("/get_image/<filename>") def get_image(filename): # 获取图片 sendfile = os.path.join(AUDIO_IMG_FILE, filename) return send_file(sendfile)
继续新建content.py
from flask import Blueprint, jsonify from setting import MONGO_DB from setting import RET cont = Blueprint("cont", __name__) @cont.route("/content_list", methods=["POST"]) def content_list(): # 内容列表 res_list = list(MONGO_DB.sources.find({})) # 字典转换列表 for index, item in enumerate(res_list): #返回 enumerate(枚举)对象 # 由于_id是ObjectId对象,转换为字符串 res_list[index]["_id"] = str(item.get("_id")) RET["code"] = 0 RET["msg"] = "" RET["data"] = res_list return jsonify(RET) # 返回json数据
修改 manager.py,注册2个蓝图
from flask import Flask, request,jsonify from setting import MONGO_DB from setting import RET from bson import ObjectId from serv import get_file from serv import content app = Flask(__name__) app.register_blueprint(get_file.getfile) app.register_blueprint(content.cont) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/login',methods=["POST"]) def login(): """ 登陆验证 :return: settings -> RET """ try: RET["code"] = 1 RET["msg"] = "用户名或密码错误" RET["data"] = {} username = request.form.get("username") password = request.form.get("password") user = MONGO_DB.users.find_one({"username": username, "password": password}) if user: # 由于user中的_id是ObjectId对象,需要转化为字符串 user["_id"] = str(user.get("_id")) RET["code"] = 0 RET["msg"] = "欢迎登陆" RET["data"] = {"user_id": user.get("_id")} except Exception as e: RET["code"] = 1 RET["msg"] = "登陆失败" return jsonify(RET) @app.route('/reg',methods=["POST"]) def reg(): """ 注册 :return: {"code":0,"msg":"","data":""} """ try: username = request.form.get("username") password = request.form.get("password") age = request.form.get("age") nickname = request.form.get("nickname") gender = request.form.get("gender") phone = request.form.get("phone") user_info = { "username": username, "password": password, "age": age, "nickname": nickname, "gender": gender, "phone": phone } res = MONGO_DB.users.insert_one(user_info) user_id = str(res.inserted_id) RET["code"] = 0 RET["msg"] = "注册成功" RET["data"] = user_id except Exception as e: RET["code"] = 1 RET["msg"] = "注册失败" return jsonify(RET) @app.route('/user_info', methods=["POST"]) def user_info(): user_id = request.form.get("user_id") # "password": 0 表示忽略密码字段 res = MONGO_DB.users.find_one({"_id": ObjectId(user_id)}, {"password": 0}) if res: res["_id"] = str(res.get("_id")) RET["code"] = 0 RET["msg"] = "" RET["data"] = res return jsonify(res) if __name__ == '__main__': app.run("0.0.0.0", 9527, debug=True)
此时目录结构如下:
./
├── audio
├── audio_img
├── manager.py
├── serv
│ ├── content.py
│ └── get_file.py
├── setting.py
├── static
├── templates
└── xiaopapa.py
重启 manager.py,访问页面:
http://127.0.0.1:9527/get_image/4ed490e8-aded-4f23-8a7c-c845e48ec778.jpg
注意:后面的图片地址,从MongoDB中复制一条即可!
效果如下:
使用postman访问content_list
前端展示
修改js目录下的mui.js,增加全局变量serv_imge
window.serv = "http://192.168.11.86:9527" window.serv_imge = window.serv+"/get_image/"; window.styles = { top: "0px", bottom: "50px" }; ...
修改mian.html,展示图文列表
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <h1 class="mui-title">首页</h1> </header> <div class="mui-scroll-wrapper"> <div class="mui-scroll"> <div class="mui-content"> <div id="slider" class="mui-slider"> <div class="mui-slider-group mui-slider-loop"> <!-- 额外增加的一个节点(循环轮播:第一个节点是最后一张轮播) --> <div class="mui-slider-item mui-slider-item-duplicate"> <a href="#"> <img src="http://placehold.it/400x300"> </a> </div> <!-- 第一张 --> <div class="mui-slider-item"> <a href="#"> <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537023056923&di=e50b27f1a9d34e586e421b50ff5cc0b0&imgtype=0&src=http%3A%2F%2Fatt.bbs.duowan.com%2Fforum%2F201508%2F18%2F173910p7045xys71x4zfyh.jpg" width="300px" height="400px"> </a> </div> <!-- 第二张 --> <div class="mui-slider-item"> <a href="#"> <img src="http://placehold.it/400x300"> </a> </div> <!-- 第三张 --> <div class="mui-slider-item"> <a href="#"> <img src="http://placehold.it/400x300"> </a> </div> <!-- 第四张 --> <div class="mui-slider-item"> <a href="#"> <img src="http://placehold.it/400x300"> </a> </div> <!-- 额外增加的一个节点(循环轮播:最后一个节点是第一张轮播) --> <div class="mui-slider-item mui-slider-item-duplicate"> <a href="#"> <img src="http://placehold.it/400x300"> </a> </div> </div> <div class="mui-slider-indicator"> <div class="mui-indicator mui-active"></div> <div class="mui-indicator"></div> <div class="mui-indicator"></div> <div class="mui-indicator"></div> </div> </div> <ul class="mui-table-view mui-grid-view mui-grid-9"> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"> <a href="#"> <span class="mui-icon mui-icon-home"></span> <div class="mui-media-body">Home</div> </a> </li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"> <a href="#"> <span class="mui-icon mui-icon-email"><span class="mui-badge mui-badge-red">5</span></span> <div class="mui-media-body">Email</div> </a> </li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"> <a href="#"> <span class="mui-icon mui-icon-chatbubble"></span> <div class="mui-media-body">Chat</div> </a> </li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"> <a href="#"> <span class="mui-icon mui-icon-location"></span> <div class="mui-media-body">Location</div> </a> </li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"> <a href="#"> <span class="mui-icon mui-icon-search"></span> <div class="mui-media-body">Search</div> </a> </li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"> <a href="#"> <span class="mui-icon mui-icon-phone"></span> <div class="mui-media-body">Phone</div> </a> </li> </ul> <ul class="mui-table-view" id="content_list"> </ul> </div> </div> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() mui('.mui-scroll-wrapper').scroll() mui.back = function() { mui.toast("不让你点") }; mui.plusReady(function(){ // 发送post请求 mui.post( window.serv+"/content_list", //访问内容列表 {}, //参数为空 function(data){ console.log(JSON.stringify(data)); // for循环列表 for (var i = 0; i < data.data.length; i++) { create_content(data.data[i]) // 执行自定义方法create_content } } ) }) document.addEventListener("talk",function(data){ mui.toast(data.detail.talk); }) function create_content(content){ // 创建内容标签 var litag = document.createElement("li"); // 创建li标签 litag.className = "mui-table-view-cell mui-media"; //添加class var atag = document.createElement("a"); // 创建a标签 atag.id = content._id; // 设置id var imgtag = document.createElement("img"); imgtag.className = "mui-media-object mui-pull-left"; imgtag.src = window.serv_imge + content.avatar; //拼接图片路径 //console.log(window.serv_imge + content.avatar); var divtag = document.createElement("div"); divtag.className = "mui-media-body"; divtag.innerText = content.title; var ptag = document.createElement("p"); ptag.className = "mui-ellipsis"; ptag.innerText = content.intro; litag.appendChild(atag); //追加到li标签中 atag.appendChild(imgtag); atag.appendChild(divtag); divtag.appendChild(ptag); document.getElementById("content_list").appendChild(litag); //将li标签添加到ul中 } </script> </html>
使用模拟器访问,效果如下:
总结:
mui的全局变量: plus.storage.setItem("key",value) # 设置storage全局变量 plus.storage.getItem("key") # 获取全局的storage 获取到了就是 value 获取不到就是 Null plus.storage.removeItem("key") # 清除storage 2018年9月14日 故事(创业故事)北京xxxxxx科技有限公司: 背景人物: 1.老张:40+ 籍贯是黑龙江哈尔滨 长子5 小女3 在北京闯荡 长期与妻小分离 不想让孩子用手机 突然想孩子了 思考了一问题:留守儿童,玩具,让玩具成为孩子伙伴帮助孩子成长,让玩具成为与父母沟通的桥梁(语音消息) 如果能让玩具成为一个朋友圈的沟通工具(社交圈) 销售 2.老李:37 懂一点技术的产品 陕西西安 1女4岁 在北京闯荡 也是留守了自己的女儿 接到老张的电话 需要技术实现 3.闫帅:29 技术 做了8年 与老李是同事,一拍即合 接到老李电话 故事剧情: 老张从打电话到公司组建,3天 老李,设计产品 闫帅介入,涉及到硬件,需要请硬件老师,技术团队 公司成立了: 1.行政人力财务综合部 1个人 2.产品部:老李 + UI 小姐姐 2个人 3.软件部:闫帅 + 1前端 + 后端 3个人 4.硬件部:江老师 + 硬件工程师(0人) 1个人 5.营销部:老张 + 1 运营 项目的需求明确: 留守儿童,玩具,让玩具成为孩子伙伴帮助孩子成长,让玩具成为与父母沟通的桥梁(语音消息) 如果能让玩具成为一个朋友圈的沟通工具(社交圈) 1.玩具:硬件方案-------(皮毛175,硬件135 ,人工硬件组装15,皮毛人工60,运费:12,包装35)499 20 2.让玩具成为孩子伙伴 3.帮助孩子成长 4.让玩具成为与父母沟通的桥梁(语音消息) 5.如果能让玩具成为一个朋友圈的沟通工具(社交圈) 闫帅与老李沟通商讨如何开发,闫帅开始部署后端 1. 1前端 + 闫帅 + 你 先做后端在做前端 全栈工程师 2. 因为闫帅的疏忽严厉批评一次前端,前端离职了,你吹牛逼说前端两周可以学完成 3. 你 成为了公司的产品的大拿 4个月 8个月 人工智能: 150万上下,不足以支撑底层开发的 第三方:百度ai,太烂了,花钱买了科大讯飞的服务 倒闭: 硬件方案(成本过高) 管理问题 资金链,不足以支撑更换硬件方案了 展开软件部需求: 2.让玩具成为孩子伙伴 (聊天沟通,内容点播) 3.帮助孩子成长 (智能百科) 4.让玩具成为与父母沟通的桥梁(语音消息,websocket ,IM通讯,自然语言处理) 5.如果能让玩具成为一个朋友圈的沟通工具(社交圈)(C端与C端的交流) 6.app:能与玩具对话 + 内容 + 遥控 + 控制玩具的通讯录 孩子说话 --- 百度语音识别 + 自然语言处理 孩子说话点歌 --- 自然语言处理 + 内容采集 + 内容名称理解 聊天沟通 --- 图灵 智能百科 --- 图灵 语音消息 --- websocket(语音通讯)+ IM通讯 c端交流 --- 玩具主动发起消息 ,自然语言处理 开发者日志:(开发者笔记) 1.基于百度ai 和 tuling123 智能对话 2.基于websocket实现了智能语音对话 2018年9月14日: 项目第一天: 1.内容采集: 采坑:XMLY采集内容层层加密 分享按钮得到了一个M端的地址,从这个地址中获得到了 "http://m.ximalaya.com/tracks/id.json" 获取音频的所有参数 基于 request / MongoDB 实现数据采集和存储 2.App显示内容图文列表: 定义了两个接口 get_image content_list 接口/get_image 获取图片 接口/content_list 获取内容列表 app: create_content(content) 用来创建 图文列表元素的 坑: 点击事件还未完成 3.注册 登陆 自动登陆 退出 项目名称:智能玩具 项目阐述:需求+完成结果 项目技术: 1.基于baidu-aip 和 tuling123 智能对话 2.基于websocket实现了智能语音对话 3.基于requests实现内容采集
完整代码,请参考链接:
https://github.com/987334176/Intelligent_toy/archive/v1.0.zip
五、MongoDB数据导入导出
主要有2种导出格式,分别为CSV和JSON。推荐使用JSON,为什么呢?
因为导出为csv时,第一行,就是表的字段名,从第二行开始,才是真正的数据!假设数据有5行
因此,导入时,会有6行。那么多出的一行,就是字段名。这显然不是我们想要的!这个问题,暂时无法解决!
所以推荐使用JSON
导出
mongoexport -d 本地数据库名 -c 本地数据表 --type=json -f 表字段(用逗号隔开) -o 本地存储路径
比如:导出bananabase库中的sources中的字段_id,title,nickname,avatar,audio,intro,category,play_count 到sources.json中:
注意:必须要指定字段,否则报错!
mongoexport -d bananabase -c sources --type=json -f _id,title,nickname,avatar,audio,intro,category,play_count -o ./sources.json
导入
将本地json文件导入到本地数据库
mongoimport --db 本地数据库名 --collection 本地数据表 --file ~/table.json --type json
比如:假设sources表被删除了,使用sources.json来恢复
mongoimport --db bananabase --collection sources --file sources.json --type json