一只猿:使用flask来做一个小应用
上周 @萍姐 问我如何抓取天猫上面店铺的评分,看了下挺简单的,于是花了点时间写了个Python脚本,加上web.py做成一个web服务,使用起来还不错,今天来看的时候发现当时为了方便直接用web.py开发有点简陋,自己也好久没用flask写过东西了,打算用flask再写一遍,顺便复习下旧的知识,如果你是flask初学者,可以参考这个例子。
提示:博主默认你已经具备了Python的基础知识,已经能够很顺畅的编写一些Python脚本,否则接下来你会比较难看懂。
旧版
这里先给出旧版本的一些使用截图,初始化的时候的样子

模糊查询

精确查询

技术
这个应用比较简单,所使用的技术也比较少,主要有以下技术要点
- requests模拟请求
- 正则匹配关键字
- web.py搭建web环境
- vue.js做数据自动绑定
是不是很简单?
Flask Web开发基于Python的Web应用开发实战pdf http://www.gooln.com/document/283768.html
在这个小应用中使用web.py的时候目录结构是这样的

其中static目录里面存放的是静态资源

结构相当简单
python代码
这里给出全部的Python代码
#!/usr/bin/env python
# coding=utf-8
import requests
import json
import web
import sys
import re
reload(sys)
sys.setdefaultencoding('utf8')
urls = (
"/", "index",
"/query", "Query"
)
render = web.template.render('static', cache=False)
class index:
def GET(self):
return render.index('static')
class Query:
def POST(self):
keywords = str(web.input().get('shopname'))
url_base = "https://list.tmall.com/search_product.htm?q="+keywords
headers = {"User-Agent": "iphone7"}
try:
result_base = requests.get(url=url_base, headers=headers, timeout=15).content.replace('\n', '').replace(' ','')
infostr = re.findall(r'j_shop_moreshop_more\">(.+?)</div>', result_base)
shoplist = []
for item in infostr:
scorelist = re.findall(r'\">(.+?)</span><iclass=\"', item)
thisShopname = re.findall(r'<span>(.+?)</span>', item)[0]
shoplist.append('{"shopname": "'+ thisShopname +'" , "dsr": "'+scorelist[0]+'", "service": "'+scorelist[1].split('">')[1]+'","ship": "'+scorelist[2].split('">')[1]+'"}')
return json.dumps({"code": 0, "rows":list(set(shoplist))})
except Exception, e:
print e
return json.dumps({"code": -1, "msg": "没查询到相关店铺"})
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
前端HTML代码
$def with (urlbase)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello world</title>
</head>
<body>
<input type="text" name="shopname">
<input type="button" value="提交" @click="query">
<div class="info" v-for="item in shopes" style="border-bottom: #ccc 1px dashed">
<p>店铺:{{ item.shopname }}</p>
<p>描述相符:{{ item.dsr }}<br>服务态度:{{ item.service }}<br>物流服务:{{ item.ship }}</p>
</div>
<script type="text/javascript" src="$urlbase/jquery.min.js"></script>
<script type="text/javascript" src="$urlbase/vue.js"></script>
<script type="text/javascript" src="$urlbase/index.js"></script>
</body>
</html>
js代码
var mainVM = new Vue({
el: 'body',
data: {
shopes:[
{
shopname:'未查询',
dsr:'未查询',
service:'未查询',
ship:'未查询'
}
]
},
methods:{
query:function(){
var _self = this,keyword = $('input[name="shopname"]').val();
$.post('/query',{"shopname":keyword},function (data) {
if(data.code == 0){
_self.shopes = [];
for(var k in data.rows){
var thisdata = JSON.parse(data.rows[k]);
_self.shopes.push({
shopname:thisdata.shopname,
dsr:thisdata.dsr,
service:thisdata.service,
ship:thisdata.ship
})
}
}else{
alert('查询出错,错误信息:'+data.msg);
}
},"json");
}
}
});
可以说代码部分也是相当简单,前端HTML和js的代码就不解释了,很容易看懂,这里只对app.py做简单的解释。
观察天猫的搜索页面,发现天猫pc端跟手机端页面都可以轻松抓取,但是使用手机端页面会更加快速方便,因为结构上更加清晰,而且数据量少,抓取速度更快
如何实现只抓取手机端页面的数据呢?很简单,这里我们只需要定义以下HTTP的请求头信息就可以了,也就是headers,如下定义
headers={"User-Agent":"iphone7"}
天猫的搜索链接是使用的get请求,地址为
"https://list.tmall.com/search_product.htm?q="+keywords
参数只需要传入一个关键字就可以了,前端使用ajax把数据POST给服务端,服务端接收使用下面的这句话
keywords=str(web.input().get('shopname'))
是不是马上就搞定了关键的几步了?接下来发起请求拿到数据就可以了
result_base=requests.get(url=url_base,headers=headers,timeout=15).content.replace('\n','').replace('','')
注意,这里我把返回的结果中的换行跟空格都去掉了,因为我这里所需要的数据很简单,为了匹配方便我直接给替换成可空,也就是后面的这个
.replace('\n','').replace('','')
然后根据正则匹配的字符串进行遍历组合成结果返回给前端就好了,前端直接使用vue.js进行数据的绑定,几乎不需要DOM操作就可以完成结果列表的渲染,棒!(这里强行安利一波vue.js)
前后端通信使用json进行数据交互,友好而且方便。
重写
上面给出了所需要的技术要点和关键代码,那么现在我需要使用flask重写一遍,当然了,关键部分还是不用变动,只是处理方式上稍微有些差异,如果会用web.py,那么使用flask上手应该是很快的。
1、web.py的处理方式
在使用web.py的时候我们启动一个web服务很简单,通常执行以下命令
python app.py
这样我们就启动了一个web服务,但是这样的话会有很多问题,主要有以下几点
- 不能关闭终端窗口,否则应用结束,一般用于调试
- 多个应用的时候公用Python环境会引起冲突
注意:
web.py并不适合高并发的应用,但是作为一般应用还是可以轻松应对的。
以上命令执行后web.py会在8080端口绑定一个web服务,如果你想创建多个应用,那么你应该在后面加上端口号
如果你使用了多个域名指向一台机器的多个应用,那么你应该使用nginx来转发请求,而不是直接输入域名加端口号
在远程vps上运行开发完成的应用时,你可以执行以下命令把web以后台服务的形式运行
nohup python app.py
这种方式简单粗暴,但是仅仅作为临时方案是可行的,运行上述命令后你可以安心的关掉终端,而且web服务依然在运行,但是一旦重启了服务器,那么就得重新登录vps再次执行命令,不是很方便。
2、flask的处理方式
flask和web.py类似,它自带了一个web服务器,默认绑定在5000端口,但是它本身自带的web服务器并不是很好,安全性也不高,作为开发使用还是足够的,正式生产环境中不太建议直接使用flask自带的web服务。
好了,现在可以开始了,为了解决上面提到几个问题,这里咱们来使用一个新东西,上面说了多应用环境冲突的问题,在这儿可以使用一个叫做“虚拟环境”的东西解决。
“虚拟环境”就是直接复制一个Python的全局环境,但是是独立出来的,你可以在这个环境里面安装各种模块,而且不会影响到Python的全局环境,也就是说如果你把其中的一个“虚拟环境”给玩坏了,起不来了,那么你只需要删掉坏的“虚拟环境”重新创建一个就可以了,这些操作都不会对Python全局环境有任何的影响,安全又方便,下面咱们就来创建一个“虚拟环境”。
博主使用的开发环境是Ubuntu 16.04 并没有自带这个软件,使用下面的命令安装
sudo apt-get install python-virtualenv -y
安装完之后测试下是否安装成功
~$ virtualenv --version
15.0.1
接下来咱们创建一个叫 tmall 虚拟环境用于运行我们的应用
~$ virtualenv tmall
Running virtualenv with interpreter /usr/bin/python2
New python executable in /home/kbdancer/tmall/bin/python2
Also creating executable in /home/kbdancer/tmall/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.
创建的时候会给出创建的位置,如果你需要在指定的目录下面创建虚拟环境,那么你得切换到目标目录,然后执行创建命令,博主这里直接在自己的用户目录下面执行的创建命令,自然就是在用户目录下面生成的一个 tmall 文件夹,文件夹下面自动生成了Python环境

安装完之后需要将这个环境激活才能使用,执行下面的命令进行激活
~$ source tmall/bin/activate
(tmall) :~$
接着在虚拟环境中安装flask环境(博主默认你的Python全局环境中已经有了easy_install或者pip),博主这里使用pip进行安装
~$ pip install flask
好了,所需要的环境配置完成,接下来就可以开始写小应用了。
3、开始编码
编码这个环节应该是快速而且高效的,上面我们已经给出了旧代码,关键部分直接复制过来就能用,稍微改改就可以跑起来了。
flask默认使用Jinja2作为模板引擎,Jinja2在进行模板渲染的时候通常会识别{{}}中的内容进行填充,但是这里博主遇到了一个尴尬的问题,Vue.js也是使用的{{}}作为标识符进行渲染,这就导致了冲突,访问页面的时候就会出现如图所示的错误
当然,解决方法还是有的,参考这篇文章进行配置 解决Jinja2与Vue.js的模板冲突 解决思路也比较简单,就是在需要Jinja2渲染的时候添加一个空格,而vue.js渲染的时候则不需要空格,python脚本如下
from flask import Flask, render_template
app = Flask(__name__)
app.jinja_env.variable_start_string = '{{ '
app.jinja_env.variable_end_string = ' }}'
前端HTML代码修改后就成了这样
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello world</title>
</head>
<body>
<input type="text" name="shopname">
<input type="button" value="提交" @click="query">
<div class="info" v-for="item in shopes" style="border-bottom: #ccc 1px dashed">
<p>店铺:{{item.shopname}}</p>
<p>描述相符:{{item.dsr}}<br>服务态度:{{item.service}}<br>物流服务:{{item.ship}}</p>
</div>
<script type="text/javascript" src="{{ url_for('static', filename='jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='vue.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='index.js') }}"></script>
</body>
</html>
Jinja2默认会在templates目录下面寻找模板文件,而静态文件比如css,js之类的默认存储在static目录下面,这里我们按照Jinja2的默认设置稍微进行修改,当然,如果你想自定义模板目录或者静态文件的目录也是可以的,只需要稍微的配置下就行了,博主这里按照默认的规则来设置。
很快,我们的小应用就跑起来了

这里还是需要提到几个关键点:
flask中接收前端传递过来的参数用到的是request对象,前端使用json把数据post到后端,后端使用下面这句进行接收
request.form.get('shopname')
更多详细使用方法参考这个地址 浅入浅出Flask框架:处理客户端通过POST方法传送的数据 接着测试下小应用能不能正常运行


OK,测试通过。
4、关于部署
由于这个小应用比较简单,部署起来可以按照常规的部署方式进行,但是并不适合生产环境,所以这里暂时不写如何部署,下次有大型网站案例的时候再详细写如何部署以及优化。
5、完整代码
python部分
#!/usr/bin/env python
# coding=utf-8
from flask import Flask, render_template, request
import requests
import json
import re
app = Flask(__name__)
app.jinja_env.variable_start_string = '{{ '
app.jinja_env.variable_end_string = ' }}'
@app.route('/')
def index():
return render_template('index.html')
@app.route('/query', methods=['POST'])
def query():
keywords = request.form.get('shopname')
url_base = "https://list.tmall.com/search_product.htm?q=" + keywords
headers = {"User-Agent": "iphone7"}
try:
result_base = requests.get(url=url_base, headers=headers, timeout=15).content.replace('\n', '').replace(' ', '')
infostr = re.findall(r'j_shop_moreshop_more\">(.+?)</div>', result_base)
shoplist = []
for item in infostr:
scorelist = re.findall(r'\">(.+?)</span><iclass=\"', item)
thisShopname = re.findall(r'<span>(.+?)</span>', item)[0]
shoplist.append('{"shopname": "' + thisShopname + '" , "dsr": "' + scorelist[0] + '", "service": "' + scorelist[1].split('">')[1] + '","ship": "' + scorelist[2].split('">')[1] + '"}')
return json.dumps({"code": 0, "rows": list(set(shoplist))})
except Exception, e:
print e
return json.dumps({"code": -1, "msg": "没查询到相关店铺"})
if __name__ == "__main__":
app.run(debug=True)
HTML部分
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello world</title>
</head>
<body>
<input type="text" name="shopname">
<input type="button" value="提交" @click="query">
<div class="info" v-for="item in shopes" style="border-bottom: #ccc 1px dashed">
<p>店铺:{{item.shopname}}</p>
<p>描述相符:{{item.dsr}}<br>服务态度:{{item.service}}<br>物流服务:{{item.ship}}</p>
</div>
<script type="text/javascript" src="{{ url_for('static', filename='jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='vue.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='index.js') }}"></script>
</body>
</html>
JS部分
没有做任何改动,就不贴出来了
总结
写这篇文章的目的一来是复习下flask的一些知识,二来是与web.py做个对比,再者就是给入门的朋友提供一个实战的例子,方便参考。
一只猿:使用flask来做一个小应用的更多相关文章
- 使用PixiJS做一个小游戏
PixiJS PixiJS使用WebGL,是一个超快的HTML5 2D渲染引擎.作为一个Javascript的2D渲染器,Pixi.js的目标是提供一个快速的.轻量级而且是兼任所有设备的2D库. 官方 ...
- 用RecyclerView做一个小清新的Gallery效果 - Ryan Lee的博客
一.简介 RecyclerView现在已经是越来越强大,且不说已经被大家用到滚瓜烂熟的代替ListView的基础功能,现在RecyclerView还可以取代ViewPager实现Banner效果,当然 ...
- 用struts2标签如何从数据库获取数据并在查询页面显示。最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变量。
最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变 ...
- 用RecyclerView做一个小清新的Gallery效果
一.简介 RecyclerView现在已经是越来越强大,且不说已经被大家用到滚瓜烂熟的代替ListView的基础功能,现在RecyclerView还可以取代ViewPager实现Banner效果,当然 ...
- E时代主机,其实做一个小虚拟主机还是不错的
http://www.idcen.com/ 做微信没有网上测试地址,找了一下发现以前用过的.记录一下.一个100m的虚拟主机需要三四十块钱,做微信,做一个小型网站还是够用的,就是mysql有点问题,不 ...
- 用MVC5+EF6+WebApi 做一个小功能(三) 项目搭建
一般一个项目开始之前都会有启动会,需求交底等等,其中会有一个环节,大讲特讲项目的意义,然后取一个高大上的项目名字,咱这是一个小功能谈不上项目,但是名字不能太小气了.好吧,就叫Trump吧.没有任何含义 ...
- jquery做一个小的轮播插件---有BUG,后续修改
//首页无缝轮播 ; (function($, window, document, undefined) { $.fn.slider = function(options) { var default ...
- 用MVC5+EF6+WebApi 做一个小功能(二) 项目需求整理
在一个项目开始前,需求整理大概要占到整个项目周期15%甚至30%的比重,可以说需求理得越清楚,后续开发中返工几率越小.在一个项目中,开发新功能的花费的精力要远远小于修改功能的精力,这基本是一个共识.老 ...
- 用MVC5+EF6+WebApi 做一个小功能(一)开场挖坑,在线答题系统
从哪开始说呢,这几年微软的技术一直在变,像是牟足了劲要累死所有的NET程序员,从WebForm到MVC到现在MPA.SPA .Razor单页,从net2.0一直走到现在.net4.6.2,后面还有一个 ...
随机推荐
- HourRank 19
https://www.hackerrank.com/contests/hourrank-19/challenges 第一题略. 第二题是nim博弈,问删掉一个区间的石子,使得先手败的方案有几种,明显 ...
- 011一对一 唯一外键关联映射_单向(one-to-one)
² 两个对象之间是一对一的关系,如Person-IdCard(人—身份证号) ² 有两种策略可以实现一对一的关联映射 主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系:数据库 ...
- ThinkPHP5.0更改框架的验证方法对象->validate(true)->save();
我们更希望看到: // 新增对象至数据表 $result = $Teacher->validate(true)->save(); 而不是: // 新增对象至数据表 $result = $T ...
- 使用 libdvm.so 内部函数dvm* 加载 dex
首先要清楚,odex只是对代码段(我将dex文件与elf文件类比,大家都将执行文件分成不同的段)作优化,而其它用于类反射信息的段都应用原来的dex,所以odex文件内部还包含了一个dex. 打开一个d ...
- 网络安全——一图看懂HTTPS建立过程
关于网络安全加密的介绍可以看之前文章: 1. 网络安全--数据的加密与签名,RSA介绍 2. Base64编码.MD5.SHA1-SHA512.HMAC(SHA1-SHA512) 3. When I ...
- CEF3 获取Cookie例子 CefCookieManager C++
首先从cef_cookie.h 源码种看到CefCookieManager 这个类: // Visit all cookies on the IO thread. The returned cooki ...
- javascript的八张图
- 后台开发之IO缓冲区管理
Linux系统IO中write原型为 ssize_t write(int filedes, const void * buff, size_t nbytes) ; 当调用write写数据的时候,调 ...
- CoreAnimation学习,学习总结,记录各种过程中遇到的坑
1. CAAimation 的 duration = 0 的时候, 这个时候就相当于没有动画了. 2. CAKeyframeAnimation *rotateAnimation = [CAKeyfr ...
- Java之戳中痛点 - (7)善用Java整型缓存池
先看一段代码: package com.test; import java.util.Scanner; public class IntegerCache { public static void m ...