一.准备工作
不需要准备。
二.预览
1.启动
启动以后自动定位所在城市,展示定位城市的天气。
2.添加城市
3.展示多个城市天气
添加天气之后能够显示多个城市天气信息。
三.设计流程
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.打开首页定位当前位置获取天气 (ip定位+jieba分词+城市号+城市天气) **5.11实现** 2.用户手动选择,查看当前所选城市天气 (Toplevel+Combobox) **已实现** 5-14 1.加入notepad,显示多个城市天气 (notebook Frame) **已实现** 2.频繁刷新检测(线程计时10秒) **已实现** 3.用户选择主题 (Menu.add_radiobutton()) **已实现** 4.一个窗口多个Combobox,怎样处理选择事件 **已实现** 5-15 1.右击notebook frame标题出现“关闭”菜单 **已砍掉** 2.用户添加了城市后,label出现在了最后的Frame中 **未实现** \'\'\' imgs=[\'./img/loading.png\'] class App: def __init__(self): self.w=Tk() self.w.title(\'天气预报小工具-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=\'风向风力\') 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=\'关于\',menu=self.s3) self.s3.add_command(label=\'关于作者\',command=lambda :messagebox.showinfo(\'关于作者\',\'作者很神秘,什么都没留下\')) 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(\'风力风向\'), data.get(\'最低气温\'), data.get(\'最高气温\'))) i+=1 self.l1_var.set(f\'今天:{self.show_date()}\\n当前所在地区:{city}\\n当前气温:{item[\"now\"]}\\n感冒指数:{item[\"ganmao\"]}\') except TypeError: messagebox.showerror(\'错误\',\'天气信息加载失败!\') self.l1_var.set(\'天气信息加载失败!\') self.s2.entryconfig(\'添加城市\', state=\'normal\') def refresh_weather(self): \"\"\" 刷新天气后,10秒内不能点击刷新 :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(\'警告\',\'此城市已添加,请勿重复添加!\') 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=\'风向风力\') 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(\'风力风向\'), data.get(\'最低气温\'), data.get(\'最高气温\'))) i += 1 self.fl1_var.set(f\'今天:{self.show_date()}\\n当前所在地区:{city}\\n当前气温:{item[\"now\"]}\\n感冒指数:{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): \'\'\' 展示\"区/县\"级信息 :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): \"\"\" 根据省、市、区、县 获取城市号 :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/\' #获取中国国内城市--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: 网页文本数据 \"\"\" 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定位到当前城市 :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()\') #结巴分词好费时间啊 jieba_cut_result = jieba.lcut(\'\'.join(location)) try: #去除首位的国家和网络类型 del jieba_cut_result[0] del jieba_cut_result[-1] item = {} # 如果结果为类似 石家庄裕华 则自动加入市区 if jieba_cut_result[0]!=jieba_cut_result[1]: item[\'province\'] = jieba_cut_result[0] + \'市\' item[\'city\'] = jieba_cut_result[1] + \"区\" return item else: # 如果结果为类似 北京北京 则自动加入市 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): \"\"\" 通过省、市在字典中查找对应的城市号 :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(\'未检索到关于{}{}的信息!\'.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[\'风力风向\']=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[\'风力风向\']=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
五.总结
本次使用Tkinter写了一款天气预报小工具,基本支持全国每个省市的天气预报,支持历史天气(昨天)查看,虽然基本功能能够实现,但是仍旧存在两个小问题:
1.添加超过两个城市天气后,具体城市信息会显示在最新添加的Freame中。
2.ip定位不准确。
本程序还有两个特色:
1.支持更换主题。
2.程序首次启动加入了加载过渡。
其他的彩蛋,您自己去发现吧!
程序放在了蓝奏云。思路、代码方面有什么不足欢迎各位大佬指正、批评!
以上就是python制作的天气预报小工具(gui界面)的详细内容,更多关于python 天气预报工具的资料请关注自学编程网其它相关文章!