新工作正式开始了2天,由于客户暂时还没交接数据过来,暂时无事可做。恰逢政佬给某超市做的商品图像识别的项目客户催收了,老板要求赶紧搞个API,于是我就想我来试试吧。

说起API,我其实是一窍不通的,我对API的印象还停留在函数调包传参数,或者是用秘钥从网站提供的服务接数据这种层面。这两种好像都不太适合我们这项目。我想了一下这个项目的应用流程大致是这样的:

用户通过某个网页上传他们要识别的图片,图片被传送到服务器上,通过服务器上的算法进行识别,识别出来的结果再返回到网页上显示给用户。

如此这般,那我岂不是还要写网页啥的……完全不会啊,是不是还要发布一个客户端的网页?还要在服务器上部署算法……瞬间我就懵圈了。此时正好同事在讨论flask,我想flask的葫芦应该还是挺多的,要不就想办法依着画个瓢吧。

又是一阵搜索之后,我发现帮助最大的还是flask的文档。http://docs.jinkan.org/docs/flask/quickstart.html 里面的例子非常实用,很快我就在“文件上传”那一节的例子中找到了现成的上传图片的方法。稍作修改如下:

 import os
from flask import Flask, request, render_template, redirect, url_for
from werkzeug import secure_filename
from datetime import datetime # 导入必要的库,datetime用来加时间戳
import pandas as pd UPLOAD_FOLDER = './api/uploads' # 设置服务器上存放图片的路径
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) # 限定上传文件的类型 app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER # 绑定路径
app.config['MAX_CONTENT_LENGTH'] = 16 * 4096 * 4096 # 限定上传文件的最大尺寸,16M,像素为4096×4096 def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS # 检查文件是否满足之前设定的限定类型 @app.route('/', methods=['GET', 'POST']) # 设定上传图片的页面
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = datetime.now().strftime("%Y%m%d%H%M%S") + secure_filename(file.filename) # 在文件名之前加上时间戳,以保证不出现同名文件
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) # 保存用户上传的文件 return redirect(url_for('get_results')) # 调用get_results这个函数,返回一个重定向的页面
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form action="" method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
''' # 这部分是个HTML的代码,用于上传图片 @app.route('/results/')
def get_results(): rd = pd.DataFrame()
rd['name'] = ['hehe', 'haha', 'oops']
rd['pct'] = [85, 75, 98]
rd['notes'] = ['!!!', '***', '???'] # 一个测试用的DataFrame
test_res = rd.to_dict('records') # 将DataFrame转换成字典 return render_template('res.html', res=test_res) # 渲染模板,传入参数test_res if __name__ == "__main__":
# 将host设置为0.0.0.0,端口8383,则外网用户也可以访问到这个服务
app.run(host="0.0.0.0", port=8383, debug=True)

稍微解释一下其中的几个步骤:

首先,通过这坨代码,我们现在的流程变成了:

在代码中,我们通过变量ALLOWED_EXTENSIONS限制上传的文件类型,上传图片就是上述格式,上传视频可以是avi、mp4,上传文本则是txt等。我们再通过函数allowed_file来判断用户上传的文件是否符合格式要求,如果符合,函数返回True,否则返回False。这样一来,如果用户上传了不符合要求的文件就不会有任何反应。

app是我们的一个Flask实例,我们通过app.config['XXX']这样的语句来设定app的一些参数,比如上传文件的最大尺寸,上传后文件保存的路径等等。此后我们通过装饰器@app.route('XXX‘)来设定每个函数对应的网页路径。比如,在这个例子中,upload_file函数前面的装饰器是@app.route('/', methods=['GET', 'POST']),则代表upload_file对应的页面就是http://127.0.0.1:8383/ (本地情况下,ip为127.0.0.1)。而get_results函数前面的装饰器是@app.route('/results/'),也就表示get_results的显示页面为http://127.0.0.1:8383/results/ 。

至于文件名的路径,secure_filename函数用来保证文件名路径的安全性,以免给服务器造成意外损害。此外,我们在文件名之前还加了个时间戳,这主要是考虑到如果用户上传了多个同名文件的话会出现互相覆盖的情况,有了时间戳就可以区分开来。

上传图片完成后,我们将页面重定向到get_results所对应的页面,这里我们写了个模板res.html。你可以把模板当成是一张需要你填写的表,别人把表格的样式什么的全弄好了,你只要把数据填进相应的位置(也就是传入参数)就可以了。模板内容如下:

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Results</title>
</head>
<ul>
{% for sth in res %}
<li>{% for iid, val in sth.items() %}
<a href="#">{{ iid }}:{{ val }}</a>
{% endfor %}</li>
{% endfor %}
</ul>
</html>

模板写了两层循环,第一层从列表res中取元素,res中的每个元素是一个字典;第二层是从字典中取信息,表现在网页上。

最后,我们将python文件随便起个名字,比如upload.py,我们在相同的目录下新建一个templates文件夹,然后把我们的模板res.html放在那个文件夹下。然后我们打开命令行,通过一下命令启动

python upload.py

顺利的话我们会看到如下文字:

随后我们打开浏览器,因为我们是在本机运行,所以ip地址就是127.0.0.1,端口按照python代码中设置的是8383,因此我们在地址栏输入http://127.0.0.1:8383/,出现如下页面:

我们随便选一张图片之后,按下Upload按钮,就会跳转到页面http://127.0.0.1:8383/results/

这个就是我们DataFrame中设定的内容。

这么看来一切都很顺利了!但问题又来了,由于upload_file和get_results这两个函数是分开的,按照我们之前的流程,算法从upload_file读取图片并运行之后,要将结果传入get_results,这之中就有传参的问题。而我毕竟是野路子码农,怎么都没法解决传参的问题,总是报错。于是政佬说我们把算法的结果写成csv导出吧,在另一个函数中在读取csv文件。好吧,那还有没有别的曲线救国的办法?

当然有啦!我们其实可以直接放弃重定向到get_results这一步,把最终要渲染的HTML模板直接也写在upload_file函数里。

 import os
from flask import Flask, request, render_template_string # 注意,这次我们导入了render_template_string
from werkzeug import secure_filename
from datetime import datetime
import pandas as pd UPLOAD_FOLDER = './api/uploads'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 4096 * 4096 def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS @app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = datetime.now().strftime("%Y%m%d%H%M%S") + secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) # add your function here! rd = pd.DataFrame()
rd['name'] = ['hehe', 'haha', 'oops']
rd['pct'] = [filename[8:10], filename[10:12], filename[12:14]]
rd['notes'] = ['!!!', '***', '???'] # 一个测试用的DataFrame rd = rd.to_dict('records') # 将DataFrame转换成字典 return render_template_string('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Results</title>
</head>
<ul>
{% for sth in res %}
<li>{% for iid, val in sth.items() %}
<a href="#">{{ iid }}:{{ val }}</a>
{% endfor %}</li>
{% endfor %}
</ul>
<input type="button" name="Submit" value="Return" onclick ="location.href='/'"/>
</html>
''', res = rd) # 我们把原来的模板res.html直接写在这里了,把rd传入模板 return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form action="" method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
''' if __name__ == "__main__":
# 将host设置为0.0.0.0,则外网用户也可以访问到这个服务
app.run(host="0.0.0.0", port=8383, debug=True)

这样一来我们把两个函数合并成了一个函数,也就不存在了传参的问题,政佬可以在函数里直接调用他的算法,而返回的结果则通过render_template_string渲染模板并传入参数,直接得到了最终的页面。

我们还在模板中加了一句:

<input type="button" name="Submit" value="Return" onclick ="location.href='/'"/>

这句语句就是添加一个Return按钮,按下之后链接的位置是'/',也就是http://127.0.0.1:8383/。我们运行一下来看看最终效果:

上传界面别无二致:

我们注意一下跳转后的界面,由于现在没有重定向了,所以网址没有任何变化。我们再点击Return就又回到了上传界面。政佬在AWS上部署之后,把服务器的ip发给用户,用户通过这个ip和8383端口就能接入这个服务了,这下就大功告成啦!

通过这次折腾,我也算是从0开始学习了一下Web API的创建过程,可以说相当有成就感了。不过这个简陋的玩意儿还有挺多问题的,比如上传.JPG(大写)就会识别不出(filename没加.lower(),弱智错误……);再比如只能处理一个请求,不能并发,这也是之后需要解决的问题。

所以继续再接再厉吧,野路子码农!

野路子码农系列(1) 创建Web API的更多相关文章

  1. 野路子码农系列(2)Python中的类,可能是最通俗的解说

    啥叫佩奇?啥叫类?啥叫面向对象?后面两个问题以前在大学里“祖传谭浩强”的时候我经常会有所疑问.老师说着一堆什么public, private,我都是一脸懵逼,啥叫私有?为啥要私有?然后就神游天外了…… ...

  2. 野路子码农系列(8)我终于大致搞懂了GBDT

    由于下下周要在组里介绍一个算法,最近开始提前准备,当初非常自信地写下自己最喜欢的GBDT,但随着逐步深入,发现其实自己对这个算法的细节并不是非常了解,了解的只是一些面试题的答案而已……(既然没有深入了 ...

  3. 野路子码农系列(3)plotly可视化的简单套路

    又双叒叕要跟客户汇报了,图都准备好了吗?matplotlib出图嫌丑?那用用plotly吧,让你的图看上去经费爆炸~ P1 起因 第一次接触plotly这个库是在我们做的一个列车信号数据挖掘的项目里, ...

  4. 野路子码农(5)Python中的装饰器,可能是最通俗的解说

    装饰器这个名词一听就充满了高级感,而且很多情况下确实也不常用.但装饰器有装饰器的好处,至少了解这个对装逼还是颇有益处的.网上有很多关于装饰器的解说,但通常都太过“循序渐进”,有的还会讲一些“闭包”之类 ...

  5. ASP.NET 5系列教程 (六): 在 MVC6 中创建 Web API

    ASP.NET 5.0 的主要目标之一是统一MVC 和 Web API 框架应用. 接下来几篇文章中您会了解以下内容: ASP.NET MVC 6 中创建简单的web API. 如何从空的项目模板中启 ...

  6. 【ASP.NET Web API教程】2.4 创建Web API的帮助页面

    原文:[ASP.NET Web API教程]2.4 创建Web API的帮助页面 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. 2.4 ...

  7. 在 MVC6 中创建 Web API

    ASP.NET 5系列教程 (六): 在 MVC6 中创建 Web API   ASP.NET 5.0 的主要目标之一是统一MVC 和 Web API 框架应用. 接下来几篇文章中您会了解以下内容: ...

  8. 使用 ASP.NET Core MVC 创建 Web API(一)

    从今天开始来学习如何在 ASP.NET Core 中构建 Web API 以及每项功能的最佳适用场景.关于此次示例的数据库创建请参考<学习ASP.NET Core Razor 编程系列一> ...

  9. 【ASP.NET Web API教程】2.4 创建Web API的帮助页面[转]

    注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. 2.4 Creating a Help Page for a Web API2.4 创建W ...

随机推荐

  1. 报表平台对CRM系统价值几何

    CRM系统即客户关系管理系统,其利用信息科学技术实现市场营销.销售.服务等活动自动化,使企业能高效地为客户提供周到的服务,以提升客户满意度与忠诚度为目的的一种管理经营方式.而CRM报表平台作为一个枢纽 ...

  2. 44.Odoo产品分析 (五) – 定制板块(1) – 管理odoo安装(1)

    查看Odoo产品分析系列--目录 1 管理员的注意事项 在记录重要的配置细节时必须要小心,而且必须要有一个连续性的合适的.让系统能够安装备份并运行在一个可接受的时间内的计划. 1.1 制定实施策略 如 ...

  3. Your project path contains non-ASCII characters

    Android studio导入project时报错 non-ASCII characters意味着中文字符报错,解决方法简单有效: 检查项目路径中是否出现中文名,将中文字符修改成英文就可以解决辽~

  4. 高通MSM8998 ABL的调试

    高通在MSM8998上引入了UEFI,用来代替LK(Little Kernel).高通UEFI由XBL和ABL两部分组成.XBL负责芯片驱动及充电等核心应用功能.ABL包括芯片无关的应用如fastbo ...

  5. 斐波那契数列(C#)

    斐波那契数,亦称之为斐波那契数列(意大利语: Successione di Fibonacci),又称黄金分割数列.费波那西数列.费波拿契数.费氏数列,指的是这样一个数列:1.1.2.3.5.8.13 ...

  6. 中国移动能力开放商店OneNET View数据可视化公测 10分钟轻便生成行业可视化界面

    随着云计算,5G技术,人工智能等底层技术的发展,万物互联时代已经到来,同时带来了海量数据,如何效果好.低成本.短时间的表现据,成为物联网行业从业者和公司的当务之急. OneNET View传统的数据展 ...

  7. Extjs renderer函数

    昨天提到了改变grid中 行的背景颜色,其实还有一个更简单的方法,就是利用renderer:function(){}函数 renderer 函数是一个拦截者模式,用于改变渲染到单元格的值和样式. re ...

  8. 初窥css---包含一些概念和一些文字样式

    初窥css CSS相关概念 全称是层叠式样式表.规定了html在网页上的显示样式.我们都知道css主要是负责装饰页面的,但是其实在HTML4之前,网页的样式与架构全部都是写在一起的,也是在HTML4之 ...

  9. Mysql原理与优化

    原文:https://mp.weixin.qq.com/s__biz=MzI4NTA1MDEwNg==&mid=2650763421&idx=1&sn=2515421f09c1 ...

  10. Docker平台的基本使用方法

    1.运行一个 container并加载镜像centos,运行起来这个实例后,在实例中执行 /bin/bash命令 docker常用参数: run  运行 -i  以交互模式运行容器,通常与 -t 同时 ...