python制作的天氣預(yù)報(bào)小工具(gui界面)
一.準(zhǔn)備工作
不需要準(zhǔn)備。
二.預(yù)覽
1.啟動(dòng)
啟動(dòng)以后自動(dòng)定位所在城市,展示定位城市的天氣。
2.添加城市
3.展示多個(gè)城市天氣
添加天氣之后能夠顯示多個(gè)城市天氣信息。
三.設(shè)計(jì)流程
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分詞+城市號(hào)+城市天氣) **5.11實(shí)現(xiàn)** 2.用戶手動(dòng)選擇,查看當(dāng)前所選城市天氣 (Toplevel+Combobox) **已實(shí)現(xiàn)** 5-14 1.加入notepad,顯示多個(gè)城市天氣 (notebook Frame) **已實(shí)現(xiàn)** 2.頻繁刷新檢測(線程計(jì)時(shí)10秒) **已實(shí)現(xiàn)** 3.用戶選擇主題 (Menu.add_radiobutton()) **已實(shí)現(xiàn)** 4.一個(gè)窗口多個(gè)Combobox,怎樣處理選擇事件 **已實(shí)現(xiàn)** 5-15 1.右擊notebook frame標(biāo)題出現(xiàn)“關(guān)閉”菜單 **已砍掉** 2.用戶添加了城市后,label出現(xiàn)在了最后的Frame中 **未實(shí)現(xiàn)** ''' imgs=['./img/loading.png'] class App: def __init__(self): self.w=Tk() self.w.title('天氣預(yù)報(bào)小工具-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): ''' 第一次啟動(dòng),展示加載圖片提示信息 :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()}\n當(dāng)前所在地區(qū):{city}\n當(dāng)前氣溫:{item["now"]}\n感冒指數(shù):{item["ganmao"]}') except TypeError: messagebox.showerror('錯(cuò)誤','天氣信息加載失敗!') self.l1_var.set('天氣信息加載失敗!') self.s2.entryconfig('添加城市', state='normal') def refresh_weather(self): """ 刷新天氣后,10秒內(nèi)不能點(diǎn)擊刷新 :return: """ self.s2.entryconfig('刷新', state='disable') self.show_local_weather() self.thread_it(self.wait_time) def wait_time(self): ''' 線程計(jì)時(shí)10s,十秒后刷新按鈕可點(diǎn)擊 :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讓用戶選擇城市,后臺(tái)獲取城市號(hào) :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()}\n當(dāng)前所在地區(qū):{city}\n當(dāng)前氣溫:{item["now"]}\n感冒指數(shù):{item["ganmao"]}') except TypeError: messagebox.showerror('錯(cuò)誤','天氣信息加載失??!') self.fl1_var.set(f'{city}天氣信息加載失敗!') self.t.destroy() def change_tab(self,*args): pass def show_tc2_value(self,event): ''' 展示"市"級(jí)信息 :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ū)/縣"級(jí)信息 :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ū)、縣 獲取城市號(hào) :return: 城市號(hào) """ 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é)巴分詞好費(fèi)時(shí)間啊 jieba_cut_result = jieba.lcut(''.join(location)) try: #去除首位的國家和網(wǎng)絡(luò)類型 del jieba_cut_result[0] del jieba_cut_result[-1] item = {} # 如果結(jié)果為類似 石家莊裕華 則自動(dòng)加入市區(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é)果為類似 北京北京 則自動(dòng)加入市 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): """ 通過省、市在字典中查找對(duì)應(yīng)的城市號(hào) :param province: 省 :param city: 市 :return: 城市號(hào) """ 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ù)報(bào)小工具,基本支持全國每個(gè)省市的天氣預(yù)報(bào),支持歷史天氣(昨天)查看,雖然基本功能能夠?qū)崿F(xiàn),但是仍舊存在兩個(gè)小問題:
1.添加超過兩個(gè)城市天氣后,具體城市信息會(huì)顯示在最新添加的Freame中。
2.ip定位不準(zhǔn)確。
本程序還有兩個(gè)特色:
1.支持更換主題。
2.程序首次啟動(dòng)加入了加載過渡。
其他的彩蛋,您自己去發(fā)現(xiàn)吧!
程序放在了藍(lán)奏云。思路、代碼方面有什么不足歡迎各位大佬指正、批評(píng)!
以上就是python制作的天氣預(yù)報(bào)小工具(gui界面)的詳細(xì)內(nèi)容,更多關(guān)于python 天氣預(yù)報(bào)工具的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Tensorflow數(shù)據(jù)讀取有三種方式(next_batch)
本篇文章主要介紹了Tensorflow數(shù)據(jù)讀取有三種方式(next_batch),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02Python qqbot 實(shí)現(xiàn)qq機(jī)器人的示例代碼
這篇文章主要介紹了Python qqbot 實(shí)現(xiàn)qq機(jī)器人的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07PyQt QListWidget修改列表項(xiàng)item的行高方法
今天小編就為大家分享一篇PyQt QListWidget修改列表項(xiàng)item的行高方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-06-06Python模擬百度自動(dòng)輸入搜索功能的實(shí)例
今天小編就為大家分享一篇Python模擬百度自動(dòng)輸入搜索功能的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-02-02Python3.6.x中內(nèi)置函數(shù)總結(jié)及講解
今天小編就為大家分享一篇關(guān)于Python3.6.x中內(nèi)置函數(shù)總結(jié)及講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02探究Python的Tornado框架對(duì)子域名和泛域名的支持
這篇文章主要介紹了探究Python的Tornado框架對(duì)子域名和泛域名的支持,Tornado作為一個(gè)典型的異步框架、在Python開發(fā)者中的人氣相當(dāng)高,需要的朋友可以參考下2015-05-05pygame實(shí)現(xiàn)一個(gè)類似滿天星游戲流程詳解
這篇文章主要介紹了使用pygame來編寫類滿天星游戲的全記錄,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-09-09詳解Python字符串對(duì)象的實(shí)現(xiàn)
本文介紹了 python 內(nèi)部是如何管理字符串對(duì)象,以及字符串查找操作是如何實(shí)現(xiàn)的,感興趣的小伙伴們可以參考一下2015-12-12