新闻动态

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

发布日期:2022-04-12 16:46 | 文章来源:CSDN

一.准备工作

不需要准备。

二.预览

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 天气预报工具的资料请关注本站其它相关文章!

国外服务器租用

版权声明:本站文章来源标注为YINGSOO的内容版权均为本站所有,欢迎引用、转载,请保持原文完整并注明来源及原文链接。禁止复制或仿造本网站,禁止在非www.yingsoo.com所属的服务器上建立镜像,否则将依法追究法律责任。本站部分内容来源于网友推荐、互联网收集整理而来,仅供学习参考,不代表本站立场,如有内容涉嫌侵权,请联系alex-e#qq.com处理。

相关文章

实时开通

自选配置、实时开通

免备案

全球线路精选!

全天候客户服务

7x24全年不间断在线

专属顾问服务

1对1客户咨询顾问

在线
客服

在线客服:7*24小时在线

客服
热线

400-630-3752
7*24小时客服服务热线

关注
微信

关注官方微信
顶部