一、項目簡介
本項目使用python語言編寫,采用Flaskweb框架來實現(xiàn)前后端交互,利于開發(fā),維護,前端使用Html和jQuery處理事件,發(fā)送數(shù)據(jù)等,后端采用requests庫,BeautifulSoup庫實現(xiàn)爬取中國氣象局的數(shù)據(jù),清洗轉(zhuǎn)化成對應表格數(shù)據(jù)格式,再使用pyecharts繪制圖形,返回給前端頁面實現(xiàn)實時展示,注意運行本項目需要聯(lián)網(wǎng)?。?!
二、項目演示
輸入你要查詢的城市,點擊搜索即可,由于網(wǎng)速,pyecharts的圖形渲染等因素,圖形展示需等待幾秒才出現(xiàn)。
注意:水球圖的渲染有時出不來,可多次點擊搜索即可,我是這樣的啦!?。ㄅcpyecharts圖形渲染有關(guān))
?
?三、項目的實現(xiàn)
?1.項目包結(jié)構(gòu)展示:
?
其中app.py為項目的啟動文件及路由,templates包存放前端頁面的,service包存放后端邏輯代碼venv為排除目錄(沒啥用可不創(chuàng)建),包可以自己標記為對用的資源目錄,我沒有使用flask模板創(chuàng)建,而是自己標記的
創(chuàng)建好包就可以書寫代碼啦?。。?/p>
2.Service包代碼編寫
?2.1.WeatherDate(爬取天氣數(shù)據(jù))
import sys
import pandas as pd
import requests
from bs4 import BeautifulSoup
# 請求頭可寫,但我看沒報錯,就沒寫
headers = {
'user-agent': '',
'Cookie': ''
}
# 列表劃分,例如[1,2,3,4]=>[[1,2],[3,4]],目的適應pyecharts數(shù)據(jù)
def chunk_list(lst, size):
return [lst[i:i + size] for i in range(0, len(lst), size)]
# 數(shù)據(jù)圖一的部分數(shù)據(jù)列表
def data1(soup):
seven_dayList = [i.text.strip().replace(' ', '').replace('\n', '') for i in soup.select('.day-item')]
temp = []
# 數(shù)據(jù)清洗
j = 0
for i in seven_dayList:
if j % 10 == 0:
temp.append('2023年' + i[3:5] + '月' + i[6:] + '日')
temp.append(i[:3])
if j % 10 == 5:
temp.append(i[:3])
temp.append(i[3:])
if i != '' and j % 10 != 0 and j % 10 != 5:
temp.append(i)
j += 1
# 數(shù)據(jù)格式對應表格
seven_dayLists = chunk_list(temp, 10)
return seven_dayLists
# 圖二表格數(shù)據(jù)列表
def data2(soup):
total_list = [i.text.strip() for i in soup.select('.hour-table td')]
temp = []
for i in total_list:
if i != '' and i != '天氣':
temp.append(i)
# 時間
time_list = []
# 氣溫
temperature_list = []
# 降水
rainfall_list = []
# 風速
windspeed_list = []
# 風向
winddirection_list = []
# 氣壓
pressure_list = []
# 濕度
humidity_list = []
# 云量
cloud_list = []
categories = ['時間', '氣溫', '降水', '風速', '風向', '氣壓', '濕度', '云量']
current_category = None
for item in temp:
if item in categories:
current_category = item
else:
if current_category == '時間':
time_list.append(item)
elif current_category == '氣溫':
temperature_list.append(item)
elif current_category == '降水':
rainfall_list.append(item)
elif current_category == '風速':
windspeed_list.append(item)
elif current_category == '風向':
winddirection_list.append(item)
elif current_category == '氣壓':
pressure_list.append(item)
elif current_category == '濕度':
humidity_list.append(item)
elif current_category == '云量':
cloud_list.append(item)
# 切割
return chunk_list(time_list, 8), chunk_list(temperature_list, 8), chunk_list(rainfall_list, 8), \
chunk_list(windspeed_list, 8), chunk_list(winddirection_list, 8), chunk_list(pressure_list, 8), \
chunk_list(humidity_list, 8), chunk_list(cloud_list, 8)
# 數(shù)據(jù)轉(zhuǎn)換(表格所需)
def data_change(seven_dayList, temperature_list, windspeed_list, pressure_list, humidity_list, cloud_list):
# 將seven_dayList切出最高氣溫和最低氣溫
data_list = []
high_temperature = []
low_temperature = []
for i in seven_dayList:
data_list.append(i[0])
high_temperature.append(eval(i[5][:-1]))
low_temperature.append(eval(i[6][:-1]))
# 將temperature_list轉(zhuǎn)化為數(shù)字
temperature_lists = []
for i in temperature_list:
temperature_lists.append(eval(i[:-1]))
# 將windspeed_list轉(zhuǎn)化為數(shù)字
windspeed_lists = []
for i in windspeed_list:
windspeed_lists.append(eval(i[:-3]))
# 將pressure_list轉(zhuǎn)化為數(shù)字
pressure_lists = []
for i in pressure_list:
pressure_lists.append(eval(i[:-3]))
# 將humidity_list轉(zhuǎn)化為數(shù)字
humidity_lists = []
for i in humidity_list:
humidity_lists.append(round(eval(i[:-1]) / 100, 3))
# 將cloud_list轉(zhuǎn)化為數(shù)字
cloud_lists = []
for i in cloud_list:
cloud_lists.append(eval(i[:-1]))
return data_list, high_temperature, low_temperature, temperature_lists, \
windspeed_lists, pressure_lists, humidity_lists, cloud_lists
# 關(guān)鍵代碼(通過城市名字找到cityinfo.xls文件對應url后的對應代碼)
def weather_data(city):
try:
data = pd.read_excel('F:\PythonXM\WeatherXM\static\cityinfo.xls', index_col='城市名稱')
code = data.loc[city]['對應代碼']
except Exception as e:
print('輸入的城市錯誤,請重新輸入!')
print(e)
sys.exit()
# 對應城市的url地址
url = 'https://weather.cma.cn/web/weather/{}.html'.format(code)
res = requests.get(url=url, headers=headers).content.decode('utf-8')
soup = BeautifulSoup(res, 'lxml')
seven_dayList = data1(soup)
time_list, temperature_list, rainfall_list, windspeed_list, winddirection_list, \
pressure_list, humidity_list, cloud_list = data2(soup)
# 測試數(shù)據(jù)(上面是所有數(shù)據(jù)可自行選取,清洗)
# print(seven_dayList)
# print(time_list)
# print(temperature_list)
# print(rainfall_list)
# print(windspeed_list)
# print(winddirection_list)
# print(pressure_list)
# print(humidity_list)
# print(cloud_list)
# 只取當日數(shù)據(jù)和所需表格數(shù)據(jù)
data_list, high_temperature, low_temperature, temperature_lists, \
windspeed_lists, pressure_lists, humidity_lists, cloud_lists = \
data_change(seven_dayList, temperature_list[0], windspeed_list[0],
pressure_list[0], humidity_list[0], cloud_list[0])
# 測試數(shù)據(jù)
# print(data_list)
# print(time_list)
# print(high_temperature)
# print(low_temperature)
# print(temperature_lists)
# print(windspeed_lists)
# print(pressure_lists)
# print(humidity_lists)
# print(cloud_lists)
return seven_dayList, data_list, high_temperature, low_temperature, time_list[0], temperature_lists, \
windspeed_lists, pressure_lists, humidity_lists, cloud_lists
if __name__ == '__main__':
weather_data('張家界')
思路:其中weather_data函數(shù)為關(guān)鍵,其他的為數(shù)據(jù)清洗,這里感謝gitee上的一位博主(忘記叫啥了,哈哈)的cityinfo.xls文件,通過輸入的城市,找到對應的尾部代號,進而請求該城市頁面的數(shù)據(jù),沒這文件可以使用selenium去獲?。ㄟ@也是一種思路),不過會很慢?。?strong>文件在下面?。。。?/span>
cityinfo.xls文件:
?
遇到的問題點:pandas在讀取包文件時,使用相對路徑'../static/cityinfo.xls',在下面的測試沒問題,但當項目運行時老是找不到該文件路徑,故寫的絕對路徑才得以解決,至于原因,暫未知,有知道歡迎在下面討論!
?2.2.DataShow(數(shù)據(jù)的可視化圖表)
from pyecharts.globals import SymbolType
from pyecharts.options import ComponentTitleOpts
from WeatherXM.service.weatherData import weather_data
import pyecharts.options as opts
from pyecharts.charts import Line, Liquid, EffectScatter, Gauge, Timeline, Scatter, Page, Grid, Pie
from pyecharts.components import Table
# # 測試數(shù)據(jù)獲取
# seven_dayList, data_list, high_temperature, low_temperature, time_list, temperature_lists, \
# windspeed_lists, pressure_lists, humidity_lists, cloud_lists = weather_data('上海')
# 標題
def tab(name, color) -> Pie: # 作為標題
tab = (
Pie(init_opts=opts.InitOpts(width='100%',height='100px')).
set_global_opts(title_opts=opts.TitleOpts(title=name, pos_left='center', pos_top='center',
title_textstyle_opts=opts.TextStyleOpts(color=color, font_size=35))))
return tab
# 需求一:當日溫度變化曲線
def temperature_line(hour, temperature) -> Line:
line = (
Line(init_opts=opts.InitOpts(width='50%'))
.add_xaxis(hour)
.add_yaxis("溫度", temperature, is_connect_nones=True)
.set_series_opts(label_opts=opts.LabelOpts(formatter='{@[1]}℃'))
.set_global_opts(title_opts=opts.TitleOpts(title="當日平均溫度變化曲線"), yaxis_opts=opts.AxisOpts(
type_='value',
axislabel_opts=opts.LabelOpts(formatter="{value} ℃")))
)
return line
# 需求二:當日平均濕度
def humidity_liquid(humidity) -> Liquid():
liquid = (
Liquid(init_opts=opts.InitOpts(width='50%'))
# .add("lq", [0.6, 0.7])
.add("濕度", humidity, is_outline_show=False)
.set_global_opts(title_opts=opts.TitleOpts(title="當日平均濕度"))
)
return liquid
# 需求三:當日風速變化:
def windspeed_effectScatter(hour, windspeed) -> EffectScatter:
effectScatter = (
EffectScatter(init_opts=opts.InitOpts(width='50%'))
.add_xaxis(hour)
.add_yaxis("風速", windspeed, symbol=SymbolType.ARROW)
.set_series_opts(label_opts=opts.LabelOpts(formatter='{@[1]}m/s'))
.set_global_opts(title_opts=opts.TitleOpts(title="當日風速變化"), yaxis_opts=opts.AxisOpts(
type_='value',
axislabel_opts=opts.LabelOpts(formatter="{value}m/s")))
)
return effectScatter
# 需求三:當日最高最低氣溫變化曲線
def high_low_temperature_line(hour, high_temperature, low_temperature) -> Line:
line = (
Line(init_opts=opts.InitOpts(width='50%'))
.add_xaxis(xaxis_data=hour)
.add_yaxis(
series_name="白天氣溫",
y_axis=high_temperature,
markpoint_opts=opts.MarkPointOpts(
data=[
opts.MarkPointItem(type_="max", name="最大值"),
opts.MarkPointItem(type_="min", name="最小值"),
]
),
markline_opts=opts.MarkLineOpts(
data=[opts.MarkLineItem(type_="average", name="平均值")]
),
)
.add_yaxis(
series_name="夜晚氣溫",
y_axis=low_temperature,
markpoint_opts=opts.MarkPointOpts(
data=[
opts.MarkPointItem(type_="max", name="最大值"),
opts.MarkPointItem(type_="min", name="最小值"),
]
),
markline_opts=opts.MarkLineOpts(
data=[opts.MarkLineItem(type_="average", name="平均值")]
),
)
.set_series_opts(label_opts=opts.LabelOpts(formatter='{@[1]}℃'))
.set_global_opts(
title_opts=opts.TitleOpts(title="七日最高最低氣溫變化曲線"),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
toolbox_opts=opts.ToolboxOpts(is_show=True),
xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False),
yaxis_opts=opts.AxisOpts(
type_='value',
axislabel_opts=opts.LabelOpts(formatter="{value} ℃"))
)
)
return line
# 需求四:當日氣壓
def pressure_gauge(hour, pressure) -> Timeline:
tl = Timeline(init_opts=opts.InitOpts(width='50%'))
for i in range(len(hour)):
gauge = (
Gauge()
.set_global_opts(title_opts=opts.TitleOpts(title="氣壓儀表盤"),
legend_opts=opts.LegendOpts(is_show=False))
.add(min_=0, max_=1500, data_pair=[(f'{hour[i]}時氣壓', pressure[i])], series_name=f'氣壓',
detail_label_opts=opts.GaugeDetailOpts(formatter="{value}hPa"))
)
tl.add(gauge, '{}'.format(hour[i]))
return tl
# 需求五:當日云量散點圖
def cloud_scatter(hour, cloud) -> Scatter:
scatter = (
Scatter(init_opts=opts.InitOpts(width='50%'))
.add_xaxis(hour)
.add_yaxis("云量", cloud)
.set_series_opts(label_opts=opts.LabelOpts(formatter='{@[1]}%'))
.set_global_opts(
title_opts=opts.TitleOpts(title="當日云量散點圖"),
visualmap_opts=opts.VisualMapOpts(type_="size", max_=150, min_=20),
yaxis_opts=opts.AxisOpts(
type_='value',
axislabel_opts=opts.LabelOpts(formatter="{value}%"))
)
)
return scatter
# 需求六:七日天氣預報表格
def seven_day_table(seven_day) -> Table:
headers = ['日期', '星期', '白天天氣', '白天風向', '白天風力', '白天氣溫', '晚上氣溫', '晚上天氣', '晚上風向', '晚上風力']
table = (
Table()
.add(headers, seven_day)
.set_global_opts(
title_opts=ComponentTitleOpts(title="七日天氣預報")
)
)
return table
# 需求匯總
def get_chart(city):
# 數(shù)據(jù)獲取
seven_dayList, data_list, high_temperature, low_temperature, time_list, temperature_lists, \
windspeed_lists, pressure_lists, humidity_lists, cloud_lists = weather_data(city)
page = Page(layout=Page.SimplePageLayout)
page.add(
tab(f'{city}未來七日天氣預報', '#000000'),
temperature_line(time_list, temperature_lists),
high_low_temperature_line(data_list, high_temperature, low_temperature),
humidity_liquid(humidity_lists),
pressure_gauge(time_list, pressure_lists),
windspeed_effectScatter(time_list, windspeed_lists),
cloud_scatter(time_list, cloud_lists),
seven_day_table(seven_dayList)
)
# page.render("page_draggable_layout.html")
return page
# def get_chart(city):
# # 數(shù)據(jù)獲取
# seven_dayList, data_list, high_temperature, low_temperature, time_list, temperature_lists, \
# windspeed_lists, pressure_lists, humidity_lists, cloud_lists = weather_data(city)
# return temperature_line(time_list, temperature_lists), \
# humidity_liquid(humidity_lists), \
# windspeed_effectScatter(time_list, windspeed_lists), \
# high_low_temperature_line(data_list, high_temperature, low_temperature), \
# pressure_gauge(time_list, pressure_lists), \
# cloud_scatter(time_list, cloud_lists), \
# seven_day_table(seven_dayList)
if __name__ == '__main__':
get_chart('衡陽市')
思路:圖表的繪制就沒什么可說的了,可自行查閱官方文檔:簡介 - pyecharts - A Python Echarts Plotting Library built with love.
這里務必使用pyecharts的組合圖表將多個圖組合在一起返回,不要像get_charts函數(shù)一樣返回多個圖表類?。?!
?
組合圖表建議使用這種,當時也使用了Grid,但繪制的圖表,位置有問題不規(guī)范好看,自己不熟練的問題(教訓:多看看官方文檔,害我想了好久?。。?/p>
?
?3.templates包前端代碼以及app.py文件的書寫
3.1.app.py文件的書寫
from flask import Flask, render_template, request, send_from_directory, render_template_string
from pyecharts.render import make_snapshot, snapshot
from WeatherXM.service.dataShow import get_chart
app = Flask(__name__, static_folder="static", template_folder='templates')
@app.route("/")
def index():
return render_template("index.html")
@app.route("/getdata", methods=['GET'])
def get_weather_data():
city = request.args.get('city')
page = get_chart(city)
# page.render('page.html')
# print(a.dump_options_with_quotes())
# return send_from_directory('../', 'page.html') # 成功
# return render_template('page1.html', chart=page.render_embed()) # 失敗
chart = page.render_embed()
return render_template_string(chart)
if __name__ == "__main__":
app.run()
3.2.index.html文件的書寫?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Awesome-pyecharts</title>
<!-- <script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>-->
<!-- <script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>-->
<!-- 引入 Pyecharts 以及 Liquid Chart 組件的依賴庫 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@4.9.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pyecharts@1.9.0/dist/pyecharts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@2.1.0/echarts-liquidfill.min.js"></script>
<link rel="stylesheet">
<link rel="stylesheet" href="../static/main.css" type="text/css">
</head>
<body>
<div class="title">
<span>天氣查詢:</span>
<input placeholder="請輸入城市" type="text" id="city">
<button id="getdata">搜索</button>
</div>
<!--<div id="a" style="width:1000px; height:600px;"></div>-->
<div id="a"></div>
<!-- <div id="chartContainer">-->
<!-- <iframe src="{{ url_for('static', filename='page.html') }}" width="100%" frameborder="0"></iframe>-->
<!-- </div>-->
<script>
$("#getdata").click(function () {
let city = $("#city").val()
if (city === '') {
alert('輸入錯誤,請重新輸入')
return;
}
echarts.init(document.getElementById('a'), 'white', {renderer: 'canvas'})
// let chart = echarts.init(document.getElementById('a'), 'white', {renderer: 'canvas'})
$.ajax({
type: "GET",
url: "/getdata",
// dataType: 'json/text',
data: {'city': city},
success: function (result) {
// chart.setOption(result)
$('#a').html(result);
// // 將獲取到的頁面插入到指定的容器中
// var chartContainer = document.getElementById('a');
// chartContainer.innerHTML = result;
},
error: function (result) {
alert('輸入的城市不存在,請重新輸入!')
}
})
}
)
</script>
</body>
</html>
思路:通過點擊搜索,發(fā)送數(shù)據(jù)給后端,后端返回html字符串,前端使用使用html解析
遇到的問題點:這里我使用的是使用?render_template_string函數(shù)來渲染簡單的模板或動態(tài)生成的 HTML 內(nèi)容,并返回給前端,也可以使用send_from_directory函數(shù)返回繪制好的Page.html文件
這里為什么不采用官網(wǎng)的a.dump_options_with_quotes()(本質(zhì)是將a這個圖類轉(zhuǎn)化為HTML)呢?
答:因為不能返回多個HTML,不然前端無法解析,也不能各自轉(zhuǎn)化為json數(shù)據(jù),前端接收不到
這里之前我的思路是將多個圖類返回,使用a.dump_options_with_quotes()直接返回列表給前端,或使用jsons數(shù)據(jù)返回,但前端就是接收不到,弄得我好煩,之后看了flask的文檔看到了這兩個方法!!有興趣的可以試試其他的!!
4.static(資源文件)
?
?思路:這里一定要配置static_folder,template_folder,不然加載不出來的,不要問我為什么,嗚嗚嗚??!
cityinfo.xls文件地址:cityinfo.xls文件
?
至此,項目就寫完了,可以準備啟動了
?四、項目啟動
點擊右上角的編輯配置,配置flask服務器:
?配置好點擊運行,點擊地址就行了
gitee地址:
python+pyecharts+flask+爬蟲實現(xiàn)實時天氣查詢可視化: 本項目使用python語言編寫,采用Flaskweb框架來實現(xiàn)前后端交互,利于開發(fā),維護,前端使用Html和jQuery處理事件,發(fā)送數(shù)據(jù)等,后端采用requests庫,BeautifulSoup庫實現(xiàn)爬取中國氣象局的數(shù)據(jù),清洗轉(zhuǎn)化成對應表格數(shù)據(jù)格式,再使用pyecharts繪制圖形,返回給前端頁面實現(xiàn)實時展示,注意運行本項目需要聯(lián)網(wǎng)?。?! (gitee.com)https://gitee.com/TheQuietCoder/WeatherXM
五、項目心得
整個項目說起來也簡單,但其實問題也挺多的,不斷遇到bug,不斷解決bug,有時候為了解決一個bug,網(wǎng)頁都翻爛了都找不到解決方法,這個過程是需要耐心的,當你真正解決了它,回過頭看也不過如此,我相信這些bug終將成為你成功的墊腳石,回首望去,輕舟已過萬重山??!文章來源:http://www.zghlxwxcb.cn/news/detail-772135.html
項目書寫,碼字不易,希望各位大佬們能留個贊??吧!?。?span toymoban-style="hidden">文章來源地址http://www.zghlxwxcb.cn/news/detail-772135.html
到了這里,關(guān)于python+pyecharts+flask+爬蟲實現(xiàn)實時天氣查詢可視化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!