目录

  • 项目简介
    • 一、项目名称:
    • 二、项目背景:
    • 三、项目核心:
    • 四、项目要求:
  • 技术说明
  • 代码展示
    • 一、前端部分:
    • 二、爬虫部分:
    • 三、后端部分:
  • 网页展示
  • 总结分析
    • 一、遇到的问题:
    • 二、解决方法:
    • 三、关于知识点分析:
    • 四、收获:

项目简介

一、项目名称:

智能出行规划网站

二、项目背景:

每当计划外出旅游时,我们最关心的应该就是目的地天气情况和两地间的交通情况。但遗憾的是,天气平台往往不提供交通信息,而出行平台往往又不提供天气信息,这样对于选择合适的出行目的地是很不方便的。

三、项目核心:

后台定时爬取所有城市的天气数据并保存在数据库中,当用户在地图上点击对应城市名后,能在图表内动态更新城市天气信息;并根据用户提交的起止地区,即时爬取航班信息后展示在表格中。

四、项目要求:

1)展示当前所选城市的一周天气信息(日期、温度、天气状况、风级);
2)展示当前所选城市从当前时间开始,24小时内气象数据(温度变化、相对湿度变化、风力等级变化);
3)在给出起点城市和终点城市后,爬取并展示两城市(必须有机场)间的航班信息(航班号、起点机场与时间、飞行时长、终点机场与时间、餐食供应情况、票价)。

技术说明

1)爬虫(lxml、selenium):由于天气网公开天气数据,所以可以直接使用xpath直接获取其响应中携带的数据;而航班信息网站反爬措施较多,需要使用selenium模拟浏览器操作来获取信息;
2)flask:搭建后端服务器,用于连接python代码与前端代码;
3)echarts:用于生成图表,实现数据的直观展示与中国地图(地理图表)的显示;
4)前端代码:网站的外观渲染与事件绑定;

代码展示

一、前端部分:

  • 网站只包含一个主页面,内容通过ajax动态更新,html文件如下:
<!DOCTYPE html>
<html lang="ch">
<head><meta charset="UTF-8"><!--viewport就是设备的屏幕上能用来显示我们的网页的那一块区域;viewport标记,用于指定用户是否可以缩放Web页面,并对相关的选项进行设定;如设置了固定的显示区域宽度,那么移动设备端就会出现横向拖动条;下述content设定方式是为了配合flexible.js使用:width=device-width是使页面适配设备显示宽度,禁止用户自行缩放;initial-scale=1.0设置初始缩放值为1,即不缩放--><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>智能出行规划</title><link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}"/>
</head>
<body>
<!-- 页面顶端部分 -->
<header><h1>智能出行规划平台</h1><div class="showtime"></div><script>var div = document.querySelector('.showtime');getDate();setInterval(getDate, 1000);function getDate() {var date = new Date();var year = date.getFullYear();var month = date.getMonth() + 1;month = month < 10 ? '0' + month : month;var dates = date.getDate();dates = dates < 10 ? '0' + dates : dates;var arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];var day = date.getDay();var hour = date.getHours();var half = hour >= 12 ? '下午' : '上午';hour = hour <= 12 ? hour : (hour - 12);hour = hour < 10 ? '0' + hour : hour;var min = date.getMinutes();min = min < 10 ? '0' + min : min;var sed = date.getSeconds();sed = sed < 10 ? '0' + sed : sed;div.innerHTML = (year + '年' + month + '月' + dates + '日 ' + arr[day] + ' ' + half + hour + '点' + min + '分' + sed + '秒');}</script>
</header><!-- 页面主体部分 -->
<section class="mainbox"><!-- 左侧 --><div class="column"><!-- 定义搭载图表的盒子,必须设置大小 --><div class="panel info"><h2>一周内天气预览</h2><div class="box"><table><tr><td>18日<br>(今天)</td><td>19日<br>(明天)</td><td>20日<br>(后天)</td><td>21日<br>(周日)</td><td>22日<br>(周一)</td><td>23日<br>(周二)</td><td>24日<br>(周三)</td></tr><tr><td>多云</td><td>多云</td><td>多云转晴</td><td></td><td></td><td></td><td></td></tr><tr><td>?/2℃</td><td>9℃/2℃</td><td>10℃/1℃</td><td>8℃/-1℃</td><td>4℃/-4℃</td><td>6℃/-4℃</td><td>7℃/-3℃</td></tr><tr><td>3-4级</td><td><3级</td><td><3级</td><td>4-5级</td><td>4-5级<br>转<3级</td><td><3级</td><td><3级</td></tr></table></div><div class="panel-footer" id="aii"></div></div><div class="panel bar"><h2>24小时气候变化</h2><div class="chart"></div><div class="panel-footer"></div></div></div><!-- 中间 --><div class="column"><!-- 数字部分 --><div class="ar"><div class="ar-hd"><form action="" method="post" target="son-window"><input id="from_city" name="from_city" type="text" placeholder='----' onclick="input_click(this)"><input id="to_city" name="to_city" type="text" placeholder='----' onclick="input_click(this)"><input id="submit"><button type="button" onclick="postData()"></button></form></div><div class="ar-bd"><ul><li>始发地区</li><li>终点地区</li></ul></div></div><!-- 地图部分 --><div class="map"><div class="map1"></div><div class="map2"></div><div class="map3"></div><div class="chart">地图模块</div></div></div><!-- 右侧 --><div class="column"><div class="window"><h2>航班信息显示</h2><div class="flight"><table id="flight-data"><tr><th>公司-航班</th><th>起飞机场/时间</th><th>飞行时长</th><th>终点机场/时间</th><th>早餐供应</th><th>票价</th></tr></table></div><div class="panel-footer"></div></div></div>
</section><!-- javascript部分 -->
<!-- flexible.js默认会将屏幕宽度十等分,然后将十等分后的一份宽度设为1rem,再通过将rem作为单位来指定某一元素大小,从而相当于设置元素的屏幕占比 -->
<!-- 这里1984px十等分后的198.4px太大,我们将其改为24等分后,1rem = 1984px/24,其值保存在html标签下的font-size样式中 -->
<script src="{{ url_for('static',filename='js/flexible.js') }}"></script>
<!-- 导入echarts的js文件 -->
<script src="{{ url_for('static',filename='js/echarts.min.js') }}"></script>
<!-- 导入jquery的js文件 -->
<script src="{{ url_for('static',filename='js/jquery.js') }}"></script>
<!-- 导入中国地图的js文件 -->
<script src="{{ url_for('static',filename='js/china.js') }}"></script>
<!-- 导入自定义的js文件 -->
<script><!--定义变量装载后端数据 -->var html_data = {{ flask_data }};<!-- 创建元素节点,用于导入js文件 -->var newScript = document.createElement('script');newScript.type = 'text/javascript';newScript.src = "{{url_for('static',filename='js/index.js')}}";<!-- appendChild()方法可向节点(body)的子节点列表(script)的末尾添加新的子节点 --><!-- 插入哪个列表取决于子节点的tag,注意getElementsByTagName返回的是列表 -->document.getElementsByTagName('body')[0].appendChild(newScript);</script>
</body>
</html>
  • 我们在index.css中设置,为了便于编辑我们使用less语言编写后再用编译器转换为css文件:
// 注意,使用lessc转化less文件为css时,less文件路径不可有中文!!!
// css初始化
* {margin: 0;padding: 0;// border-box固定了我们设定的元素宽与高,内容大小只能小于等于元素大小;// 通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度;box-sizing: border-box;
}li {// 去除列表前标list-style: none;
}// 指明我们自定义的字体名对应的字体文件路径
@font-face {font-family: electronicFont;src: url(../font/DS-DIGIT.TTF);
}body {background: url("../images/bg.png") // 这一步设置不平铺,靠上居中;no-repeat top center;background-size: cover;line-height: 1.15;
}// 页面头部盒子
header {position: relative;height: 1.25rem;background: url("../images/head_bg.png") no-repeat;background-size: 100% 100%;h1 {font-size: 0.475rem;color: #ffffff;text-align: center;line-height: 1rem;}.showtime {position: absolute;right: 0.375rem;top: 0;line-height: 0.9rem;color: rgba(126, 126, 126, 1);font-size: 0.25rem;}
}// 页面主体盒子
.mainbox {display: flex;// 面向pc端设置的最小宽度min-width: 1024px;// 适配的最大页面大小max-width: 2400px;margin: 0 auto;padding: 0.125rem 0.125rem 0;// 直接指定flex将等分所有column.column {flex: 3;}// 单独为第二列指定占比为5,此时盒内比为3:5:3.column:nth-child(2) {flex: 5;margin: 0 0.125rem 0.1875rem;overflow: hidden;}.panel {position: relative;height: 3.875rem;border: 1px solid rgba(255, 255, 255, 0.2);background: url("../images/line.png") rgba(255, 255, 255, 0.1);padding: 0 0.1875rem 0.5rem;margin-bottom: 0.1875rem;// 通过伪元素,设置上方边框角&::before {position: absolute;top: 0;left: 0;width: 10px;height: 10px;border-left: 2px solid dimgray;border-top: 2px solid dimgray;content: "";}&::after {position: absolute;top: 0;right: 0;width: 10px;height: 10px;border-right: 2px solid dimgray;border-top: 2px solid dimgray;content: "";}// 定义底部区域,通过伪元素,设置下方边框角.panel-footer {position: absolute;bottom: 0;left: 0;width: 100%;&::before {position: absolute;bottom: 0;left: 0;width: 10px;height: 10px;border-left: 2px solid dimgray;border-bottom: 2px solid dimgray;content: "";}&::after {position: absolute;bottom: 0;right: 0;width: 10px;height: 10px;border-right: 2px solid dimgray;border-bottom: 2px solid dimgray;content: "";}}h2 {height: 0.6rem;color: #ffffff;line-height: 0.6rem;text-align: center;font-size: 0.25rem;font-weight: 400;}}.info {.box {height: 3rem;table {/* table-layout用于设置表格的布局算法:fixed使列宽的显示基于我们设置的表格宽度与列宽;默认设置是auto,即基于单元格内容动态计算列宽 */table-layout: fixed;/* word-break用于设置单元格内容的换行:设置为break-all,则允许在单词内换行 */word-break: break-all;position: relative;text-align: center;margin: auto;border-radius: 0.2rem;border: 1px solid rgba(255, 255, 255, 0.2);background-color: transparent;animation: gradual 10s linear infinite alternate;td {height: 0.6rem;width: 1rem;color: #5A5A5A;text-align: center;font-size: 0.15rem;font-weight: 500;border: 1px solid rgba(255, 255, 255, 0.2);}&::before {position: absolute;top: 0;left: 0;content: "";width: 10px;height: 10px;border-left: 4px solid dimgray;border-top: 4px solid dimgray;}&::after {position: absolute;bottom: 0;right: 0;content: "";width: 10px;height: 10px;border-right: 4px solid dimgray;border-bottom: 4px solid dimgray;}}}@keyframes gradual {//0% {//  background-image: linear-gradient(to right, rgba(155, 48, 255, 0.2), rgba(55, 162, 255, 0.2), rgba(0, 255, 255, 0.2));//}//50% {//  background-image: linear-gradient(to right, rgba(0, 255, 255, 0.2), rgba(155, 48, 255, 0.2), rgba(55, 162, 255, 0.2));//}//100% {//  background-image: linear-gradient(to right, rgba(55, 162, 255, 0.2), rgba(0, 255, 255, 0.2), rgba(155, 48, 255, 0.2));//}from {background-color: rgba(250, 250, 250, 0.2);}to {background-color: rgba(150, 150, 150, 0.2);}}}.bar {height: 7.9375rem;.chart {height: 6.1875rem;}}.window {position: relative;height: 12rem;border: 1px solid rgba(255, 255, 255, 0.2);background: url("../images/line.png") rgba(255, 255, 255, 0.1);padding: 0 0.1875rem 0.5rem;margin-bottom: 0.1875rem;// 通过伪元素,设置上方边框角&::before {position: absolute;top: 0;left: 0;width: 10px;height: 10px;border-left: 2px solid dimgray;border-top: 2px solid dimgray;content: "";}&::after {position: absolute;top: 0;right: 0;width: 10px;height: 10px;border-right: 2px solid dimgray;border-top: 2px solid dimgray;content: "";}// 定义底部区域,通过伪元素,设置下方边框角.panel-footer {position: absolute;bottom: 0;left: 0;width: 100%;&::before {position: absolute;bottom: 0;left: 0;width: 10px;height: 10px;border-left: 2px solid dimgray;border-bottom: 2px solid dimgray;content: "";}&::after {position: absolute;bottom: 0;right: 0;width: 10px;height: 10px;border-right: 2px solid dimgray;border-bottom: 2px solid dimgray;content: "";}}h2 {height: 0.6rem;color: #ffffff;line-height: 0.6rem;text-align: center;font-size: 0.25rem;font-weight: 400;}.flight {height: 11.125rem;overflow: hidden;table {table-layout: fixed;word-break: break-all;position: relative;text-align: center;margin: auto;border-radius: 0.2rem;border: 1px solid rgba(255, 255, 255, 0.2);background-color: transparent;animation: gradual 10s linear infinite alternate;td {height: 0.6rem;width: 1rem;color: #5A5A5A;text-align: center;font-size: 0.15rem;font-weight: 500;border: 1px solid rgba(255, 255, 255, 0.2);}&::before {position: absolute;top: 0;left: 0;content: "";width: 10px;height: 10px;border-left: 4px solid dimgray;border-top: 4px solid dimgray;}&::after {position: absolute;bottom: 0;right: 0;content: "";width: 10px;height: 10px;border-right: 4px solid dimgray;border-bottom: 4px solid dimgray;}}}}
}// 数字部分
.ar {background: rgba(255, 255, 255, 0.1);padding: 0.1875rem;.ar-hd {position: relative;border: 1px solid rgba(255, 255, 255, 0.2);&::before {position: absolute;top: 0;left: 0;content: "";width: 30px;height: 10px;border-top: 2px solid dimgray;border-left: 2px solid dimgray;}&::after {position: absolute;bottom: 0;right: 0;content: "";width: 30px;height: 10px;border-right: 2px solid dimgray;border-bottom: 2px solid dimgray;}form {display: flex;input {width: 50%;position: relative;flex: 1;line-height: 1rem;font-size: 0.875rem;color: #5B5B5B;text-align: center;font-family: electronicFont, serif;background-color: transparent;border: 0;// 给第一列添加伪元素,画中间分界线&::after {content: "";position: absolute;top: 25%;right: 0;height: 50%;width: 1px;background: rgba(255, 255, 255, 0.2);}}#submit {display: none;}button {display: none;}}}.ar-bd {ul {display: flex;li {flex: 1;text-align: center;color: #5B5B5B;font-size: 0.225rem;height: 0.5rem;line-height: 0.5rem;padding-top: 0.125rem;}}}
}.map {position: relative;height: 10.125rem;.map1 {width: 6.475rem;height: 6.475rem;position: absolute;background: url("../images/map.png");background-size: 100% 100%;/* 设置水平垂直居中第一步使元素左上角对准父一级的中心位置第二部平移元素自身长宽50%的距离,使元素中心对准父一级的中心位置*/top: 50%;left: 50%;transform: translate(-50%, -50%);// 透明度opacity: 0.2;}// 后定义的图形默认盖在先定义的图形上面.map2 {width: 8.0375rem;height: 8.0375rem;position: absolute;background: url("../images/lbx.png");background-size: 100% 100%;top: 50%;left: 50%;transform: translate(-50%, -50%);// 调用动画:名称、执行周期15s、全程匀速、无限次播放animation: rotate 15s linear infinite;opacity: 0.4;}.map3 {width: 7.075rem;height: 7.075rem;position: absolute;background: url("../images/jt.png");background-size: 100% 100%;top: 50%;left: 50%;transform: translate(-50%, -50%);// 区别:执行周期10s、新加了一个反向播放animation: rotate 10s linear infinite reverse;opacity: 0.6;}.chart {/* 宽度占满(已在mainbox中设定总宽度并划分);高度等同父一级区块,也能自动伸缩 */width: 100%;height: 10.125rem;position: absolute;top: 0;left: 0;}/* 定义一个动画rotate1,@keyframes原理:从from设定的样式逐渐变到to设定的样式,也可以用0%和100%代替from和to;设定完后在元素内通过animation调用动画。*/@keyframes rotate {from {// rotate可以使指定元素旋转一个角度,1deg即1度transform: translate(-50%, -50%) rotate(0deg);}to {transform: translate(-50%, -50%) rotate(360deg);}}
}/* @media是针对不同的媒体设备,定义不同的CSS样式此处是针对screen(电脑屏幕、平板、移动设备)做出的样式修改用于约束页面随屏幕缩放的尺寸,在1024-2400px间不受影响 */
@media screen and (max-width: 1024px) {html {/* 当screen设备的显示页面宽度为小于1024px时,将html的font-size设为42px也就是1rem被固定为了42px,页面不会再随着浏览器窗口缩小而缩放 */font-size: 42px !important;}
}@media screen and (min-width: 2400px) {html {/* 当screen设备的显示页面宽度为大于2400px时,将html的font-size设为100px也就是1rem被固定为了100px,页面不会再随着浏览器窗口放大而增大 */font-size: 100px !important;}
}
  • 网站中导入的flexible.js、echarts.min.js、china.js是外部导入包,需要在官网自行下载;index.js是我们自己编写的js文件,网页中的点击事件已经echarts图表的设计都在该文件中进行:
// 为了解决图形过多而可能出现option命名的重复,我们使用立即执行函数“(<函数>)()”来进行图表的定义;
// 立即执行函数也叫自调用函数,外层的括号将函数包装为一个“函数表达式”;
// 函数表达式是一个对象,在对象后添加“()”就表示执行该对象,我们定义一般函数时也是生成了函数名对应的对象;
// 由于使用了立即执行函数,所以该js文件必须在页面渲染完成后再调用,不然无法得到我们查找的元素// 图表模块panel bar
var bar = function (data) {// 1实例化对象var myChart = echarts.init(document.querySelector('.bar .chart'));// 2. 指定配置项和数据var option;var yAxisData = [];var data1 = [];var data2 = [];var data3 = [];if (data) {yAxisData = data['time_list'];data1 = data['data_list2'];data2 = data['data_list1'];data3 = data['data_list3'];} else {for (var i = 46; i < 70; i++) {yAxisData = ["19", "20", "21", "22", "23", "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"];data1 = [80, 84, 88, 91, 93, 94, 94, 95, 96, 96, 96, 96, 97, 98, 99, 99, 96, 92, 82, 78, 73, 73, 79, 87, 89];data2 = [8, 7, 5, 5, 4, 3, 2, 2, 2, 1, 1, 1, 1, 2, 3, 4, 6, 6, 8, 9, 10, 9, 8, 7, 7];data3 = [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1];}}option = {tooltip: {trigger: 'axis',axisPointer: {// 坐标轴指示器,坐标轴触发有效type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'}},grid: {left: '10%',top: '15%',right: '10%',bottom: '0',//containLabel: true},legend: {data: ['相对湿度-%', '温度-°C', '风力等级-级']},xAxis: {type: 'value',position: 'top',splitLine: {lineStyle: {type: 'dashed'}}},yAxis: {type: 'category',data: yAxisData,axisLine: {show: false},axisLabel: {show: true},axisTick: {show: false,},splitLine: {show: false},},series: [{name: '相对湿度-%',type: 'bar',data: data1,barWidth: '70%',itemStyle: {opacity: 0.8,color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{offset: 0,color: 'rgba(55, 162, 255)'},{offset: 1,color: 'rgba(116, 21, 219)'}])},emphasis: {focus: 'series'},animationDelay: function (idx) {return idx * 10;}},{name: '温度-°C',type: 'line',data: data2,stack: 'Total',smooth: true,lineStyle: {width: 0},showSymbol: false,itemStyle:{opacity: 0.8,color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{offset: 0,color: 'rgba(255, 191, 0)'},{offset: 1,color: 'rgba(224, 62, 76)'}])},areaStyle: {opacity: 0.8,color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{offset: 0,color: 'rgba(255, 191, 0)'},{offset: 1,color: 'rgba(224, 62, 76)'}])},emphasis: {focus: 'series'},animationDelay: function (idx) {return idx * 10 + 100;}},{name: '风力等级-级',type: 'line',data: data3,stack: 'Total',smooth: true,lineStyle: {width: 0},showSymbol: false,itemStyle: {opacity: 0.8,color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{offset: 0,color: 'rgba(128, 255, 165)'},{offset: 1,color: 'rgba(1, 191, 236)'}])},areaStyle: {opacity: 0.8,color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{offset: 0,color: 'rgba(128, 255, 165)'},{offset: 1,color: 'rgba(1, 191, 236)'}])},emphasis: {focus: 'series'},animationDelay: function (idx) {return idx * 10 + 100;}}],animationEasing: 'elasticOut',animationDelayUpdate: function (idx) {return idx * 5;}};// 3. 把配置项给实例对象myChart.setOption(option);// 4. 让图表跟随屏幕自动的去适应window.addEventListener("resize", function () {// 添加一个事件监听:当windows执行resize事件(改变窗体大小)时,执行立即该函数:// 函数内容是调用mycharts的resize方法来同步修改图表大小myChart.resize();});
};// 中国地图模块map chart
var china_map = function () {// 1. 实例化对象var myChart = echarts.init(document.querySelector(".map .chart"));// 记录了哪些城市有机场,以及这些城市在中国地图上的坐标位置var geoCoordMap = {上海: [121.4648, 31.2891],东莞: [113.8953, 22.901],东营: [118.7073, 37.5513],中山: [113.4229, 22.478],临汾: [111.4783, 36.1615],临沂: [118.3118, 35.2936],丹东: [124.541, 40.4242],丽水: [119.5642, 28.1854],乌鲁木齐: [87.9236, 43.5883],佛山: [112.8955, 23.1097],保定: [115.0488, 39.0948],兰州: [103.5901, 36.3043],包头: [110.3467, 41.4899],北京: [116.4551, 40.2539],北海: [109.314, 21.6211],南京: [118.8062, 31.9208],南宁: [108.479, 23.1152],南昌: [116.0046, 28.6633],南通: [121.1023, 32.1625],厦门: [118.1689, 24.6478],台州: [121.1353, 28.6688],合肥: [117.29, 32.0581],呼和浩特: [111.4124, 40.4901],咸阳: [108.4131, 34.8706],哈尔滨: [127.9688, 45.368],唐山: [118.4766, 39.6826],嘉兴: [120.9155, 30.6354],大同: [113.7854, 39.8035],大连: [122.2229, 39.4409],天津: [117.4219, 39.4189],太原: [112.3352, 37.9413],威海: [121.9482, 37.1393],宁波: [121.5967, 29.6466],宝鸡: [107.1826, 34.3433],宿迁: [118.5535, 33.7775],常州: [119.4543, 31.5582],广州: [113.5107, 23.2196],廊坊: [116.521, 39.0509],延安: [109.1052, 36.4252],张家口: [115.1477, 40.8527],徐州: [117.5208, 34.3268],德州: [116.6858, 37.2107],惠州: [114.6204, 23.1647],成都: [103.9526, 30.7617],扬州: [119.4653, 32.8162],承德: [117.5757, 41.4075],拉萨: [91.1865, 30.1465],无锡: [120.3442, 31.5527],日照: [119.2786, 35.5023],昆明: [102.9199, 25.4663],杭州: [119.5313, 29.8773],枣庄: [117.323, 34.8926],柳州: [109.3799, 24.9774],株洲: [113.5327, 27.0319],武汉: [114.3896, 30.6628],汕头: [117.1692, 23.3405],江门: [112.6318, 22.1484],沈阳: [123.1238, 42.1216],沧州: [116.8286, 38.2104],河源: [114.917, 23.9722],泉州: [118.3228, 25.1147],泰安: [117.0264, 36.0516],泰州: [120.0586, 32.5525],济南: [117.1582, 36.8701],济宁: [116.8286, 35.3375],海口: [110.3893, 19.8516],淄博: [118.0371, 36.6064],淮安: [118.927, 33.4039],深圳: [114.5435, 22.5439],清远: [112.9175, 24.3292],温州: [120.498, 27.8119],渭南: [109.7864, 35.0299],湖州: [119.8608, 30.7782],湘潭: [112.5439, 27.7075],滨州: [117.8174, 37.4963],潍坊: [119.0918, 36.524],烟台: [120.7397, 37.5128],玉溪: [101.9312, 23.8898],珠海: [113.7305, 22.1155],盐城: [120.2234, 33.5577],盘锦: [121.9482, 41.0449],石家庄: [114.4995, 38.1006],福州: [119.4543, 25.9222],秦皇岛: [119.2126, 40.0232],绍兴: [120.564, 29.7565],聊城: [115.9167, 36.4032],肇庆: [112.1265, 23.5822],舟山: [122.2559, 30.2234],苏州: [120.6519, 31.3989],莱芜: [117.6526, 36.2714],菏泽: [115.6201, 35.2057],营口: [122.4316, 40.4297],葫芦岛: [120.1575, 40.578],衡水: [115.8838, 37.7161],衢州: [118.6853, 28.8666],西宁: [101.4038, 36.8207],西安: [109.1162, 34.2004],贵阳: [106.6992, 26.7682],连云港: [119.1248, 34.552],邢台: [114.8071, 37.2821],邯郸: [114.4775, 36.535],郑州: [113.4668, 34.6234],鄂尔多斯: [108.9734, 39.2487],重庆: [107.7539, 30.1904],金华: [120.0037, 29.1028],铜川: [109.0393, 35.1947],银川: [106.3586, 38.1775],镇江: [119.4763, 31.9702],长春: [125.8154, 44.2584],长沙: [113.0823, 28.2568],长治: [112.8625, 36.4746],阳泉: [113.4778, 38.0951],青岛: [120.4651, 36.3373],韶关: [113.7964, 24.7028]};/* 定义不同的航线(双方需要有机场):[始发地,目的地,设置目的地value值]只有目的地的机场会高亮圆点,value值作为圆点半径,会在目的地和航线上显示 */// 西安航线数据列表var XAData = [[{name: "西安"}, {name: "拉萨", value: 100}],[{name: "西安"}, {name: "上海", value: 100}],[{name: "西安"}, {name: "广州", value: 100}],[{name: "西安"}, {name: "西宁", value: 100}],[{name: "西安"}, {name: "银川", value: 100}]];// 西宁航线数据列表var XNData = [[{name: "西宁"}, {name: "北京", value: 100}],[{name: "西宁"}, {name: "上海", value: 100}],[{name: "西宁"}, {name: "广州", value: 100}],[{name: "西宁"}, {name: "西安", value: 100}],[{name: "西宁"}, {name: "银川", value: 100}]];// 银川航线数据列表var YCData = [[{name: "银川"}, {name: "上海", value: 100}],[{name: "银川"}, {name: "西安", value: 100}],[{name: "银川"}, {name: "西宁", value: 100}],[{name: "哈尔滨"}, {name: "北京", value: 100}],[{name: "北京"}, {name: "南昌", value: 100}]];// 拉萨数据列表var WLData = [[{name: "拉萨"}, {name: "哈尔滨", value: 100}],[{name: "拉萨"}, {name: "昆明", value: 100}],[{name: "拉萨"}, {name: "乌鲁木齐", value: 100}],[{name: "拉萨"}, {name: "西安", value: 100}]]// 设置特效图形的样式:矢量路径绘制图形var planePath ="path://M1705.06,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705.06,1318.313z";// var planePath = 'arrow';// 重写航线数据格式var convertData = function (data) {var res = [];// data对应的是航线数据(如:XNData),是一个的列表for (var i = 0; i < data.length; i++) {// 依次获取其中每一条航线信息var dataItem = data[i];// 去机场信息字典中查找始发机场坐标与目标机场坐标var fromCoord = geoCoordMap[dataItem[0].name];var toCoord = geoCoordMap[dataItem[1].name];if (fromCoord && toCoord) {// 如果两个机场都存在,就把该条信息重写为如下格式,然后插入到新列表尾部res.push({fromName: dataItem[0].name,toName: dataItem[1].name,// 坐标列表:起点坐标与终点坐标coords: [fromCoord, toCoord],value: dataItem[1].value});}}// 返回重写格式后的航线列表return res;};// 定义不同航线的颜色var color = ["#a6c84c", "#ffa022", "#46bee9", "#9F79EE"];// 预定义一个系列数组,存放航线数据列表var series = [];// 用一个数组封装各组航线数据,再使用forEach()方法调用数组的每个元素,并将元素传递给回调函数[["西安", XAData],["西宁", XNData],["银川", YCData],["拉萨", WLData],].forEach(// 回调函数:function (item, i) {// push()方法可向数组的末尾添加一个或多个元素,并返回新的长度,这里在生成option中seriesseries.push(// 箭头——红尾巴{// 匹配图例,系名相同的图形(同一系列)可以被一个legend控制name: item[0] + " Top4",// 用于带有起点和终点信息的线数据的绘制,主要用于地图上的航线,路线的可视化type: "lines",// 用于设置分层,大的zlevel的图形在小的zlevel图形上方zlevel: 1,// 线特效的配置effect: {show: true,// 特效动画单次时长(周期)period: 6,// 特效轨迹长,数值越大轨迹越长,取值范围0-1trailLength: 0.7,// 特效颜色color: "red",// 特效的大小symbolSize: 3},// 设置线条样式lineStyle: {normal: {color: color[i],width: 0,// 线条边的曲度,取值范围0-1curveness: 0.2}},// 将航线数据列表作为参数传给回调函数convertData,重写格式data: convertData(item[1])},// 箭头——飞机图标{name: item[0] + " Top4",// 用于带有起点和终点信息的线数据的绘制,主要用于地图上的航线,路线的可视化type: "lines",// 盖在尾巴式箭头上方zlevel: 2,symbol: ["none", "arrow"],// 线两端的标记大小symbolSize: 10,effect: {show: true,period: 6,trailLength: 0,// 特效图形样式:标准样式、图片或矢量图形,这里用上方定义的矢量图形symbol: planePath,symbolSize: 15},lineStyle: {normal: {color: color[i],width: 1,// 不透明度opacity: 0.6,curveness: 0.2}},data: convertData(item[1])},// 目标地的高亮圆点{// 系列名name: item[0] + "Top4",// 带有涟漪特效动画的散点(气泡)图,利用动画特效可以将某些想要突出的数据进行视觉突出。type: "effectScatter",// 指定坐标系为地理坐标系geocoordinateSystem: "geo",zlevel: 2,// 涟漪相关配置rippleEffect: {// 波纹绘制方式:fill或strokebrushType: "stroke"},label: {// 设置标签的一般显示样式(似乎属于通用设置?)normal: {show: true,position: "right",// 标签内容格式器,{b}是将数据名(data里的name属性对应值)设为labelformatter: "{b}"}},// 修改圆点尺寸,参数为数据值(data里的value属性对应值)symbolSize: function (val) {// 用我们航线数据里的values值除8作为圆点大小return val[2] / 8;},// 设置圆点样式itemStyle: {// 设置一般样式normal: {// 指定颜色列表中对应序号的颜色color: color[i]},// 高亮时(选中)图形与标签样式emphasis: {scale: true,// 设置区域颜色,似乎已经无效?areaColor: "#2B91B7"}},/* map返回数组中,满足回调函数内指定条件的元素,此处功能类似于上面的格式重写,是为了增加数据项;item[1]是航线数据列表(如XAData),dataItem是其中一条航线数据 */data: item[1].map(function (dataItem) {return {name: dataItem[1].name,/* concat()方法用于连接两个或多个字符串、数组,此处将目标地的坐标数组与我们数据中设置的value值连接 */value: geoCoordMap[dataItem[1].name].concat([dataItem[1].value])};})});});// 2.指定配置var option = {tooltip: {// 触发提示框的(方式)类型:'item'为数据项图形触发,如地图块,航线,目的地trigger: "item",// 提示框内容格式器,将回调函数返回值设为提示框的显示内容,params为series列表formatter: function (params, ticket, callback) {// 分别为目标地(effectScatter)、航线(lines)、地图区块指定不同的提示框内容if (params.seriesType === "effectScatter") {// 返回该series内data部分对应数据的拼接结果return "线路:" + params.data.name + "<br/>" + params.data.value[2];} else if (params.seriesType === "lines") {return (// 返回该series内data部分对应数据的拼接结果params.data.fromName +"-->" +params.data.toName +"<br/>" +params.data.value);} else {// 返回地图区块名称(即系列名)return params.name;}}},legend: {// 图例列表的布局方向:横向'horizontal'和纵向"vertical"orient: "vertical",/* 图例组件离容器上侧的距离,此处将图例置于图表容器底部如果top的值为'top'、'middle'、'bottom',组件会根据相应的位置自动对齐 */top: "bottom",/* 图例组件离容器左侧的距离,此处将图例置于图表容器右侧如果left的值为'left'、'center'、'right',组件会根据相应的位置自动对齐 */left: "right",// 每组航线数据都需要新增一个标签data: ["西安 Top4", "西宁 Top4", "银川 Top4", "拉萨 Top4"],textStyle: {color: "#fff"},/* 图例选择的模式,控制是否可以通过点击图例改变系列的显示状态;默认开启图例选择,可以设成false关闭;除此之外也可以设成'single'或者'multiple'使用单选或者多选模式。*/selectedMode: "multiple"},// 地理坐标系组件,用于绘制地理坐标图geo: {// 引入已经注册的地图js文件:china.jsmap: "china",label: {emphasis: {show: true,// color: "rgba(146, 111, 52, 0.8)",color: "#fff"}},// 把中国地图放大了1.2倍zoom: 1.2,roam: true,itemStyle: {normal: {opacity: 0.6,// 地图省份的背景颜色areaColor: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{offset: 0, color: '#FAFAFA'},{offset: 0.5, color: '#E6E6E6'},{offset: 1, color: '#D2D2D2'}]),borderColor: "rgba(0,0,0,0.5)",borderWidth: 1},// 高亮设置emphasis: {// 选中时的区域颜色areaColor: "rgba(223, 190, 106, 0.8)"}}},// 将上面处理后的series作为option的seriesseries: series};// 3. 把配置给实例对象myChart.setOption(option);// 3.5 设置点击事件// 点击前解绑,防止点击事件触发多次myChart.off('click',);myChart.on('click', echartsMapClick);// 4. 让图表跟随屏幕自动的去适应window.addEventListener("resize", function () {myChart.resize();});
};// 定义地图点击事件函数
var choice = "";
var echartsMapClick = function (params) {if (!params.name) {} else {choice = params.name;Refresh_chart(params.name);}// $("#from_city").on("click",input_click($(this),params.name));// $("#to_city").on("click",input_click($(this),params.name));
};// 刷新图表与表格
var Refresh_chart = function (choice) {var data = {city: choice};var str = JSON.stringify(data);$.ajax({url: '/chart',method: 'post',data: str,dataType: 'json',contentType: 'application/json; charset=UTF-8',success: function (result) {// console.log(result['week_data']);// console.log(result['day_data']);bar(result['day_data']);set_table(result['week_data']);},error: function (error) {console.log(error);}})
};// 设置天气表格数据
var set_table = function (data){var td_list = document.getElementsByTagName("td");console.log(data);for(var i=0;i<data.length;i++){td_list[i].innerHTML=data[i]['date'].slice(0,3)+'<br>'+data[i]['date'].slice(3);td_list[i+7].innerHTML=data[i]['weather'];td_list[i+14].innerHTML=data[i]['max_tem']+'/'+data[i]['min_tem']td_list[i+21].innerHTML=data[i]['wind']}
}// 聚焦函数
var input_click = function (self) {self.value = choice;if (self.name === "to_city") {document.getElementsByTagName("button")[0].click();}
};// ajax请求获取外部页面
var postData = function () {var from_city = $('#from_city').val();var to_city = $('#to_city').val();var data = {from_city: from_city,to_city: to_city};// 对象转json字符串var str = JSON.stringify(data)$.ajax({url: '/flight',method: 'post',data: str,dataType: 'json',contentType: 'application/json; charset=UTF-8',success: function (result) {console.log(result);insert_table(result)},error: function (error) {console.log(error);}})
};var insert_table = function(data) {var table=document.getElementById("flight-data");if (table.rows.length>1){for(var n=table.rows.length;n>1;n--){table.deleteRow(n-1);}}for(var i=0;i<data.length;i++) {var row=table.insertRow(table.rows.length);var cell1=row.insertCell(0);var cell2=row.insertCell(1);var cell3=row.insertCell(2);var cell4=row.insertCell(3);var cell5=row.insertCell(4);var cell6=row.insertCell(5);cell1.innerHTML=data[i]['aircraft']+'<br>'+data[i]['company']cell2.innerHTML=data[i]['takeoff_airport']+'<br>'+data[i]['takeoff_time'];cell3.innerHTML=data[i]['duration'];cell4.innerHTML=data[i]['landing_airport']+'<br>'+data[i]['landing_time'];cell5.innerHTML=data[i]['breakfast'];cell6.innerHTML=data[i]['price'];}
};// 睡眠函数
function sleep(numberMillis) {var now = new Date();var exitTime = now.getTime() + numberMillis;while (true) {now = new Date();if (now.getTime() > exitTime)return;}
}bar()
china_map()

二、爬虫部分:

  • 爬虫部分由一个定时爬虫和一个即时爬虫组成;
  • 定时爬虫采用xpath方法实现,由管理员设定一个定时器后便可定期爬取数据保存在数据库中:
import re
import pymysql
import requests
from lxml import etree# import io
# import sys
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #改变标准输出的默认编码class mysql:def __init__(self):self.connect = pymysql.connect(host='localhost',port=3306,user='root',password='x20000317',database='web_work',charset='utf8mb4')# 以字典的形式获取查询结果self.cursor = self.connect.cursor(pymysql.cursors.DictCursor)def save(self,*args):passdef read(self,*args):passdef exit(self):self.cursor.close()self.connect.close()class weather(mysql):def __init__(self):super().__init__()self.headers = {'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Mobile Safari/537.36 Edg/95.0.1020.53'}# 定义一个列表存储所有详情页urlself.url_list = []# 八个地区名称列表,多页爬取self.area_list = ['hb','db','hd','hz','hn','xb','xn','gat']# 存储一周内综合天气数据self.weekdata = []# 存储一天内各小时数据self.hourdata = []# 创建数据库sql_1 = 'create table week(id int primary key auto_increment,city varchar(20),date char(20),weather varchar(20),max_tem char(10),min_tem char(10),wind char(10))'sql_2 = 'create table hour(id int primary key auto_increment,city char(15),time char(4),temperature int,humidity int,wind_direction varchar(10),wind_power int)'try:self.cursor.execute(sql_1)except:passtry:self.cursor.execute(sql_2)except:pass# 获取详情页网址def get_url(self):for area_name in self.area_list:# 中国天气预报官网urlmain_url = 'http://www.weather.com.cn/textFC/{}.shtml'.format(area_name)# 获取页面html内容并用etree解析page_text = requests.get(url=main_url, headers=self.headers).texttree = etree.HTML(page_text)td_list = tree.xpath("//div[@class='hanml']/div[1]//td[@class='last']")for td in td_list:new_url = td.xpath("./a/@href")if new_url and (new_url[0] not in self.url_list):self.url_list.append(new_url[0])# 正则表达式获取数据def v_func(self, string):value_list = []time_pattern = '"od21":"(.*?)",'# 字符串列表time_list = re.findall(time_pattern, string, re.M)temperature_pattern = '"od22":"(.*?)",'# 字符串列表temperature_list = re.findall(temperature_pattern, string, re.M)humidity_pattern = '"od27":"(.*?)",'# 字符串列表humidity_list = re.findall(humidity_pattern, string, re.M)wind_pattern = '"od24":"(.*?)","od25":"(.*?)",'# 字符串元组列表wind_list = re.findall(wind_pattern, string, re.M)for i in range(len(time_list)):value_dict = {'time': time_list[i],'temperature': temperature_list[i],'humidity': humidity_list[i],'wind_direction': wind_list[i][0],'wind_power': wind_list[i][1]}value_list.insert(0, value_dict)return value_list# 获取详情页数据def get_data(self):self.get_url()for url in self.url_list:page = requests.get(url=url, headers=self.headers)page.encoding="utf-8"tree = etree.HTML(page.text)# 城市名信息处理city_list1 = tree.xpath("//div[@class='crumbs fl']//text()")city_list2 = [item.strip('全国').strip('>') for item in city_list1]city_list3 = [item.strip() for item in city_list2 if item.strip() != '']self.city_info = '-'.join(city_list3)# 城市一周天气数据处理li_list = tree.xpath("//ul[@class='t clearfix']/li")for li in li_list:text_list1 = li.xpath(".//text()")# 去除/text_list2 = [item.strip('/') for item in text_list1]# 去除\ntext_list3 = [item.strip() for item in text_list2 if item.strip()!='']text_list3.insert(0,self.city_info)if len(text_list3)!=6:text_list3.insert(3,'None')self.weekdata.append(text_list3)# 城市小时数据处理string = tree.xpath("//div[@class='left-div']/script//text()")[0]data_dict = {'name':re.findall('"od1":"(.*?)",',string)[0],'value':self.v_func(string)}self.hourdata.append(data_dict)self.save(self.weekdata,1)self.save(self.hourdata,2)# 重写保存方法def save(self,data,num):if num == 1:sql = 'truncate table week'self.cursor.execute(sql)self.connect.commit()sql = 'insert into week(city,date,weather,max_tem,min_tem,wind) values(%s,%s,%s,%s,%s,%s)'for item in data:self.cursor.execute(sql,(item[0],item[1],item[2],item[3],item[4],item[5]))self.connect.commit()elif num == 2:sql = 'truncate table hour'self.cursor.execute(sql)self.connect.commit()sql = 'insert into hour(city,time,wind_direction,temperature,humidity,wind_power) values(%s,%s,%s,%s,%s,%s)'for data_item in data:for item in data_item['value']:try:self.cursor.execute(sql,(data_item['name'],item['time'],item['wind_direction'],item['temperature'],item['humidity'],item['wind_power']))except Exception as e:print(e)print(data_item['name'],item)self.connect.commit()# 重写读取方法def read(self,num,data):if num == 1:sql = 'select * from week where city LIKE %s'self.cursor.execute(sql,('%'+data+'%'))elif num == 2:sql = 'select * from hour where city=%s'self.cursor.execute(sql,(data))# 当数据库内没有数据时返回的是元组,需要转换为列表info_list = list(self.cursor.fetchall())# print(info_list)return info_list
  • 即时爬虫采用selenium方法实现,在获取到前端发来的post请求后,便依据发送来的查询内容爬取相关信息:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
#实现规避检测
from selenium.webdriver import ChromeOptionsclass flight:def __init__(self,data1,data2,data3):# 实现无可视化界面的操作chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')# 实现规避检测option = ChromeOptions()option.add_experimental_option('excludeSwitches', ['enable-automation'])# 实例化driverself.bro = webdriver.Chrome(executable_path='driver/chromedriver.exe',chrome_options=chrome_options,options=option)# 城市名到机场缩写的转换self.city = {'阿勒泰': 'AAT','兴义': 'ACX','百色': 'AEB','阿克苏': 'AKU','鞍山': 'AOG','安庆': 'AQG','安顺': 'AVA','阿拉善左旗': 'AXF','包头': 'BAV','毕节': 'BFJ','北海': 'BHY','北京': 'BJS','秦皇岛': 'BPE','博乐': 'BPL','昌都': 'BPX','保山': 'BSD','广州': 'CAN','承德': 'CDE','常德': 'CGD','郑州': 'CGO','长春': 'CGQ','朝阳': 'CHG','赤峰': 'CIF','长治': 'CIH','重庆': 'CKG','长沙': 'CSX','成都': 'CTU','沧源': 'CWJ','嘉义': 'CYI','常州': 'CZX','大同': 'DAT','达县': 'DAX','白城': 'DBC','稻城': 'DCY','丹东': 'DDG','香格里拉迪庆)': 'DIG','大连': 'DLC','大理': 'DLU','敦煌': 'DNH','东营': 'DOY','大庆': 'DQA','鄂尔多斯': 'DSN','张家界': 'DYG','额济纳旗': 'EJN','恩施': 'ENH','延安': 'ENY','二连浩特': 'ERL','福州': 'FOC','阜阳': 'FUG','佛山': 'FUO','抚远': 'FYJ','格尔木': 'GOQ','广元': 'GYS','固原': 'GYU','海口': 'HAK','邯郸': 'HDG','黑河': 'HEK','呼和浩特': 'HET','合肥': 'HFE','杭州': 'HGH','淮安': 'HIA','怀化': 'HJJ','香港': 'HKG','海拉尔': 'HLD','乌兰浩特': 'HLH','哈密': 'HMI','神农架': 'HPG','哈尔滨': 'HRB','舟山': 'HSN','和田': 'HTN','惠州': 'HUZ','台州': 'HYN','汉中': 'HZG','黎平': 'HZH','银川': 'INC','且末': 'IQM','庆阳': 'IQN','景德镇': 'JDZ','加格达奇': 'JGD','嘉峪关': 'JGN','井冈山': 'JGS','西双版纳': 'JHG','金昌': 'JIC','黔江': 'JIQ','九江': 'JIU','晋江': 'JJN','澜沧': 'JMJ','佳木斯': 'JMU','济宁': 'JNG','锦州': 'JNZ','建三江': 'JSJ','池州': 'JUH','衢州': 'JUZ','鸡西': 'JXA','九寨沟': 'JZH','库车': 'KCA','康定': 'KGT','喀什': 'KHG','南昌': 'KHN','凯里': 'KJH','昆明': 'KMG','金门': 'KNH','赣州': 'KOW','库尔勒': 'KRL','克拉玛依': 'KRY','贵阳': 'KWE','桂林': 'KWL','龙岩': 'LCX','伊春': 'LDS','临汾': 'LFQ','兰州': 'LHW','丽江': 'LJG','荔波': 'LLB','永州': 'LLF','吕梁': 'LLV','临沧': 'LNJ','六盘水': 'LPF','芒市': 'LUM','拉萨': 'LXA','洛阳': 'LYA','连云港': 'LYG','临沂': 'LYI','柳州': 'LZH','泸州': 'LZO','林芝': 'LZY','牡丹江': 'MDG','马祖': 'MFK','澳门': 'MFM','绵阳': 'MIG','梅州': 'MXZ','南充': 'NAO','白山': 'NBS','齐齐哈尔': 'NDG','宁波': 'NGB','阿里': 'NGQ','南京': 'NKG','宁蒗': 'NLH','南宁': 'NNG','南阳': 'NNY','南通': 'NTG','满洲里': 'NZH','漠河': 'OHE','攀枝花': 'PZI','阿拉善右旗': 'RHT','日照': 'RIZ','日喀则': 'RKZ','巴彦淖尔': 'RLK','上海': 'SHA','沈阳': 'SHE','西安': 'SIA','石家庄': 'SJW','揭阳': 'SWA','普洱': 'SYM','三亚': 'SYX','深圳': 'SZX','青岛': 'TAO','塔城': 'TCG','腾冲': 'TCZ','铜仁': 'TEN','通辽': 'TGO','天水': 'THQ','吐鲁番': 'TLQ','济南': 'TNA','天津': 'TSN','唐山': 'TVS','黄山': 'TXN','太原': 'TYN','乌鲁木齐': 'URC','榆林': 'UYN','潍坊': 'WEF','威海': 'WEH','遵义(茅台)': 'WMT','文山': 'WNH','温州': 'WNZ','乌海': 'WUA','武汉': 'WUH','武夷山': 'WUS','无锡': 'WUX','梧州': 'WUZ','万州': 'WXN','襄阳': 'XFN','西昌': 'XIC','锡林浩特': 'XIL','厦门': 'XMN','西宁': 'XNN','徐州': 'XUZ','宜宾': 'YBP','运城': 'YCU','宜春': 'YIC','阿尔山': 'YIE','宜昌': 'YIH','伊宁': 'YIN','义乌': 'YIW','延吉': 'YNJ','烟台': 'YNT','盐城': 'YNZ','扬州': 'YTY','玉树': 'YUS','张掖': 'YZY','昭通': 'ZAT','湛江': 'ZHA','中卫': 'ZHY','张家口': 'ZQZ','珠海': 'ZUH','遵义(新舟)': 'ZYI'}self.start_end = [self.city[data1],self.city[data2],data3]# 获取网站信息def get_info(self):web_url = f'https://www.ly.com/flights/itinerary/oneway/{self.start_end[0]}-{self.start_end[1]}?date={self.start_end[2]}'self.bro.get(web_url)page_text = self.bro.page_sourceself.bro.quit()tree = etree.HTML(page_text)flight_list = tree.xpath("//div[@class='flight-item']")info_list = []for flight in flight_list:data = flight.xpath(".//text()")data = [item.strip() for item in data if item.strip() != '']info = {'company':data[0],'aircraft':data[1],'takeoff_time':data[2],'takeoff_airport':data[3],'duration':data[4],'landing_time':data[5],'landing_airport':data[6],'breakfast':data[7],'price':data[8]}info_list.append(info)# print(info_list)return info_list

三、后端部分:

  • 后端使用flask生成一个服务器,包含三个路由,主路由负责显示网站主页面,其余两个路由接收前端发来的ajax请求并返回响应数据:
from flask import Flask,render_template,request
from crawler import *
import datetime
import jsonapp = Flask(__name__)
app.config['JSON_AS_ASCII'] = False # 解决flask接口中文数据编码问题data = [100,100,100,100,100]@app.route('/',methods=['GET','POST'])
def mainwindow():return render_template('index.html',flask_data=data)@app.route('/flight',methods=['POST'])
def flightwindow():# data = request.get_data()# decode_data = urllib.parse.unquote(data.decode())data = request.get_json()# 获取表单中name为from_city的文本域提交的数据from_city = data['from_city']# 获取表单中name为to_city的文本域提交的数据to_city = data['to_city']# 获取当前年月日now_time = datetime.datetime.strftime(datetime.datetime.now(),'%Y-%m-%d')# 实例化爬虫对象obj = flight(from_city, to_city,now_time)# 返回静态文件return json.dumps(obj.get_info())@app.route('/chart',methods=['POST'])
def chartwindow():data = request.get_json()city = data['city']obj = weather()# 获取一周天气变化数据week_data = obj.read(1,city)for item in week_data:item['city'] = cityresponse1 = week_data[0:7:1]# 获取24小时气候数据day_data = obj.read(2,city)response2 = {'time_list':[],'data_list1':[],'data_list2':[],'data_list3':[],}for day in day_data:response2['time_list'].append(day['time'])response2['data_list1'].append(day['temperature'])response2['data_list2'].append(day['humidity'])response2['data_list3'].append(day['wind_power'])# 生成返回对象response = {'week_data':response1,'day_data':response2}obj.exit()# 返回对象的json字符串return json.dumps(response)if __name__ == '__main__':app.run(debug=True)

网页展示

基于flask的web项目:出行规划网站

总结分析

一、遇到的问题:

  • 前端发送post请求时会刷新当前界面,导致主页面一直重置,无法显示查询信息;
  • 前端发送的数据被后端显示为乱码;

二、解决方法:

  • 首先需要使用ajax方法发送数据,不要在前端表单处定义submit来提交,而是将按钮绑定我们自定义的函数,然后在该函数中定义一个ajax异步请求提交
  • 在前端定义了一个对象data后,发送时应该使用JSON.stringify(data)来将其转化为json字符串发送,同时后端需要使用request.get_json()方法将其作为json对象接收,然后便可用类似python字典的方式调用对象中的内容;

三、关于知识点分析:

  • ajax请求代码分析:
$.ajax({url: '/flight',method: 'post',data: str,dataType: 'json',contentType: 'application/json; charset=UTF-8',success: function (result) {console.log(result);insert_table(result)},error: function (error) {console.log(error);}})
  • 其中:
    1)url是我们要提交ajax请求的目标url(从当前页面发送,如果只用后缀,表示向当前url的同级或子一级url发送请求);
    2)method是发送请求的方式;
    3)data是请求携带的表单数据;
    4)datatype是我们我们预期接收到的响应数据类型,注意,不是我们发送的数据类型;
    5)contenttype才是申明我们发送的数据是text文本字符串还是json字符串,以及其解码方式;
    6)最后就是定义成功发送ajax请求并接收到响应数据后做的操作,以及在发送请求或接收响应阶段失败时要做的操作。
  • 表格数据插入:
var table=document.getElementById("flight-data");if (table.rows.length>1){for(var n=table.rows.length;n>1;n--){table.deleteRow(n-1);}}for(var i=0;i<data.length;i++) {var row=table.insertRow(table.rows.length);var cell1=row.insertCell(0);var cell2=row.insertCell(1);var cell3=row.insertCell(2);var cell4=row.insertCell(3);var cell5=row.insertCell(4);var cell6=row.insertCell(5);cell1.innerHTML=data[i]['aircraft']+'<br>'+data[i]['company']cell2.innerHTML=data[i]['takeoff_airport']+'<br>'+data[i]['takeoff_time'];cell3.innerHTML=data[i]['duration'];cell4.innerHTML=data[i]['landing_airport']+'<br>'+data[i]['landing_time'];cell5.innerHTML=data[i]['breakfast'];cell6.innerHTML=data[i]['price'];}
  • 其中:
    要先获取到table这一级的元素,然后使用insertRow方法获取到要插入的行号,最后用insertCell定位到该行的每一列元素后,用innerHTML实现数据插入。

四、收获:

  • 通过这次项目,复习并实际运用到了flask、数据库、html、css与Javascript相关的知识,加深了对其中一些函数、方法的理解,并且拓展学习了echarts与less的使用,收获颇丰。

web项目:智能出行规划网站——爬虫+flask+echarts+基础前端(html、css、js、jq)相关推荐

  1. Java Web项目性能测试 - JMeter测试网站吞吐量、反应时间百分比、流量

    Java Web项目性能测试 - JMeter测试网站吞吐量.反应时间百分比.流量 为了衡量.调整.完成Java Web项目的性能指标,满足客户.用户对性能的要求,保证项目上线后能正常运行,以及了解项 ...

  2. 学生网页作业web网页设计实例作业 ——自适应绿色茶叶公司(12页) HTML+CSS+JS网页设计期末课程大作业

    web网页设计实例作业 --自适应绿色茶叶公司(12页) HTML+CSS+JS网页设计期末课程大作业 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. ...

  3. 实现简易版德州扑克|学习麻瓜编程以项目为导向入门前端 HTML+CSS+JS

    实现简易版德州扑克|学习麻瓜编程以项目为导向入门前端 HTML+CSS+JS 实现简易版德州扑克 1.先上达到网页效果图(简易版德州扑克) 2. 代码实现 2.1 HTML和JS代码 2.2 CSS代 ...

  4. HTML5期末大作业:静态购物网站设计——静态购物网站模板11页(前台+后台) HTML+CSS+JS

    HTML5期末大作业:静态购物网站设计--静态购物网站模板11页(前台+后台) HTML+CSS+JS 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. ...

  5. 学生网页作业HTML5期末大作业 静态购物网站设计——静态购物网站模板11页(前台+后台) HTML+CSS+JS

    HTML5期末大作业:静态购物网站设计--静态购物网站模板11页(前台+后台) HTML+CSS+JS 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. ...

  6. web网页设计实例作业 ——自适应绿色茶叶公司(12页) HTML+CSS+JS网页设计期末课程大作业

    web网页设计实例作业 --自适应绿色茶叶公司(12页) HTML+CSS+JS网页设计期末课程大作业 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. ...

  7. [前端项目学习笔记] 200行代码网站首页轮播实现(html,css,js)

    目录 1.设置基本布局 2.添加轮播按钮 3.轮播代码初步实现 4.给按钮添加点击事件实现轮播 5.添加圆点轮播 6.将列表项替换为图片,并给图片加上超链接 7.最终效果 1.设置基本布局 整体布局我 ...

  8. Web前端--HTML+CSS+JS新型冠状病毒射击小游戏

    临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?网页要求的总数量太多?HTML网页作业无从下手?没有合适的模板?等等一系列问题.你想要解决的问题,在这里常见网页设计作业题材有 个 ...

  9. Web前端--HTML+CSS+JS实现圣诞抓礼物小游戏

    临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?网页要求的总数量太多?HTML网页作业无从下手?没有合适的模板?等等一系列问题.你想要解决的问题,在这里常见网页设计作业题材有 个 ...

最新文章

  1. params.success params.success(res.data)
  2. leveldb源码分析:Open启动流程
  3. 同时用引用和指针 int *a;
  4. 书评 | 圈内大佬怎么看编程日历
  5. CSS基础(part15)--元素的隐藏与显示
  6. python招生海报_从原研哉的哲学中学习海报设计
  7. 【源码】常用的人脸识别数据库以及上篇性别识别源码
  8. Java,JavaFX的流畅设计风格进度栏
  9. 【windows phone】CollectionViewSource的妙用
  10. cos大乱斗服务器维护,《COS大乱斗》服务器数据互通公告
  11. Java文档阅读笔记-Spring Boot JDBC
  12. 案例:Oracle 11g RAC 数据库连接数过高处理办法
  13. 【机器学习】一型模糊集和二型模糊集
  14. Ukey双因素身份认证步骤 安当加密
  15. 华为摄像头搜索软件_ZOOM会议软件简要操作说明-安卓手机版
  16. 一款DYI动态桌面壁纸程序
  17. 关于虚拟机Ubuntu联网问题
  18. UVM中的factory机制
  19. 《死神》现队长、原队长和假面的对照
  20. 一文揭开您对手机所有的疑惑

热门文章

  1. dpkg 依赖关系问题-仍未被配置
  2. android多媒体自定义编解码器
  3. 网站关键词优化的细节【长尾关键词的挖掘与筛选】
  4. 如何使用Createjs来编写HTML5游戏(二)使用EaselJS处理图片
  5. 手机恢复出厂设置命令_三星 手机 恢复 出厂 设置 指令 密码 串号
  6. 软件测试有效性指标,如何衡量软件测试的有效性?
  7. PVID(pvid vlan是什么意思)
  8. 奖客富翁系统代码C语言,木马代码-c语言木马代码,最简单的,我保证不做违法的 – 手机爱问...
  9. 《软件定义车辆的风险评估和开发成本优化》 论文学习笔记
  10. 关于 MySQLTransactionRollbackException 异常的排查经历