一.準(zhǔn)備工作
python開發(fā)環(huán)境
二.預(yù)覽
1.啟動
啟動以后自動定位所在城市,展示定位城市的天氣。
2.添加城市
3.展示多個城市天氣
添加天氣之后能夠顯示多個城市天氣信息。
三.設(shè)計流程
1.獲取城市天氣信息過程
用此流程圖展示定位城市信息到獲取城市天氣信息過程。
四.源代碼
1.Weather_Tool-v1.0.py
from tkinter import *
from tkinter import ttk
from PIL import Image,ImageTk
from tkinter import messagebox
from Weather_Spider import Weather_Get
from threading import Thread
import datetime
import time
'''
5-1
1.打開首頁定位當(dāng)前位置獲取天氣 (ip定位+jieba分詞+城市號+城市天氣) **5.11實現(xiàn)**
2.用戶手動選擇,查看當(dāng)前所選城市天氣 (Toplevel+Combobox) **已實現(xiàn)**
5-14
1.加入notepad,顯示多個城市天氣 (notebook Frame) **已實現(xiàn)**
2.頻繁刷新檢測(線程計時10秒) **已實現(xiàn)**
3.用戶選擇主題 (Menu.add_radiobutton()) **已實現(xiàn)**
4.一個窗口多個Combobox,怎樣處理選擇事件 **已實現(xiàn)**
5-15
1.右擊notebook frame標(biāo)題出現(xiàn)“關(guān)閉”菜單 **已砍掉**
2.用戶添加了城市后,label出現(xiàn)在了最后的Frame中 **未實現(xiàn)**
'''
imgs=['./img/loading.png']
class App:
def __init__(self):
self.w=Tk()
self.w.title('天氣預(yù)報小工具-v1.0')
width=600
height=282
left=(self.w.winfo_screenwidth()-width)/2
top=(self.w.winfo_screenheight()-height)/2
self.w.geometry('%dx%d+%d+%d'%(width,height,left,top))
self.w.iconbitmap('biticon.ico')
self.w.resizable(False,False)
self.cerate_widgets()
self.first_launch()
self.set_widgets()
self.place_widgets()
self.thread_it(self.show_local_weather)
self.w.mainloop()
def cerate_widgets(self):
self.note=ttk.Notebook()
self.f1=Frame()
self.tree=ttk.Treeview(self.f1)
self.l1_var=StringVar()
self.l1=ttk.Label(self.f1,textvariable=self.l1_var)
self.m=Menu(self.w)
self.w['menu']=self.m
self.s1=Menu(self.m,tearoff=False)
self.s2=Menu(self.m,tearoff=False)
self.s3=Menu(self.m,tearoff=False)
def set_widgets(self):
self.location=[]
style = ttk.Style(self.w)
style.theme_use("default")
columns=('rq','tq','flfx','zdqw','zgqw')
self.tree.config(show='headings',columns=columns)
self.tree.column(columns[0],anchor=CENTER,minwidth=95,width=110)
self.tree.column(columns[1],anchor=CENTER,minwidth=60,width=70)
self.tree.column(columns[2],anchor=CENTER,minwidth=90,width=100)
self.tree.column(columns[3],anchor=CENTER,minwidth=90,width=100)
self.tree.column(columns[4],anchor=CENTER,minwidth=90,width=100)
self.tree.heading('rq', text='日期')
self.tree.heading('tq', text='天氣')
self.tree.heading('flfx', text='風(fēng)向風(fēng)力')
self.tree.heading('zdqw', text='最低氣溫')
self.tree.heading('zgqw', text='最高氣溫')
self.m.add_cascade(label='開始',menu=self.s1)
self.s1.add_command(label='aaa',command='')
self.s1.add_separator()
self.s1.add_command(label='退出',command=self.quit_window)
self.m.add_cascade(label='操作',menu=self.s2)
self.s2.add_command(label='刷新',command=lambda:self.thread_it(self.refresh_weather))
self.s2.add_command(label='添加城市',command=lambda:self.thread_it(self.select_city),state='disable')
s2_sub = Menu(self.s2, tearoff=0)
self.s2.add_separator()
self.s2.add_cascade(label='更換主題',menu=s2_sub)
self.m.add_cascade(label='關(guān)于',menu=self.s3)
self.s3.add_command(label='關(guān)于作者',command=lambda :messagebox.showinfo('關(guān)于作者','作者很神秘,什么都沒留下'))
self.tree.tag_configure('evenColor',background='lightblue')
self.w.protocol('WM_DELETE_WINDOW',self.quit_window)
themes=[ 'default','clam', 'alt', 'classic']
self.themevar=StringVar()
for i,t in enumerate(themes):
s2_sub.add_radiobutton(label=t,variable=self.themevar,command=lambda:self.thread_it(self.change_theme),value=t)
self.themevar.set('default')
def place_widgets(self):
self.note.place(x=0,y=0,width=600,height=282)
self.tree.place(x=0,y=0,width=600,height=150)
self.l1.place(x=0,y=150,height=85,width=600)
def first_launch(self):
'''
第一次啟動,展示加載圖片提示信息
:return:
'''
self.start_time=time.time()
paned = PanedWindow(self.w)
self.img = imgs
img = Image.open(self.img[0])
paned.image = ImageTk.PhotoImage(img)
self.load_img = Label(self.w, image=paned.image)
self.load_lab = Label(self.w, text='Loading...')
self.load_img.pack()
self.load_lab.pack()
def show_local_weather(self):
'''
展示定位天氣信息
:return:
'''
self.l1_var.set('正在刷新天氣......')
items = self.tree.get_children()
for item in items:
self.tree.delete(item)
try:
city,item=Weather_Get().get_local_weather()
self.load_img.destroy()
self.load_lab.destroy()
self.s2.entryconfig('添加城市', state='normal')
self.note.add(self.f1,text=city)
i=0
for data in item['recent']:
self.tree.insert('', i, values=(
data.get('日期'), data.get('天氣'), data.get('風(fēng)力風(fēng)向'), data.get('最低氣溫'), data.get('最高氣溫')))
i+=1
self.l1_var.set(f'今天:{self.show_date()}
當(dāng)前所在地區(qū):{city}
當(dāng)前氣溫:{item["now"]}
感冒指數(shù):{item["ganmao"]}')
except TypeError:
messagebox.showerror('錯誤','天氣信息加載失敗!')
self.l1_var.set('天氣信息加載失敗!')
self.s2.entryconfig('添加城市', state='normal')
def refresh_weather(self):
"""
刷新天氣后,10秒內(nèi)不能點擊刷新
:return:
"""
self.s2.entryconfig('刷新', state='disable')
self.show_local_weather()
self.thread_it(self.wait_time)
def wait_time(self):
'''
線程計時10s,十秒后刷新按鈕可點擊
:return:
'''
time.sleep(10)
self.s2.entryconfig('刷新', state='normal')
def show_date(self):
"""
展示日期信息,便于天氣展示
:return:
"""
date = str(datetime.date.today())
year,month,day=date.split('-')
week_day_dict = {
0: '星期一',
1: '星期二',
2: '星期三',
3: '星期四',
4: '星期五',
5: '星期六',
6: '星期日 ',
}
now=datetime.datetime.now()
date_index = now.weekday()
return f'{year}年{month}月{day}日 {week_day_dict[date_index]}'
def select_city(self):
'''
Toplevel讓用戶選擇城市,后臺獲取城市號
:return:
'''
self.t=Toplevel()
self.t.resizable(0,0)
width=300
height=140
left=(self.t.winfo_screenwidth()-width)/2
top=(self.t.winfo_screenheight()-height)/2
self.t.geometry('%dx%d+%d+%d'%(width,height,left,top))
self.t.title('選擇城市')
self.tl1=ttk.Label(self.t,text='請選擇城市:')
self.tl1.pack()
provinces=Weather_Get().get_provinces()
self.tc1=ttk.Combobox(self.t,justify='center',state='readonly',value=provinces)
self.tc2=ttk.Combobox(self.t,justify='center',state='readonly')
self.tc1.pack()
self.tc1.bind('<<ComboboxSelected>>',self.show_tc2_value)
self.tc2.bind('<<ComboboxSelected>>',self.show_tc3_value)
self.tc2.pack()
self.tc3=ttk.Combobox(self.t,justify='center',state='readonly')
self.tc3.pack()
self.tb1=ttk.Button(self.t,text='選擇',command=lambda :self.thread_it(self.ack_city))
self.tb1.pack(pady=10)
#----待完善
def ack_city(self):
'''
Toplevel中選擇了城市,選擇使用notebook中建立Frame展示所選城市信息
:return:
'''
cityno=self.get_city_no()
weather_item=Weather_Get().get_weather(cityno)
location=self.province_name+self.city_name+self.region
if location in self.location:
messagebox.showwarning('警告','此城市已添加,請勿重復(fù)添加!')
else:
self.location.append(location)
self.f2= Frame(takefocus=True)
self.note.add(self.f2, text=location)
self.tree2 = ttk.Treeview(self.f2)
columns = ('rq', 'tq', 'flfx', 'zdqw', 'zgqw')
self.tree2.config(show='headings', columns=columns)
self.tree2.column(columns[0], anchor=CENTER, minwidth=95, width=110)
self.tree2.column(columns[1], anchor=CENTER, minwidth=60, width=70)
self.tree2.column(columns[2], anchor=CENTER, minwidth=90, width=100)
self.tree2.column(columns[3], anchor=CENTER, minwidth=90, width=100)
self.tree2.column(columns[4], anchor=CENTER, minwidth=90, width=100)
self.tree2.heading('rq', text='日期')
self.tree2.heading('tq', text='天氣')
self.tree2.heading('flfx', text='風(fēng)向風(fēng)力')
self.tree2.heading('zdqw', text='最低氣溫')
self.tree2.heading('zgqw', text='最高氣溫')
self.tree2.place(x=0,y=0,width=600,height=150)
# label_='label'+str(self.click_no)
# label_var='label'+str(self.click_no)+'_var'
self.fl1_var=StringVar()
self.fl1=ttk.Label(self.f2,textvariable=self.fl1_var)
self.fl1.place(x=0,y=150,height=85,width=600)
items = self.tree2.get_children()
for item in items:
self.tree2.delete(item)
try:
item = weather_item
city=location
i = 0
for data in item['recent']:
self.tree2.insert('', i, values=(
data.get('日期'), data.get('天氣'), data.get('風(fēng)力風(fēng)向'), data.get('最低氣溫'), data.get('最高氣溫')))
i += 1
self.fl1_var.set(f'今天:{self.show_date()}
當(dāng)前所在地區(qū):{city}
當(dāng)前氣溫:{item["now"]}
感冒指數(shù):{item["ganmao"]}')
except TypeError:
messagebox.showerror('錯誤','天氣信息加載失??!')
self.fl1_var.set(f'{city}天氣信息加載失敗!')
self.t.destroy()
def change_tab(self,*args):
pass
def show_tc2_value(self,event):
'''
展示"市"級信息
:param event:
:return:
'''
self.tc2.config(value=[])
self.tc3.config(value=[])
self.province_name=self.tc1.get()
cities=Weather_Get().get_cities(self.province_name)
self.tc2.config(value=cities)
def show_tc3_value(self,event):
'''
展示"區(qū)/縣"級信息
:param event:
:return:
'''
self.city_name=self.tc2.get()
regions=Weather_Get().get_regions(self.province_name,self.city_name)
self.tc3.config(value=regions)
def get_city_no(self):
"""
根據(jù)省、市、區(qū)、縣 獲取城市號
:return: 城市號
"""
self.region=self.tc3.get()
city_no=Weather_Get().get_city_id_by_add(self.province_name,self.city_name,self.region)
return city_no
def change_theme(self,):
'''
更換主題
:return:
'''
theme=self.themevar.get()
style = ttk.Style(self.w)
style.theme_use(theme)
def quit_window(self):
ret=messagebox.askyesno('退出','是否要退出?')
if ret:
self.w.destroy()
def thread_it(self,func,*args):
'''
防止線程沖突
:param func:
:param args:
:return:
'''
t=Thread(target=func,args=args)
t.setDaemon(True)
t.start()
if __name__ == '__main__':
a=App()
2.Weather_Spider.py
#coding:utf-8
import requests
import json
from lxml import etree
import jieba
class Weather_Get():
def __init__(self):
self.base_ip_url='http://ip-api.com/json'
self.location_url='https://ip.tool.chinaz.com/'
#獲取中國國內(nèi)城市--number接口
self.city_number_url='http://static.2ktq.com/sktq/common/city_China.json'
#天氣查詢接口
self.base_weather_url='http://wthrcdn.etouch.cn/weather_mini?citykey={}'
self.headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
}
self.item=self.get_city_item()
def request(self,url,headers):
"""
請求url,可自定義請求頭
:param url: 請求的url
:param headers: 自定義的請求頭
:return: 網(wǎng)頁文本數(shù)據(jù)
"""
s=requests.session()
s.keep_alive=False
try:
r=s.get(url,headers=headers)
r.encoding='utf-8'
if r.status_code==200:
r.encoding = r.apparent_encoding
return r.text
else:
return None
except requests.exceptions.ConnectionError:
return None
def get_city(self):
"""
通過ip定位到當(dāng)前城市
:return:所在省市位置信息
"""
my_headers={
'Connection': 'keep-alive',
'Host': 'ip.tool.chinaz.com',
'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Upgrade-Insecure-Requests': '1'
}
res = etree.HTML(self.request(self.location_url,headers=my_headers))
location = res.xpath('//div[@class="WhoIpWrap jspu"]//span[@class="Whwtdhalf w30-0 lh24 tl ml80"]/em/text()')
#結(jié)巴分詞好費時間啊
jieba_cut_result = jieba.lcut(''.join(location))
try:
#去除首位的國家和網(wǎng)絡(luò)類型
del jieba_cut_result[0]
del jieba_cut_result[-1]
item = {}
# 如果結(jié)果為類似 石家莊裕華 則自動加入市區(qū)
if jieba_cut_result[0]!=jieba_cut_result[1]:
item['province'] = jieba_cut_result[0] + '市'
item['city'] = jieba_cut_result[1] + "區(qū)"
return item
else:
# 如果結(jié)果為類似 北京北京 則自動加入市
item['province'] = jieba_cut_result[0] + '市'
item['city'] = jieba_cut_result[1] + "市"
return item
except IndexError:
return False
def get_city_item(self):
res =self.request(self.city_number_url,headers=self.headers)
item=eval("{'cities':"+res+"}")
return item
def get_provinces(self):
province=[p for p in self.item['cities']]
#print(province)
return province
def get_cities(self,province):
cities_=self.item['cities'][province]
cities=[city for city in cities_.keys()]
return cities
def get_regions(self,province,city):
regions_=self.item['cities'][province][city]
regions=[region for region in regions_.keys()]
#print(province,city,regions)
return regions
def get_city_id_by_add(self,province,city,region=''):
if region=='':
city_no=self.item['cities'][province][city][city].replace('CN','')
else:
city_no=self.item['cities'][province][city][region].replace('CN','')
return city_no
def get_cityid(self,province,city):
"""
通過省、市在字典中查找對應(yīng)的城市號
:param province: 省
:param city: 市
:return: 城市號
"""
if province in self.item['cities'].keys():
try:
#河北省唐山市唐山市(通常的省市)
number=self.item['cities'][province].get(city).get(city).replace('CN','')
return number
except AttributeError:
number=self.item['cities'][province].get(province).get(city).replace('CN','')
return number
else:
print('未檢索到關(guān)于{}{}的信息!'.format(province,city))
def get_weather(self,number):
weather_data = json.loads(self.request(self.base_weather_url.format(number),self.headers))
# pprint.pprint(weather_data)
data=weather_data['data']
item={}
yesterday={}
item_list=[]
yesterday['日期']=data['yesterday']['date']+'(昨天)'
item['now']=data['wendu']+'℃'
item['ganmao']=data['ganmao']
yesterday['天氣']=data['yesterday']['type']
yesterday['風(fēng)力風(fēng)向']=data['yesterday']['fx']+data['yesterday']['fl'].replace('<![CDATA[','').replace(']]>','')
yesterday['最低氣溫']=data['yesterday']['low'].replace('低溫 ','')
yesterday['最高氣溫']=data['yesterday']['high'].replace('高溫 ','')
item_list.append(yesterday)
count=0
for weateher in data['forecast']:
item2={}
if count==0:
date=weateher['date']+'(今天)'
elif count==1:
date=weateher['date']+'(明天)'
elif count==2:
date=weateher['date']+'(后天)'
else:
date=weateher['date']+f'({count-1}天后)'
item2['日期']=date
item2['天氣'] = weateher['type']
item2['風(fēng)力風(fēng)向']=weateher['fengxiang']+weateher['fengli'].replace('<![CDATA[','').replace(']]>','')
item2['最低氣溫'] = weateher['low'].replace('低溫 ', '')
item2['最高氣溫'] = weateher['high'].replace('高溫 ', '')
item_list.append(item2)
count+=1
item['recent']=item_list
return item
def get_local_weather(self):
item=Weather_Get().get_city()
if item:
p=item['province']
c=item['city']
number=Weather_Get().get_cityid(p,c)
weather=Weather_Get().get_weather(number)
return p+c,weather
else:
return False
五.總結(jié)
本次使用Tkinter寫了一款天氣預(yù)報小工具,基本支持全國每個省市的天氣預(yù)報,支持歷史天氣(昨天)查看,雖然基本功能能夠?qū)崿F(xiàn),但是仍舊存在兩個小問題:
1.添加超過兩個城市天氣后,具體城市信息會顯示在最新添加的Freame中。
2.ip定位不準(zhǔn)確。
本程序還有兩個特色:
1.支持更換主題。
2.程序首次啟動加入了加載過渡。
小結(jié)
以上就是python制作天氣預(yù)報工具的詳細開發(fā)過程,更多學(xué)習(xí)python GUI頁面開發(fā)的資料請關(guān)注W3Cschool其它相關(guān)文章!