热门文章

最新文章

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

发布时间:2021-06-22 15:26:45

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


一.准备工作

不需要准备。

二.预览

1.启动

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

2.添加城市

3.展示多个城市天气

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

三.设计流程

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

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

四.源代码

1.Weather_Tool-v1.0.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#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.程序首次启动加入了加载过渡。
其他的彩蛋,您自己去发现吧!



返回顶部