热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

野路子码农系列(1)创建WebAPI

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

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

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

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

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

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

 1 import os
 2 from flask import Flask, request, render_template, redirect, url_for
 3 from werkzeug import secure_filename
 4 from datetime import datetime # 导入必要的库,datetime用来加时间戳
 5 import pandas as pd
 6 
 7 UPLOAD_FOLDER = './api/uploads' # 设置服务器上存放图片的路径
 8 ALLOWED_EXTENSIOnS= set(['png', 'jpg', 'jpeg', 'gif']) # 限定上传文件的类型
 9 
10 app = Flask(__name__)
11 app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER # 绑定路径
12 app.config['MAX_CONTENT_LENGTH'] = 16 * 4096 * 4096 # 限定上传文件的最大尺寸,16M,像素为4096×4096
13 
14 def allowed_file(filename):
15     return '.' in filename and \
16            filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS # 检查文件是否满足之前设定的限定类型
17 
18 @app.route('/', methods=['GET', 'POST']) # 设定上传图片的页面
19 def upload_file():
20     if request.method == 'POST':
21         file = request.files['file']
22         if file and allowed_file(file.filename):
23             filename = datetime.now().strftime("%Y%m%d%H%M%S") + secure_filename(file.filename) # 在文件名之前加上时间戳,以保证不出现同名文件
24             file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) # 保存用户上传的文件
25             
26             return redirect(url_for('get_results')) # 调用get_results这个函数,返回一个重定向的页面
27     return '''
28     
29     
30     

Upload new File

31 32

33 34 35 ''' # 这部分是个HTML的代码,用于上传图片 36 37 @app.route('/results/') 38 def get_results(): 39 40 rd = pd.DataFrame() 41 rd['name'] = ['hehe', 'haha', 'oops'] 42 rd['pct'] = [85, 75, 98] 43 rd['notes'] = ['!!!', '***', '???'] # 一个测试用的DataFrame 44 test_res = rd.to_dict('records') # 将DataFrame转换成字典 45 46 return render_template('res.html', res=test_res) # 渲染模板,传入参数test_res 47 48 49 if __name__ == "__main__": 50 # 将host设置为0.0.0.0,端口8383,则外网用户也可以访问到这个服务 51 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。你可以把模板当成是一张需要你填写的表,别人把表格的样式什么的全弄好了,你只要把数据填进相应的位置(也就是传入参数)就可以了。模板内容如下:

 1 DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Resultstitle>
 6 head>
 7 <ul>
 8 {% for sth in res %}
 9     <li>{% for iid, val in sth.items() %}
10     <a href="#">{{ iid }}:{{ val }}a>
11     {% endfor %}li>
12 {% endfor %}
13 ul>
14 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函数里。

 1 import os
 2 from flask import Flask, request, render_template_string  # 注意,这次我们导入了render_template_string
 3 from werkzeug import secure_filename
 4 from datetime import datetime
 5 import pandas as pd
 6 
 7 UPLOAD_FOLDER = './api/uploads'
 8 ALLOWED_EXTENSIOnS= set(['png', 'jpg', 'jpeg', 'gif'])
 9 
10 app = Flask(__name__)
11 app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
12 app.config['MAX_CONTENT_LENGTH'] = 16 * 4096 * 4096
13 
14 def allowed_file(filename):
15     return '.' in filename and \
16            filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
17 
18 @app.route('/', methods=['GET', 'POST'])
19 def upload_file():
20     if request.method == 'POST':
21         file = request.files['file']
22         if file and allowed_file(file.filename):
23             filename = datetime.now().strftime("%Y%m%d%H%M%S") + secure_filename(file.filename)
24             file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
25             
26             # add your function here!
27             
28             rd = pd.DataFrame() 
29             rd['name'] = ['hehe', 'haha', 'oops']
30             rd['pct'] = [filename[8:10], filename[10:12], filename[12:14]]
31             rd['notes'] = ['!!!', '***', '???'] # 一个测试用的DataFrame
32     
33             rd = rd.to_dict('records') # 将DataFrame转换成字典
34 
35             return render_template_string('''
36                                           
37                                           
38                                           
39                                           
40                                           
41                                           
42                                           
    43 {% for sth in res %} 44
  • {% for iid, val in sth.items() %} 45 {{ iid }}:{{ val }} 46 {% endfor %}
  • 47 {% endfor %} 48
49 50 51 ''', res = rd) # 我们把原来的模板res.html直接写在这里了,把rd传入模板 52 53 return ''' 54 55 56

Upload new File

57 58

59 60 61 ''' 62 63 if __name__ == "__main__": 64 # 将host设置为0.0.0.0,则外网用户也可以访问到这个服务 65 app.run(host="0.0.0.0", port=8383, debug=True)

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

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

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

上传界面别无二致:

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

 

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

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

 


推荐阅读
  • 利用Flask框架进行高效Web应用开发
    本文探讨了如何利用Flask框架高效开发Web应用,以满足特定业务需求。具体案例中,一家餐厅希望每天推出不同的特色菜,并通过网站向顾客展示当天的特色菜。此外,还增加了一个介绍页面,在bios路径下详细展示了餐厅主人、厨师和服务员的背景和简介。通过Flask框架的灵活配置和简洁代码,实现了这一功能,提升了用户体验和餐厅的管理水平。 ... [详细]
  • 本文介绍了UUID(通用唯一标识符)的概念及其在JavaScript中生成Java兼容UUID的代码实现与优化技巧。UUID是一个128位的唯一标识符,广泛应用于分布式系统中以确保唯一性。文章详细探讨了如何利用JavaScript生成符合Java标准的UUID,并提供了多种优化方法,以提高生成效率和兼容性。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • Spring Boot 中配置全局文件上传路径并实现文件上传功能
    本文介绍如何在 Spring Boot 项目中配置全局文件上传路径,并通过读取配置项实现文件上传功能。通过这种方式,可以更好地管理和维护文件路径。 ... [详细]
  • 解决问题:1、批量读取点云las数据2、点云数据读与写出3、csf滤波分类参考:https:github.comsuyunzzzCSF论文题目ÿ ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文深入探讨了Ajax的工作机制及其在现代Web开发中的应用。Ajax作为一种异步通信技术,改变了传统的客户端与服务器直接交互的模式。通过引入Ajax,客户端与服务器之间的通信变得更加高效和灵活。文章详细分析了Ajax的核心原理,包括XMLHttpRequest对象的使用、数据传输格式(如JSON和XML)以及事件处理机制。此外,还介绍了Ajax在提升用户体验、实现动态页面更新等方面的具体应用,并讨论了其在当前Web开发中的重要性和未来发展趋势。 ... [详细]
  • 本文探讨了利用Python实现高效语音识别技术的方法。通过使用先进的语音处理库和算法,本文详细介绍了如何构建一个准确且高效的语音识别系统。提供的代码示例和实验结果展示了该方法在实际应用中的优越性能。相关文件可从以下链接下载:链接:https://pan.baidu.com/s/1RWNVHuXMQleOrEi5vig_bQ,提取码:p57s。 ... [详细]
  • javax.mail.search.BodyTerm.matchPart()方法的使用及代码示例 ... [详细]
  • 利用python爬取豆瓣电影Top250的相关信息,包括电影详情链接,图片链接,影片中文名,影片外国名,评分,评价数,概况,导演,主演,年份,地区,类别这12项内容,然后将爬取的信息写入Exce ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 近期在研究逆向工程,因此尝试了一些CTF题目。通过合天网络安全实验室的CTF实战演练平台(http://www.hetianlab.com/CTFrace.html),我对Linux逆向工程的掌握还不够深入,因此暂时跳过了RE300题目。首先从逆向100开始,将文件后缀名修改为.apk进行初步分析。这一过程不仅帮助我熟悉了基本的逆向技巧,还加深了对Android应用结构的理解。 ... [详细]
author-avatar
本人xiao13
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有