用一个Flask的小例子来聊聊架构和工具选择
很久没写了,本来打算就“谢邀”,写点爬虫的内容,但似乎这块比较敏感,就不单独开篇写了,以后写其他内容或群里遇到讨论,就瞎聊两句算了。而这篇,也是源自群友一个偶然的“谢邀”(调百度接口,指定用Flask做web服务器做个展示),于是趁周日有空,瞎写一篇,顺便聊聊“架构”、“工具选择”,以及学习方向和学习成本的问题。
首先,直接上作业题,给代码,然后再聊废话。
作业题:调用一个免费外部接口,NLP或图像方面的都行,然后用Flask做一个服务器,语言当然是用python,把接口返回的东西展示出来。
需求给的很明确,那咱就挑个简单的,聊天机器人呗,百度、黄小鸭、图灵都有接口,当然是选“大厂”百度了。如果没有百度账号,就注册一个,进百度AI平台:https://ai.baidu.com/,登录好账号之后,在控制台界面,寻找并点击这个红框里面的东西:智能对话定制与服务平台UNIT。
创建应用,拿到API Key和Secret Key:
点击上面图片中的“UNIT配置平台”,并点击下个页面中的“进入UNIT”
然后新建一个机器人,记住ID号:
点击刚刚新建的机器人,在技能管理里面,添加技能,在列表中选取免费的(大部分免费),然后绑定给机器人
之前已经绑定了2个技能了,现在新增一个“电影”技能
选择技能,添加给机器人,实现绑定
技能列表中已经出现新技能,电影,这些技能的ID号要记住,调用接口的时候要用。
准备工作做完了,现在进入写代码环节,一个Flask主程序作为后端,一个前端(使用Flask官方定义的目录结构,放置静态页面和JS脚本,以及CSS样式)。
数据流程是这样的:
1、用JS的ajax技术,发请求(本例请求中主要是对话内容)给Flask后端;
2、再由Flask后端调用百度的API;
3、Flask后端收到百度API的返回数据后,返回给前端的ajax;
4、再由ajax渲染到静态页面上。
这是一个数据流程。N轮及多用户对话,就是重复这个流程。
整个项目的文件结构如下:
jquery-3.5.1.min.js需要自己下载,网上很多资源,下载好了直接用就行,本例用它是因为要用ajax提交请求,否则用form提交的话,稍微有点low,实在拿不出手……,放在static/JS目录下
sytle.css放在static/CSS目录下,文件内容如下:
@charset "utf-8";
body { margin:0; padding:0; width:100%;}
html { padding:0; margin:0;}.shadow-box {width: 100%;height: 100%;position: fixed;top: 0;left: 0;background: rgba(0,0,0,0.3);z-index: 9999;}
.shadow {position: absolute;top: 50%;left: 50%;margin: -380px 0 0 -400px;}
.shadow-bg {width: 800px;height: 800px;position: absolute;background: #ffffff;border-radius: 5px;padding: 5px 5px 5px 5px;}
.shadow-bg .echart_bar{position: relative;border: 1px solid #CCC;margin: 10px 0 0 10px;padding-top: 0px;}
.shadow-bg .echart_block_1{position: relative;border: 1px solid #CCC;margin: 10px 0 0 10px;padding-top: 0px;float: left;width: 286px;height:440px;}
.shadow-bg .echart_block_1 ul { text-align:right; padding:0; margin:0; list-style:none; border:0;}
.shadow-bg .echart_block_1 ul li { float:left; margin:0; padding:0 5px; border:0; height:30px;font-size:12px;}
.shadow-bg .echart_block_2{position: relative;border: 1px solid #CCC;margin: 10px 0 0 10px;padding-top: 0px;float: left;width: 784px;height:300px;}
.shadow-bg .title{float: left;width: 800px;height: 30px;line-height: 30px;position: relative;}.shadow-assis {position: absolute;top: 50%;left: 50%;margin: -180px 0 0 -200px;}
.shadow-bg-assis {width: 400px;height: 360px;position: absolute;background: #ffffff;border-radius: 5px;padding: 5px 5px 5px 5px;}
.shadow-bg-assis .title{float: left;width: 400px;height: 30px;line-height: 30px;position: relative;}
/* .assis-box {position: relative;width: 300px;height: 310px;margin: 10px 0 0 10px;border: 1px solid cornflowerblue;padding-top: 20px;} */
/* .shadow-bg-assis .assis-box {position: relative;width: 300px;height: 310px;border: 1px solid #CCC;margin: 10px 0 0 10px;padding-top: 0px;} */
.shadow-bg-assis .assis-box01 {width: 380px;height: 250px;overflow: auto;margin: 10px;border: 1px solid #CECECE;padding-top: 5px;}
.shadow-bg-assis .assis-txt {width: 330px;height: 18px;margin-left: 10px;}
如果不怎么讲究的话,完全可以不用CSS,都是样式,没啥好说的
index.html文件,放置在templates目录下,内容如下:
<html><head><title>proj_1_flask</title><link rel="stylesheet" type="text/css" href="/CSS/style.css"/><script src="/JS/jquery-3.5.1.min.js"></script> </head><body><!-- 百度UNIT调用 --><div class="shadow-box" id="assistantArea" style="display: block;"><div class="shadow-assis"><div class="shadow-bg-assis" id="containerArea"><div class="title-assis"><span class="t_txt" id="assistantArea_title">百度UNIT调用</span></div><div id="box01" class="assis-box01"></div><input type="text" id="txt" class="assis-txt" placeholder="ctrl+Enter发送" /><input type="button" id="btn" value="发送" /></div></div></div><script type="text/javascript">var oBox = document.getElementById("box01");var oTxt = document.getElementById("txt");var oBtn = document.getElementById("btn");function msg() {var val = oTxt.value;// oBox.innerHTML += '<p>' + val + '</p>'// oBox.scrollTop = oBox.scrollHeight; //让滚动条一直处于底部oTxt.value=""; //发送完成侯情况输入框$.ajax({url: "/Baidu_Unit_Chat",type: "POST", dataType: "json",async: true,data: {"sender_name": 'your_name', "send_text": val},beforeSend: function(XMLHttpRequest){// Handle the beforeSend eventvar p = document.createElement("p");p.setAttribute("style", "color:#0080C1;"); p.innerHTML = 'From me:' + valoBox.appendChild(p); oBox.scrollTop = oBox.scrollHeight; //让滚动条一直处于底部console.time('global');},success: function(res){console.log(res);var str = res.text;var p = document.createElement("p");// p.setAttribute("style", "color:#0080C1;"); p.innerHTML = 'From AI:' + stroBox.appendChild(p);oBox.scrollTop = oBox.scrollHeight; //让滚动条一直处于底部console.timeEnd('global');}});}oBtn.onclick = msg;document.onkeydown = function(e) {var e = e || window.event;if (e.ctrlKey && e.keyCode == 13 ||e.keyCode ==13) {msg();}}
</script></body>
</html>
这段里面,ajax的部分稍微讲讲,对ajax不熟的人可以了解一下
红框和线条画出来的地方注意一下:
冒号前面的,是ajax内置的属性,不能改,照写上去。
url是待会提交给Flask处理的地址
type是post,这是百度api要求的
async: true,代表是“异步”提交请求。效果是你说的话,提交后马上显示在页面上,百度api返回的话,等它返回之后再显示在页面上。
如果是同步,效果就是,你要等百度返回值之后,把你说的话和百度返回的话,一起显示出来。
data就是你提交的一个字典结构,可以定义你想定义的所有内容进去,这里只定义2个作为示例,而且主要是send_text有用,send_name提交后并未做处理,就直接返回了,如果要做用户管理和鉴权,需要用到这个,可以自己扩充一下。
beforeSend和async: true配合,就是上面说的那个效果。
success是返回后发生的事,参数res就是Flask处理后,返回的内容。
最后,就是Flask了,这里的文件是Flask_Main.py,代码如下:
# coding:utf-8import requests
import json
from flask import Flask, render_template, request
app = Flask(__name__, static_url_path='')class UNIT:def __init__(self, api_key, api_secret):self.access_token = Noneself.url = Noneself.set_access_token(api_key, api_secret)def set_access_token(self, api_key, api_secret):host = 'https://aip.baidubce.com/oauth/2.0/token?' \'grant_type=client_credentials&' \'client_id={0}&' \'client_secret={1}'.format(api_key, api_secret)response = requests.post(host)if response:self.access_token = response.json()['access_token']def query(self, query_text):self.url = 'https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=' + self.access_token'''提交字段说明:详情见百度AI开发中心UNIT文档中的【请求参数详细说明】"log_id": "UNITTEST_10000", # 随便写"version": "2.0", # 必须写2.0"service_id": "Sxxxx", # 填你自己百度AI平台申请UNIT的机器人id"skill_ids": ["1076657", "1076658"], # 申请机器人的技能,把技能id写这里,这两个技能是尬聊和问候,需要其他技能自己去百度拿,大部分免费"session_id": "", # ssession保存机器人的历史会话信息,由机器人创建,客户端从上轮应答中取出并直接传递,不需要了解其内容。如果为空,则表示清空session"request": {"query": "%s", # 你的输入"user_id": "88888" # 与技能对话的用户id},"dialog_state": {"contexts": {"SYS_REMEMBERED_SKILLS": ["1076657"]} # dialog_state.contexts["SYS_REMEMBERED_SKILLS"]中设定一个技能ID列表,避免处于列表中的技能session被清空,就可以基于历史会话信息与机器人进行多轮对话,而无需接触复杂的原始session。}'''post_data = """{"log_id": "UNITTEST_10000","version": "2.0","service_id": "Sxxxx","skill_ids": ["1076657", "1076658"],"session_id": "","request": {"query": "%s","user_id": "88888"},"dialog_state": {"contexts": {"SYS_REMEMBERED_SKILLS": ["1076657"]}}}""" % query_textpost_data = post_data.encode('utf-8')headers = {'content-type': 'application/x-www-form-urlencoded'}response = requests.post(self.url, data=post_data, headers=headers)print(response.json())if response:return response.json()['result']['response_list'][0]['action_list'][0]['say']unit = UNIT('your_api_key', 'your_api_secret')@app.route('/')
def index():return render_template('index.html')@app.route('/Baidu_Unit_Chat', methods=['post'])
def ai_talk():rst_dict = {}send_text = request.form.get('send_text')sender_name = request.form.get('sender_name')out_str = unit.query(send_text) # 调用api的返回值if out_str is None:rst_dict['status'] = 'failure'else:rst_dict['status'] = 'success'rst_dict['from_whom'] = sender_namerst_dict['text'] = out_strai_say = json.dumps(rst_dict)return ai_sayif __name__ == '__main__':app.run(debug=True)
注释都写得很清楚了,需要找百度拿的就是下面几个值
类UNIT内的函数:
your_api_key和your_api_secret,文章开头有怎么拿的方法
函数set_access_token在UNIT类实例化的时候执行,根据上面两个值,生成token,这个token下面要用,这个token值用函数获取,是实时变化的。
函数query的参数query_text,就是前端ajax送过来的对话信息
类UNIT在Flask运行之初即实例化加载,然后提供持久服务。
类UNIT外的函数:
index函数不用说了
ai_talk函数,处理前端提交到/Baidu_Unit_Chat的请求,本例中,是和ajax配合工作。该函数中send_text = request.form.get('send_text')获得ajax送过来的对话信息,交给unit.query(send_text)处理,并赋值给out_str,返回的字典经过json打包后,发送给前端ajax,并由ajax解析后渲染到页面上。
至此,完成一个数据流程。(对应看文章前半部分关于流程的描述,观察程序实现的过程)。
最后效果放一个,用图片形式偷偷说下“爬虫”,悟一下……
因为加了电影技能,试了下,然后……百度api的效果怎样,就不讨论了……
Flask上的提示
至此,正文就完了,下面开始讲废话:
简单说说这个nlp的制作原理,我也做过内核部分,流程类似,算是番外篇吧,有兴趣就了解一下:
1、设置一些列场景,就是百度定义的“技能”,在每个场景中,找打量相关语料,进行训练,每个场景一个对话模型
2、对输入的文本,先进行场景判断,场景的数量是有限的,用文本分类做模型,判断输入文本属于哪个场景,就调用哪个场景的对话模型
3、多轮对话方面,设置session保存,同一个用户,保存前几轮的对话内容,以便判断聊天主题需要转换,也可以提供同一用户在停止聊天后,保持上一轮的语境,以便下次聊天时迅速切入,或判断是否需要转换话题
4、以上是用模型进行的选择和对话,我称之为“软切换”
5、设计“词槽”,做关键字匹配,我称之为“硬切换”,这个可以迅速提升效果,缺点是,太low……
6、还有其他很多办法,各自开脑洞
……
差不多就这样
1、Flask是不是一个好web服务器?
是个好web服务器,但由于和python绑定太深,缺点也很多,比如如果一个项目是由多人合作完成,项目组内有人用java,有人用jsp,有人用C/C++,Flask就不是一个好的选择了,而且速度方面,一直是个问题。当然,有人觉得,对于学习来说,无所谓,可以用就行。嗯,学习阶段是可以用,但熟练以后,我相信自然就会放弃Flask,至少是主框架不会再用Flask。
2、前面我重点介绍的其实是数据流这个问题,无论是对这种小demo,还是非常大的项目,都要习惯用数据流来思考架构问题,把复杂的问题,拆解成简单问题,然后每个问题,用最适合的工具去处理,最后就形成了所谓的架构。
3、在选用工具方面,要尽量的选择通用性较强的工具,学习新工具,也要学习通用性强的工具,长期坚持下去,自己的工具箱才会强大,才会在处理问题的时候,信手拈来,搭建靠谱的架构。因为,无论是通用性强还是弱,对于工程问题来说,学习成本差别不会太大。
废话也讲完了,打完收工。
用一个Flask的小例子来聊聊架构和工具选择相关推荐
- Java基础。public,private,static变量!以及一个实例化的小例子 以及方法
public,适用范围最广! private,仅仅方法内部可以使用!创建出来的对象也是不可以使用的! 代码: public class mmm12333 {public int a = 1111;pr ...
- 决策树分析例题经典案例_决策树原理及一个简单的小例子
首先通过两个图来引入什么是决策树. 是否学习的决策过程 决策树是仿树结构来进行决策的,例如上图来说,我们要对'是否学习'这个问题进行决策时,通常伴随一系列的子决策.先看是否有'对象',有的话是否需要' ...
- 模拟一个火车站售票小例子
1.车票 属性:起始站.终点站.票价 2.系统12306 属性:集合 方法 3.窗口window 多线程 package system12306;/*** @author liuxian*/ publ ...
- 一个有趣的小例子,带你入门协程模块-asyncio
上篇文章写了关于yield from的用法,简单的了解异步模式,[上次的内容链接]这次让我们通过一个有趣例子带大家了解asyncio基本使用. 目标效果图 在控制台中显示一个由ASCII字符" ...
- Hadoop中RPC协议小例子报错java.lang.reflect.UndeclaredThrowableException解决方法
最近在学习传智播客吴超老师的Hadoop视频,里面他在讲解RPC通信原理的过程中给了一个RPC的小例子,但是自己编写的过程中遇到一个小错误,整理如下: log4j:WARN No appenders ...
- win32 实现死锁的小例子
死锁的一种情况是两个线程竞争两个锁,需要同时拿到两个锁才能执行,然后出现了两个线程各拿一个锁的情况,这样两个线程就都无法继续执行,称为死锁. 避免死锁的方法有很多,有预防死锁,出现死锁后通过某些方法释 ...
- JointJS简单小例子
JointJS是一个HTML5的JavaScript库,用于创建完全互动式的图表,它极易上手且操作简单,并且支持所有的现代浏览器,对于时间紧迫的我们非常有利.我们可以使用JointJS已提供的图元素绘 ...
- 简单的 Java 导出 Excel 表格 小例子《一抹茶CSDN》
Java 导出 Excel 为什么要有导出Excel表格的功能呢? 因为我们在使用软件时会有,一些数据需要导出来,进行留存,大多数人使用的都是office的办公软件,就会使用常用的Excel表格.因此 ...
- OOM问题小例子及思考
目录 一.背景介绍 二.思路&方案 三.过程 四.总结 五.升华 一.背景介绍 前段时间朋友新进入一个公司,遇到线上系统cpu飙高,内存占满情况:和他交流过后有了本篇总结,该博文会以一个简单的 ...
最新文章
- python笔记基础-python学习笔记之基础一(第一天)
- Bitbucket Pipelines在Atlassian的Bitbucket云上提供持续交付功能
- 【NLP-语义匹配】详解深度语义匹配模型DSSM
- Windows7 IIS7.5 HTTP Error 503 The service is unavailable 另类解决方案
- MySQL 8.0 数据字典有哪些变化?
- lintcode 734. 形式为a^i b^j c^k的子序列数量 题解
- jetpack的camerax_Android开发-Jetpack组件CameraX
- 从零开始的全栈工程师——underscore
- 2019 蓝桥杯省赛 B 组模拟赛(一) J. 程序设计:蒜厂年会 环形连续子序列求和问题
- 解放双手,基于github travis-ci docker自动化部署java项目
- 『TensorFlow』批处理类
- React-Native Fetch使用Promise封装(一)
- marvell raid linux,华硕P7F-M (-MARVELL 88SE6145 SATA RAID)主板驱动-版下载,适用于win7,Win7-64,winxp-驱动精灵...
- pmp 第六版 模拟卷1疑难问题
- Javashop 支持全业务模式电商系统
- STM32单片机PT100温度采集控制系统
- 给自定义Dialog加入保留对话框值的功能
- java espresso车架,只爱钢架公路:Casati Espresso
- NR PRACH (七)Type 2(2-step) RA 参数及相关规定
- Geoserver 发布wmts服务,以及cesium加载发布的wmts服务