python制作的天气预报小工具(gui界面)

作者:懷淰メ 时间:2022-04-03 17:20:42 

目录
  • 一.准备工作

  • 二.预览

    • 1.启动

    • 2.添加城市

    • 3.展示多个城市天气

  • 三.设计流程

    • 1.获取城市天气信息过程

  • 四.源代码

    • 1.Weather_Tool-v1.0.py

    • 2.Weather_Spider.py

  • 五.总结

    一.准备工作

    不需要准备。

    二.预览

    1.启动

    启动以后自动定位所在城市,展示定位城市的天气。

    python制作的天气预报小工具(gui界面)

    2.添加城市

    python制作的天气预报小工具(gui界面)

    3.展示多个城市天气

    添加天气之后能够显示多个城市天气信息。

    python制作的天气预报小工具(gui界面)

    三.设计流程

    1.获取城市天气信息过程

    用此流程图展示定位城市信息到获取城市天气信息过程。

    python制作的天气预报小工具(gui界面)

    四.源代码

    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.程序首次启动加入了加载过渡。
    其他的彩蛋,您自己去发现吧!
    程序放在了蓝奏云。思路、代码方面有什么不足欢迎各位大佬指正、批评!

    来源:https://blog.csdn.net/a1397852386/article/details/116947547

    标签:python,天气预报,gui
    0
    投稿

    猜你喜欢

  • Python使用win32 COM实现Excel的写入与保存功能示例

    2021-03-30 11:28:50
  • Python实现一键改变raw格式照片风格

    2021-04-07 10:48:16
  • Python os模块常用方法和属性总结

    2021-05-06 13:46:12
  • face_recognition库在python的安装

    2021-06-16 02:29:27
  • Python使用百度API上传文件到百度网盘代码分享

    2023-08-05 02:38:52
  • Python中的文本相似度的计算方法总结

    2021-02-08 08:40:37
  • Python初学者必备的文件读写指南

    2023-03-16 11:44:15
  • python list 查询是否存在并且并返回下标的操作

    2023-06-20 12:05:43
  • Selenium chrome配置代理Python版的方法

    2022-02-24 14:26:08
  • Python 图片视频模糊化实现案例

    2023-05-28 11:50:52
  • python使用json.dumps输出中文问题

    2023-11-17 22:04:46
  • Python素数检测的方法

    2021-02-13 13:07:30
  • Python sorted函数详解(高级篇)

    2021-08-14 15:58:28
  • js 实现数值的千分位及保存小数方法(推荐)

    2023-08-17 01:13:11
  • Python开源自动化工具Playwright安装及介绍使用

    2023-08-20 13:17:43
  • 使用python编写android截屏脚本双击运行即可

    2021-01-25 20:47:19
  • 深入浅析Python中join 和 split详解(推荐)

    2022-09-19 17:43:38
  • 如何避免asp的SQL的执行效率低

    2009-01-08 18:18:00
  • python中os模块和sys模块的使用详解

    2021-08-29 21:42:35
  • 利用python判断字母大小写的几种方法小结

    2022-05-10 16:41:49
  • asp之家 网络编程 m.aspxhome.com