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

2021-07-12 0 421

一.准备工作

不需要准备。

二.预览

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

以上就是python制作的天气预报小工具(gui界面)的详细内容,更多关于python 天气预报工具的资料请关注自学编程网其它相关文章!

遇见资源网 Python python制作的天气预报小工具(gui界面) http://www.ox520.com/28899.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务