Mongo/ES数据储存及利用Flask进行结果展示
手动反爬虫,禁止转载: 原博地址 https://blog.youkuaiyun.com/lys_828/article/details/121283758(优快云博主:Be_melting)
知识梳理不易,请尊重劳动成果,文章仅发布在优快云网站上,在其他网站看到该博文均属于未经作者授权的恶意爬取信息
5 数据储存及结果展示
在收集了航班数据之后,我们进行了基础的处理,去除了不需要的部分,对空值进行了处理,现在我们需要把数据存储到NoSQL的数据库中。
存储的这一步,我们使用之前准备的MongoDB和ES作为存储服务器,这样后面就可以利用Flask应用服务器对数据进行展示处理,该部分属于下图中框中的内容。
5.1 将数据保存到MongoDB
重新创建一个文件,命名为example03.py,然后打开Compass软件,连接上数据库(如果没有连接数据库,最后写入的时候会报写入网络错误)。
接着在创建的文件中重现加载Spark的环境和激活之前自定义的pymogo_spark程序,就能够直接使用saveToMongoDB()的方法直接将数据写入数据库,但是需要注意的是要先将数据换成为字典的格式,代码及运行结果如下。
最后刷新一下Compass页面,检查其中的数据库情况,对应界面如下,证明成功写入数据到数据库。
5.2 利用Flash进行数据结果展示
5.2.1 将数据展示到指定页面
在step1文件夹下创建一个web文件夹,里面包含两个固定的文件夹为static和templates,然后把boostrap相关的4个文件移动到static文件夹下,在templates文件夹下创建一个index.html文件。接着就是在web文件夹下创建一个on_time01.py文件,将3.4部分介绍利用Flask搭建网站的代码直接挪用,然后添加如下代码。
@app.route('/on_time')
def on_time():
flight = client['example'].on_time.find_one()
return json_util.dumps(flight)
程序执行后,在浏览器端的网址路径下添加/on_time回车后,就可以输出如下内容。(利用Flask很快速就将数据从Mongo DB中展示到网页上)
5.2.2 数据筛选显示
按照运营商、飞行航班、飞行时间字段进行筛选显示,丰富on_time()函数中的内容如下。
@app.route('/on_time')
def on_time():
carrier = request.args.get('Carrier')
flight_date = request.args.get('FlightDate')
flight_num = request.args.get('FlightNum')
# print (carrier,flight_date,flight_num)
flight = client['example'].on_time.find_one(
{
'Carrier': carrier,
'FlightDate': flight_date,
'FlightNum': flight_num
}
)
return json_util.dumps(flight)
Mongo DB中也可以设置字段的索引,从而来大幅度的提高查询速度,比如就拿上面介绍的三个字段进行索引的创建。点击首页Indexes选项卡,然后按照如下进行设置,添加完索引字段后,点击右下角的CREATE INDEX按钮,程序就会创建索引。
创建完毕后,重新运行on_time01.py后,指定一个查询条件,进行查询,比如查询网址:http://127.0.0.1:5000/on_time?Carrier=US&FlightDate=2015-05-26&FlightNum=2174,输出结果如下。(成功完成数据的筛序显示)
5.2.3 美化数据输出
上面数据的输出就是单纯的把结果展示出来,没有美感,可以利用boostrap进行页面的美化。之所以选择使用boostrap,就是因为不需要自己了解太多的前端技术,直接把示例样本的代码部分copy过来就可以直接使用,比如在index.html文件中添加如下代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>大数据分析项目</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<link href="/static/bootstrap.min.css" rel="stylesheet">
<link href="/static/bootstrap-theme.min.css" rel="stylesheet">
</head>
<body>
<div id="wrap">
<!-- Begin page content -->
<div class="container">
<div class="page-header">
<h1>航班数据分析系统</h1>
</div>
{% block body2 %}{% endblock %}
</div>
<div id="push"></div>
</div>
<div id="footer">
<div class="container">
<p class="muted credit"><a>航班数据分析系统</a>
</div>
</div>
<script src="/static/bootstrap.min.js"></script>
</body>
</html>
代码复制粘贴后进行保存,然后再index.html文件同路径下创建一个flight.html文件,代码内容如下。
{% extends "index.html" %}
{% block body2 %}
<div>
<p class="lead">航班编号 {{flight.FlightNum}}</p>
<table class="table">
<thead>
<th>航空公司名称</th>
<th>出发地</th>
<th>目的地</th>
<th>飞机机尾编号</th>
<th>日期</th>
<th>到达时间</th>
<th>飞行距离</th>
</thead>
<tbody>
<tr>
<td>{{flight.Carrier}}</td>
<td>{{flight.Origin}}</td>
<td>{{flight.Dest}}</td>
<td>{{flight.TailNum}}</td>
<td>{{flight.FlightDate}}</td>
<td>{{flight.AirTime}}</td>
<td>{{flight.Distance}}</td>
</tr>
</tbody>
</table>
</div>
{% endblock %}
添加完毕后,点击保存。为了对比不同步骤下的美化效果,可以创建不同版本的py文件,比如这里新建一个on_time02.py文件,将其中的on_time()函数的最后一行return内容进行修改,指引到刚刚创建的flight.html文件,代码完善如下。
@app.route('/on_time')
def on_time():
carrier = request.args.get('Carrier')
flight_date = request.args.get('FlightDate')
flight_num = request.args.get('FlightNum')
# print (carrier,flight_date,flight_num)
flight = client['example'].on_time.find_one(
{
'Carrier': carrier,
'FlightDate': flight_date,
'FlightNum': flight_num
}
)
return render_template('flight.html',flight=flight)
此时再进行程序的运行,刷新筛选页面,对比前后两个版本的输出内容如下。(上面的输出就是经过美化的2.0版本,下面就是原输出结果)
以上的数据展示都是查询只有一行的数据,如果筛选的结果是多行数据,如何进行结果展示?因此需要创建3.0版本on_time03.py,同时对于flight.html文件中的代码也需要进行修改。
on_time03.py文件中只要将on_time()函数中的find_one改为find即可,重新创建一个flights.html文件,把原来的flight.html文件中的代码全部copy过来,然后添加一个循环体,从而实现多数据的展示,代码如下。(只需要在tbody标签中添加循环体开始和结束的代码)
<tbody>
{% for flight in flights %}
<tr>
<td>{{flight.Carrier}}</td>
<td>{{flight.Origin}}</td>
<td>{{flight.Dest}}</td>
<td>{{flight.TailNum}}</td>
<td>{{flight.FlightDate}}</td>
<td>{{flight.AirTime}}</td>
<td>{{flight.Distance}}</td>
</tr>
{% endfor %}
</tbody>
修改完毕后进行保存,重新运行on_time03.py文件,刷新筛选结果页面,输出结果如下。
虽然可以直接在浏览器的网址栏通过输入对应的条件进行查询,但是这种方式很麻烦,而且也不直观,因此可以通过设置字段变量,通过获取用户的输入直接进行单条件/多条件信息的查询,在on_time03.py文件中添加如下代码。
@app.route("/flights/<origin>/<dest>/<flight_date>")
def list_flights(origin, dest, flight_date):
flights = client.example.on_time.find(
{
'Origin': origin,
'Dest': dest,
'FlightDate': flight_date,
},
sort=[
('DepTime', 1),
('ArrTime', 1)
]
)
flight_count = client.example.on_time.count_documents(
{
'Origin': origin,
'Dest': dest,
'FlightDate':flight_date,
}
)
return render_template('flights.html' , flights=flights,flight_count=flight_count,flight_date=flight_date)
添加完毕,选择保存,再次运行后,输入网址:http://127.0.0.1:5000/flights/ATL/SFO/2015-01-01,等待网页刷新。(等待大概10s左右,网页顺利的刷新出来)
可以通过添加索引来调高查询效率,打开Mongo DB的可视化界面,创建索引如下,也可以直接通过命令行操作。
再次运行程序后,刷新网页,秒出搜索结果。
5.2.4 多数据分页显示
解决了多数据输出的问题,接下来就遇到了另一个问题,如果数据过多,比如查询的数据量在100条以上,那么一个页面要显示出全部的数据,就需要一直往下拉动网页右侧的滚动条,因此为了解决这个问题,可以设置数据分页进行展示。
首先创建4.0版本的on_time04.py,把原来版本的代码全都复制过来。由于要设定分页,前提就是按照多少条数据量进行分页,故需要先制定这个数量,因此为了方便管理,可以单独创建一个config.py文件,将分页数据量赋值,比如制定12条数据后进行翻页:RECORDS_PER_PAGE=12。 然后在on_time04.py文件中添加如下代码。
@app.route("/flights/<origin>/<dest>/<flight_date>")
def list_flights(origin, dest, flight_date):
start = request.args.get('start') or 0
start = int(start)
end = request.args.get('end') or config.RECORDS_PER_PAGE
end = int(end)
width = end - start
nav_offsets = get_ud_offsets(start, end, config.RECORDS_PER_PAGE)
flights = client.example.on_time.find(
{
'Origin': origin,
'Dest': dest,
'FlightDate': flight_date,
},
sort=[
('DepTime', 1),
('ArrTime', 1)
]
)
flight_count = client.example.on_time.count_documents(
{
'Origin': origin,
'Dest': dest,
'FlightDate': flight_date,
}
)
flights = flights.skip(start).limit(width)
return render_template(
'flights.html',
flights=flights,
flight_count=flight_count,
flight_date=flight_date,
nav_path= request.path,
nav_offsets = nav_offsets
)
def get_ud_offsets(offset1, offset2, increment):
offsets = {}
offsets['上一页'] = {'top_offset': max(offset2 - increment, 0),
'bottom_offset': max(offset1 - increment, 0)} # Don't go < 0
offsets['下一页'] = {'top_offset': offset2 + increment, 'bottom_offset':
offset1 + increment}
return offsets
其中list_flights()设置好数据显示的位置,而get_ud_offsets()函数是添加上一页和下一页点击按钮的链接。然后还需要在flights.html文件中倒数第二行添加如下四行代码。
{% import "macros.jnj" as common %}
{% if nav_offsets and nav_path -%}
{{ common.display_nav(nav_offsets, nav_path, flight_count, query)|safe }}
{% endif -%}
保存完毕后进行网页刷新,最终输出结果如下。
此外为了防止网页输入时候出现问题,可以在on_time04.py文件中进行一个防错的设置,避免出现一些意外的bug。
def strip_place(url):
try:
if '&start=' in url:
p = re.match('(.+)&start=.+&end=.+', url).group(1)
elif '%3Fstart=' in url:
p = re.match('(.+)%3Fstart=.+&end=.+', url).group(1)
else:
p = url
except AttributeError as e:
return url
return p
5.2.5 制作具有查询功能的页面
以上的数据筛选都是基于url给定的筛选条件,很不方便,可以设置一下数据的查询框,通过查询框中的内容与url进行联动,从而正确获取想要筛选的数据(基于ES数据库)。首先创建5.0版本的on_time05.py,复制之前版本的内容后,添加如下代码。(config.ELASTIC_URL是在config.py文件中添加的信息,ELASTIC_URL=‘http://localhost:9200’)
from elasticsearch import Elasticsearch
elastic = Elasticsearch(config.ELASTIC_URL)
@app.route("/flights/search")
def search_flights():
carrier = request.args.get('Carrier')
flight_date = request.args.get('FlightDate')
origin = request.args.get('Origin')
dest = request.args.get('Dest')
tail_number = request.args.get('TailNum')
start = request.args.get('start') or 0
start = int(start)
end = request.args.get('end') or config.RECORDS_PER_PAGE
end = int(end)
if end - start > config.RECORDS_PER_PAGE:
start = end - config.RECORDS_PER_PAGE
nav_path = strip_place(request.url)
flight_number = request.args.get('FlightNum')
query = {
'query': {
'bool': {
'must': []}
},
'sort': [
{'FlightDate': 'asc'},
],
'from': start,
'size': config.RECORDS_PER_PAGE
}
print(flight_number)
if '?' in flight_number:
flight_number = flight_number.split('?')[0]
if carrier:
query['query']['bool']['must'].append({'match': {'Carrier': carrier}})
if flight_date:
query['query']['bool']['must'].append({'match': {'FlightDate': flight_date}})
if origin:
query['query']['bool']['must'].append({'match': {'Origin': origin}})
if dest:
query['query']['bool']['must'].append({'match': {'Dest': dest}})
if tail_number:
query['query']['bool']['must'].append({'match': {'TailNum': tail_number}})
if flight_number:
query['query']['bool']['must'].append({'match': {'FlightNum': flight_number}})
results = elastic.search(query)
flights, flight_count = process_search(results)
nav_offsets = get_ud_offsets(start, end, config.RECORDS_PER_PAGE)
try:
flight_count1 = flight_count['value']
except:
flight_count1 = 0
return render_template("search.html",
flights=flights,
flight_date=flight_date,
flight_count=flight_count1,
nav_path=nav_path,
nav_offsets=nav_offsets,
carrier=carrier,
origin=origin,
dest=dest,
tail_number=tail_number,
flight_number=flight_number
)
def process_search(results):
records = []
total = 0
if results['hits'] and results['hits']['hits']:
total = results['hits']['total']
hits = results['hits']['hits']
for hit in hits:
record = hit['_source']
records.append(record)
return records, total
保存文件后,重新运行,然后输入网址:http://127.0.0.1:5000/flights/search?Carrier=DL&FlightDate=2015-01-01&FlightNum=478&Origin=&Dest=&TailNum=,刷新后结果如下。
如果删除航班编号的内容,再次进行提交查询,输出结果如下。(结果中会有很多内容,翻页功能也正常使用)