最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

python3GUI

IT圈 admin 0浏览 0评论

python3GUI

文章目录

  • 一.准备工作
  • 二.预览
    • 1.启动
    • 2.添加城市
    • 3.展示多个城市天气
  • 三.设计流程
    • 1.获取城市天气信息过程(V1.0)
    • 2.获取城市天气信息过程(V1.5)
  • 四.源代码
    • 1.Weather_Tool-v2.0.py
    • 2.Weather_Spider.py
  • 五.总结


大家好啊!我用Tkinter写了一个天气预报小工具,支持34个省级行政区以及港澳台地区天气,覆盖全面。程序打包好放在了蓝奏云,与大家分享一下。(2021-08-16更新1.5)

一.准备工作

不需要准备。

二.预览

1.启动

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

2.添加城市

3.展示多个城市天气

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

三.设计流程

1.获取城市天气信息过程(V1.0)

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

2.获取城市天气信息过程(V1.5)

四.源代码

1.Weather_Tool-v2.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
import webbrowser"""
点击完添加城市天气就加载好天气省市信息
"""'''
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中6-28
更新IP定位城市天气信息逻辑8-16
1.解决了5-15问题:添加新城市,原标签控件失效问题 exec()
2.点击“添加”之后将窗口切换到新添加的窗口
'''
imgs=['./img/loading.png']
class App:def __init__(self):self.w=Tk()self.w.title('天气预报小工具-v1.5')width=600height=280left=(self.w.winfo_screenwidth()-width)/2top=(self.w.winfo_screenheight()-height)/2self.w.geometry('%dx%d+%d+%d'%(width,height,left,top))self.w.iconbitmap('./img/biticon.ico')self.w.resizable(False,False)self.W=Weather_Get()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()#f1存储当前定位所在城市天气信息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.mself.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.city_number_list=[]self.city_list=[]#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='打开中国天气网',command=lambda:webbrowser.open(self.china_weather_url))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.note.bind('<<NotebookTabChanged>>',self.get_note_current_tab_index)self.w.protocol('WM_DELETE_WINDOW',self.quit_window)self.w.bind('<Escape>',self.escape)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('')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 = imgsimg = 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,number=self.W.get_local_weather()self.china_weather_url=f'/{number}.shtml'self.load_img.destroy()self.load_lab.destroy()self.s2.entryconfig('添加城市', state='normal')self.note.add(self.f1,text=city)i=0for data in item['recent']:self.tree.insert('', i, values=(data.get('日期'), data.get('天气'), data.get('风力风向'), data.get('最低气温'), data.get('最高气温')))i+=1now_time = str(datetime.datetime.now()).split('.')[0].split(' ')[-1]self.l1_var.set(f'今天:{self.show_date()}\n当前所在地区:{city}\n当前气温:{item["now"]}({now_time}更新)\n感冒指数:{item["ganmao"]}')#将定位城市加入 已展示城市列表self.location中self.city_number_list.append(number)self.city_list.append(city)except TypeError:messagebox.showerror('错误','天气信息加载失败!')self.l1_var.set('天气信息加载失败!')self.s2.entryconfig('添加城市', state='normal')def get_note_current_tab_index(self,event):#当前切换的组件tab_id = self.note.select()print(tab_id)#当前notebok的所在索引self.current_tab_index = self.note.index(tab_id)#当前tab的text# tab_name = self.note.tab(tab_index, "text")def refresh_weather(self):"""刷新天气后,10秒内不能点击刷新:return:"""try:if self.current_tab_index==0:self.s2.entryconfig('刷新', state='disable')self.show_local_weather()else:current_city_number=self.city_number_list[self.current_tab_index]current_city=self.city_list[self.current_tab_index]exec("self.fl_{}_var.set('正在刷新天气......')".format(current_city_number))items = eval("self.tree_{}.get_children()".format(current_city_number))for item in items:exec("self.tree_{}.delete(item)".format(current_city_number))try:item= self.W.get_weather(current_city_number)i = 0for data in item['recent']:exec("self.tree_{}.insert('', i, values=(data.get('日期'), data.get('天气'), data.get('风力风向'), data.get('最低气温'), data.get('最高气温')))".format(current_city_number))i += 1now_time = str(datetime.datetime.now()).split('.')[0].split(' ')[-1]weather_infos = r"今天:{}\n当前所选地区:{}\n当前气温:{}({}更新)\n感冒指数:{}".format(self.show_date(), current_city, item['now'], now_time, item['ganmao'])exec("self.fl_{}_var.set('{}')".format(current_city_number, weather_infos))# 将定位城市加入 已展示城市列表self.location中except TypeError:messagebox.showerror('错误', '天气信息加载失败!')self.l1_var.set('天气信息加载失败!')self.s2.entryconfig('添加城市', state='normal')except AttributeError: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()date_time=f'{year}年{month}月{day}日 {week_day_dict[date_index]}'return date_timedef select_city(self):'''Toplevel让用户选择城市,后台获取城市号:return:'''self.t=Toplevel()self.t.resizable(0,0)width=300height=140left=(self.t.winfo_screenwidth()-width)/2top=(self.t.winfo_screenheight()-height)/2self.t.geometry('%dx%d+%d+%d'%(width,height,left,top))self.t.title('选择城市')self.tl1=ttk.Label(self.t,text='请选择城市:')self.tl1.pack()provinces=self.W.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.t.bind('<Return>',lambda event:self.ack_city)self.tb1.pack(pady=10)def ack_city(self):'''Toplevel中选择了城市,选择使用notebook中建立Frame展示所选城市信息:return:'''cityno=self.get_city_no()weather_item=self.W.get_weather(cityno)location=self.province_name+self.city_name+self.regionif location in self.city_list:messagebox.showwarning('警告','此城市已添加,请勿重复添加!')else:self.city_number_list.append(cityno)self.city_list.append(location)exec("self.f_{}= Frame(takefocus=True)".format(cityno))exec("self.note.add(self.f_{}, text=location)".format(cityno))exec("self.tree_{} = ttk.Treeview(self.f_{})".format(cityno,cityno))columns = ('rq', 'tq', 'flfx', 'zdqw', 'zgqw')exec("self.tree_{}.config(show='headings', columns=columns)".format(cityno))exec("self.tree_{}.column(columns[0], anchor=CENTER, minwidth=95, width=110)".format(cityno))exec("self.tree_{}.column(columns[1], anchor=CENTER, minwidth=60, width=70)".format(cityno))exec("self.tree_{}.column(columns[2], anchor=CENTER, minwidth=90, width=100)".format(cityno))exec("self.tree_{}.column(columns[3], anchor=CENTER, minwidth=90, width=100)".format(cityno))exec("self.tree_{}.column(columns[4], anchor=CENTER, minwidth=90, width=100)".format(cityno))exec("self.tree_{}.heading('rq', text='日期')".format(cityno))exec("self.tree_{}.heading('tq', text='天气')".format(cityno))exec("self.tree_{}.heading('flfx', text='风向风力')".format(cityno))exec("self.tree_{}.heading('zdqw', text='最低气温')".format(cityno))exec("self.tree_{}.heading('zgqw', text='最高气温')".format(cityno))exec("self.tree_{}.place(x=0,y=0,width=600,height=150)".format(cityno))exec("self.fl_{}_var=StringVar()".format(cityno))exec("self.fl_{}=ttk.Label(self.f_{},textvariable=self.fl_{}_var)".format(cityno,cityno,cityno))exec("self.fl_{}.place(x=0,y=150,height=85,width=600)".format(cityno))items = eval("self.tree_{}.get_children()".format(cityno))for item in items:exec("self.tree_{}.delete(item)".format(cityno))try:item = weather_itemcity=locationi = 0for data in item['recent']:exec("self.tree_{}.insert('', i, values=(data.get('日期'), data.get('天气'), data.get('风力风向'), data.get('最低气温'), data.get('最高气温')))".format(cityno))i += 1now_time = str(datetime.datetime.now()).split('.')[0].split(' ')[-1]#使用exec()不能加入转义字符,否则会报SyntaxError: EOL while scanning string literal错误!weather_infos=r"今天:{}\n当前所选地区:{}\n当前气温:{}({}更新)\n感冒指数:{}".format(self.show_date(),city,item['now'],now_time,item['ganmao'])exec("self.fl_{}_var.set('{}')".format(cityno,weather_infos))self.china_weather_url = f'/{cityno}.shtml'self.note.select(f".!frame{len(self.city_list)}")#将焦点切换到最新添加except TypeError:messagebox.showerror('错误','天气信息加载失败!')exec("self.fl_{}_var.set('{}天气信息加载失败!')".format(cityno,location))self.t.destroy()def show_tc2_value(self,event):'''展示"市"级信息:param event::return:'''self.tc2.config(value=[])self.tc3.config(value=[])self.province_name=self.tc1.get()cities=self.W.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=self.W.get_regions(self.province_name,self.city_name)self.tc3.config(value=regions)def get_city_no(self):"""根据省、市、区、县 获取城市号:return: 城市号"""self.region=self.tc3.get()try:if self.city_name=='':messagebox.showwarning('警告','请选择城市')else:city_no=self.W.get_city_id_by_add(self.province_name,self.city_name,self.region)return city_noexcept  AttributeError :messagebox.showwarning('警告', '请选择城市')def change_theme(self,):'''更换主题:return:'''theme=self.themevar.get()style = ttk.Style(self.w)style.theme_use(theme)def escape(self,event):self.quit_window()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
import re
"""
get_location_city()返回无数据
这条路走不通
"""class Weather_Get():def __init__(self):#ip查询self.search_ip_url=''#IP归属地查询self.location_url='={}'#获取中国国内城市--number接口self.city_number_url='.json'#天气查询接口self.base_weather_url='={}'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=Falsetry:r=s.get(url,headers=headers)r.encoding='utf-8'if r.status_code==200:r.encoding = r.apparent_encodingreturn r.textelse:return Noneexcept requests.exceptions.ConnectionError:return Nonedef get_location_city(self):"""通过ip定位到当前城市:return:所在省市位置信息"""my_headers={'Connection': 'keep-alive','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'}_json=json.loads(self.request(self.search_ip_url,headers=my_headers))ip=_json.get('origin')if ip:res=self.request(self.location_url.format(ip),headers=my_headers)dingwei_item={}dingwei_item['city']=''.join(re.findall('{city:"(.*?)",',res))dingwei_item['province']=''.join(re.findall('province:"(.*?)"}',res))return dingwei_itemelse:return Nonedef get_city_item(self):res =self.request(self.city_number_url,headers=self.headers)item=eval("{'cities':"+res+"}")return itemdef get_provinces(self):province=[p for p in self.item['cities']]#print(province)return provincedef get_cities(self,province):cities_=self.item['cities'][province]cities=[city for city in cities_.keys()]return citiesdef get_regions(self,province,city):regions_=self.item['cities'][province][city]regions=[region for region in regions_.keys()]return regionsdef 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_nodef 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 numberexcept AttributeError:number=self.item['cities'][province].get(province).get(city).replace('CN','')return numberelse:return None#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=0for 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['日期']=dateitem2['天气'] = weateher['type']item2['风力风向']=weateher['fengxiang']+weateher['fengli'].replace('<![CDATA[','').replace(']]>','')item2['最低气温'] = weateher['low'].replace('低温 ', '')item2['最高气温'] = weateher['high'].replace('高温 ', '')item_list.append(item2)count+=1item['recent']=item_listreturn itemdef get_local_weather(self):item=Weather_Get().get_location_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,numberelse:return Falseif __name__ == '__main__':Weather_Get().get_local_weather()#Weather_Get().get_regions('北京市','北京市')

五.总结

本次使用Tkinter写了一款天气预报小工具,基本支持全国每个省市的天气预报,支持历史天气(昨天)查看,虽然基本功能能够实现,但是仍旧存在两个小问题(1.0):
1.添加超过两个城市天气后,具体城市信息会显示在最新添加的Freame中。
2.ip定位不准确。

本程序还有两个特色:
1.支持更换主题。
2.程序首次启动加入了加载过渡。
3.使用ip定位后解析城市归属地,获取准确城市天气信息。

其他的彩蛋,您自己去发现吧!
程序放在了蓝奏云-v1.0
蓝奏云-v1.5(20210816最新版)。思路、代码方面有什么不足欢迎各位大佬指正、批评!

python3GUI

文章目录

  • 一.准备工作
  • 二.预览
    • 1.启动
    • 2.添加城市
    • 3.展示多个城市天气
  • 三.设计流程
    • 1.获取城市天气信息过程(V1.0)
    • 2.获取城市天气信息过程(V1.5)
  • 四.源代码
    • 1.Weather_Tool-v2.0.py
    • 2.Weather_Spider.py
  • 五.总结


大家好啊!我用Tkinter写了一个天气预报小工具,支持34个省级行政区以及港澳台地区天气,覆盖全面。程序打包好放在了蓝奏云,与大家分享一下。(2021-08-16更新1.5)

一.准备工作

不需要准备。

二.预览

1.启动

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

2.添加城市

3.展示多个城市天气

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

三.设计流程

1.获取城市天气信息过程(V1.0)

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

2.获取城市天气信息过程(V1.5)

四.源代码

1.Weather_Tool-v2.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
import webbrowser"""
点击完添加城市天气就加载好天气省市信息
"""'''
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中6-28
更新IP定位城市天气信息逻辑8-16
1.解决了5-15问题:添加新城市,原标签控件失效问题 exec()
2.点击“添加”之后将窗口切换到新添加的窗口
'''
imgs=['./img/loading.png']
class App:def __init__(self):self.w=Tk()self.w.title('天气预报小工具-v1.5')width=600height=280left=(self.w.winfo_screenwidth()-width)/2top=(self.w.winfo_screenheight()-height)/2self.w.geometry('%dx%d+%d+%d'%(width,height,left,top))self.w.iconbitmap('./img/biticon.ico')self.w.resizable(False,False)self.W=Weather_Get()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()#f1存储当前定位所在城市天气信息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.mself.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.city_number_list=[]self.city_list=[]#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='打开中国天气网',command=lambda:webbrowser.open(self.china_weather_url))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.note.bind('<<NotebookTabChanged>>',self.get_note_current_tab_index)self.w.protocol('WM_DELETE_WINDOW',self.quit_window)self.w.bind('<Escape>',self.escape)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('')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 = imgsimg = 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,number=self.W.get_local_weather()self.china_weather_url=f'/{number}.shtml'self.load_img.destroy()self.load_lab.destroy()self.s2.entryconfig('添加城市', state='normal')self.note.add(self.f1,text=city)i=0for data in item['recent']:self.tree.insert('', i, values=(data.get('日期'), data.get('天气'), data.get('风力风向'), data.get('最低气温'), data.get('最高气温')))i+=1now_time = str(datetime.datetime.now()).split('.')[0].split(' ')[-1]self.l1_var.set(f'今天:{self.show_date()}\n当前所在地区:{city}\n当前气温:{item["now"]}({now_time}更新)\n感冒指数:{item["ganmao"]}')#将定位城市加入 已展示城市列表self.location中self.city_number_list.append(number)self.city_list.append(city)except TypeError:messagebox.showerror('错误','天气信息加载失败!')self.l1_var.set('天气信息加载失败!')self.s2.entryconfig('添加城市', state='normal')def get_note_current_tab_index(self,event):#当前切换的组件tab_id = self.note.select()print(tab_id)#当前notebok的所在索引self.current_tab_index = self.note.index(tab_id)#当前tab的text# tab_name = self.note.tab(tab_index, "text")def refresh_weather(self):"""刷新天气后,10秒内不能点击刷新:return:"""try:if self.current_tab_index==0:self.s2.entryconfig('刷新', state='disable')self.show_local_weather()else:current_city_number=self.city_number_list[self.current_tab_index]current_city=self.city_list[self.current_tab_index]exec("self.fl_{}_var.set('正在刷新天气......')".format(current_city_number))items = eval("self.tree_{}.get_children()".format(current_city_number))for item in items:exec("self.tree_{}.delete(item)".format(current_city_number))try:item= self.W.get_weather(current_city_number)i = 0for data in item['recent']:exec("self.tree_{}.insert('', i, values=(data.get('日期'), data.get('天气'), data.get('风力风向'), data.get('最低气温'), data.get('最高气温')))".format(current_city_number))i += 1now_time = str(datetime.datetime.now()).split('.')[0].split(' ')[-1]weather_infos = r"今天:{}\n当前所选地区:{}\n当前气温:{}({}更新)\n感冒指数:{}".format(self.show_date(), current_city, item['now'], now_time, item['ganmao'])exec("self.fl_{}_var.set('{}')".format(current_city_number, weather_infos))# 将定位城市加入 已展示城市列表self.location中except TypeError:messagebox.showerror('错误', '天气信息加载失败!')self.l1_var.set('天气信息加载失败!')self.s2.entryconfig('添加城市', state='normal')except AttributeError: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()date_time=f'{year}年{month}月{day}日 {week_day_dict[date_index]}'return date_timedef select_city(self):'''Toplevel让用户选择城市,后台获取城市号:return:'''self.t=Toplevel()self.t.resizable(0,0)width=300height=140left=(self.t.winfo_screenwidth()-width)/2top=(self.t.winfo_screenheight()-height)/2self.t.geometry('%dx%d+%d+%d'%(width,height,left,top))self.t.title('选择城市')self.tl1=ttk.Label(self.t,text='请选择城市:')self.tl1.pack()provinces=self.W.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.t.bind('<Return>',lambda event:self.ack_city)self.tb1.pack(pady=10)def ack_city(self):'''Toplevel中选择了城市,选择使用notebook中建立Frame展示所选城市信息:return:'''cityno=self.get_city_no()weather_item=self.W.get_weather(cityno)location=self.province_name+self.city_name+self.regionif location in self.city_list:messagebox.showwarning('警告','此城市已添加,请勿重复添加!')else:self.city_number_list.append(cityno)self.city_list.append(location)exec("self.f_{}= Frame(takefocus=True)".format(cityno))exec("self.note.add(self.f_{}, text=location)".format(cityno))exec("self.tree_{} = ttk.Treeview(self.f_{})".format(cityno,cityno))columns = ('rq', 'tq', 'flfx', 'zdqw', 'zgqw')exec("self.tree_{}.config(show='headings', columns=columns)".format(cityno))exec("self.tree_{}.column(columns[0], anchor=CENTER, minwidth=95, width=110)".format(cityno))exec("self.tree_{}.column(columns[1], anchor=CENTER, minwidth=60, width=70)".format(cityno))exec("self.tree_{}.column(columns[2], anchor=CENTER, minwidth=90, width=100)".format(cityno))exec("self.tree_{}.column(columns[3], anchor=CENTER, minwidth=90, width=100)".format(cityno))exec("self.tree_{}.column(columns[4], anchor=CENTER, minwidth=90, width=100)".format(cityno))exec("self.tree_{}.heading('rq', text='日期')".format(cityno))exec("self.tree_{}.heading('tq', text='天气')".format(cityno))exec("self.tree_{}.heading('flfx', text='风向风力')".format(cityno))exec("self.tree_{}.heading('zdqw', text='最低气温')".format(cityno))exec("self.tree_{}.heading('zgqw', text='最高气温')".format(cityno))exec("self.tree_{}.place(x=0,y=0,width=600,height=150)".format(cityno))exec("self.fl_{}_var=StringVar()".format(cityno))exec("self.fl_{}=ttk.Label(self.f_{},textvariable=self.fl_{}_var)".format(cityno,cityno,cityno))exec("self.fl_{}.place(x=0,y=150,height=85,width=600)".format(cityno))items = eval("self.tree_{}.get_children()".format(cityno))for item in items:exec("self.tree_{}.delete(item)".format(cityno))try:item = weather_itemcity=locationi = 0for data in item['recent']:exec("self.tree_{}.insert('', i, values=(data.get('日期'), data.get('天气'), data.get('风力风向'), data.get('最低气温'), data.get('最高气温')))".format(cityno))i += 1now_time = str(datetime.datetime.now()).split('.')[0].split(' ')[-1]#使用exec()不能加入转义字符,否则会报SyntaxError: EOL while scanning string literal错误!weather_infos=r"今天:{}\n当前所选地区:{}\n当前气温:{}({}更新)\n感冒指数:{}".format(self.show_date(),city,item['now'],now_time,item['ganmao'])exec("self.fl_{}_var.set('{}')".format(cityno,weather_infos))self.china_weather_url = f'/{cityno}.shtml'self.note.select(f".!frame{len(self.city_list)}")#将焦点切换到最新添加except TypeError:messagebox.showerror('错误','天气信息加载失败!')exec("self.fl_{}_var.set('{}天气信息加载失败!')".format(cityno,location))self.t.destroy()def show_tc2_value(self,event):'''展示"市"级信息:param event::return:'''self.tc2.config(value=[])self.tc3.config(value=[])self.province_name=self.tc1.get()cities=self.W.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=self.W.get_regions(self.province_name,self.city_name)self.tc3.config(value=regions)def get_city_no(self):"""根据省、市、区、县 获取城市号:return: 城市号"""self.region=self.tc3.get()try:if self.city_name=='':messagebox.showwarning('警告','请选择城市')else:city_no=self.W.get_city_id_by_add(self.province_name,self.city_name,self.region)return city_noexcept  AttributeError :messagebox.showwarning('警告', '请选择城市')def change_theme(self,):'''更换主题:return:'''theme=self.themevar.get()style = ttk.Style(self.w)style.theme_use(theme)def escape(self,event):self.quit_window()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
import re
"""
get_location_city()返回无数据
这条路走不通
"""class Weather_Get():def __init__(self):#ip查询self.search_ip_url=''#IP归属地查询self.location_url='={}'#获取中国国内城市--number接口self.city_number_url='.json'#天气查询接口self.base_weather_url='={}'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=Falsetry:r=s.get(url,headers=headers)r.encoding='utf-8'if r.status_code==200:r.encoding = r.apparent_encodingreturn r.textelse:return Noneexcept requests.exceptions.ConnectionError:return Nonedef get_location_city(self):"""通过ip定位到当前城市:return:所在省市位置信息"""my_headers={'Connection': 'keep-alive','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'}_json=json.loads(self.request(self.search_ip_url,headers=my_headers))ip=_json.get('origin')if ip:res=self.request(self.location_url.format(ip),headers=my_headers)dingwei_item={}dingwei_item['city']=''.join(re.findall('{city:"(.*?)",',res))dingwei_item['province']=''.join(re.findall('province:"(.*?)"}',res))return dingwei_itemelse:return Nonedef get_city_item(self):res =self.request(self.city_number_url,headers=self.headers)item=eval("{'cities':"+res+"}")return itemdef get_provinces(self):province=[p for p in self.item['cities']]#print(province)return provincedef get_cities(self,province):cities_=self.item['cities'][province]cities=[city for city in cities_.keys()]return citiesdef get_regions(self,province,city):regions_=self.item['cities'][province][city]regions=[region for region in regions_.keys()]return regionsdef 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_nodef 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 numberexcept AttributeError:number=self.item['cities'][province].get(province).get(city).replace('CN','')return numberelse:return None#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=0for 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['日期']=dateitem2['天气'] = weateher['type']item2['风力风向']=weateher['fengxiang']+weateher['fengli'].replace('<![CDATA[','').replace(']]>','')item2['最低气温'] = weateher['low'].replace('低温 ', '')item2['最高气温'] = weateher['high'].replace('高温 ', '')item_list.append(item2)count+=1item['recent']=item_listreturn itemdef get_local_weather(self):item=Weather_Get().get_location_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,numberelse:return Falseif __name__ == '__main__':Weather_Get().get_local_weather()#Weather_Get().get_regions('北京市','北京市')

五.总结

本次使用Tkinter写了一款天气预报小工具,基本支持全国每个省市的天气预报,支持历史天气(昨天)查看,虽然基本功能能够实现,但是仍旧存在两个小问题(1.0):
1.添加超过两个城市天气后,具体城市信息会显示在最新添加的Freame中。
2.ip定位不准确。

本程序还有两个特色:
1.支持更换主题。
2.程序首次启动加入了加载过渡。
3.使用ip定位后解析城市归属地,获取准确城市天气信息。

其他的彩蛋,您自己去发现吧!
程序放在了蓝奏云-v1.0
蓝奏云-v1.5(20210816最新版)。思路、代码方面有什么不足欢迎各位大佬指正、批评!

发布评论

评论列表 (0)

  1. 暂无评论