AI编程(三十九):天气查询工具 — 把”看天吃饭”交给程序

作者:

在

前面38篇,你攒了一柜子零件:变量、条件、循环、函数、文件、JSON、requests、API。今天这篇不发新零件——今天干的是”组装”:把 requests、JSON、函数三件套拧成一个真正能用的小工具——输入城市名,天气、温度、湿度一目了然,还能顺手记进账本。这是你第一个”联网的正经软件”

一、从每天早上那个动作说起

每天早上你醒来的第一件事,十有八九是摸手机、点开天气APP、看一眼今天几度要不要带伞。这个动作你可能已经做了几千次,从来没觉得它有什么了不起。

但你注意过吗:那个小小的天气页面背后,发生了一整套你已经全学过的事情——

  1. APP 向天气服务器发了一个请求(第37篇:HTTP请求);
  2. 服务器从”官方点餐窗口”端出一份天气数据(第38篇:API);
  3. 数据是 JSON 格式的(第27篇:JSON);
  4. APP 把温度、湿度、天气描述一个个抠出来,画成你看到的那个界面;
  5. 而”查哪个城市”是你说的——你输入”沈阳”,它就查沈阳(第7篇:函数的参数)。

看出来了吗?你已经会做这个APP了。不会的只是”做成手机APP”那层皮,而那层皮今天也不需要——一个命令行小工具,一样能把活儿干了。

今天这篇有个特殊身份:它是阶段4(网络基础)的第一个组装课。前两篇你分别练了 requests(怎么去拿)和 API+JSON(拿回来怎么拆),今天不再发新零件,而是把零件装成整机。就像学做菜:前几天分别练了切菜和颠勺,今天炒第一盘完整的菜——原料你全认识,新东西只有”怎么把它们拼成一道菜”。

顺便说一句心里话:很多学到这个阶段的人会犯一个毛病——零件都认识,一到组装就发懵,然后怀疑自己”是不是前面没学好”。不是的。组装本身就是一门手艺,今天就专门练它。

二、今天不学新语法,学”组装图”

先画图纸:一个天气工具需要哪几块

做任何程序,动手前先花一分钟画图纸,能省一小时返工。一个天气查询工具,拆开看就四块:

模块 干什么 用到哪篇的功夫

|——|——–|—————-|

拿需求 问用户”查哪个城市” 第3篇:input
查数据 拼网址、发请求、拿JSON 第37、38篇:requests + API
拆数据 从JSON里抠出温度、湿度、描述 第27篇:JSON字典取值
展示+存档 打印给用户看,顺便记进文件 第9篇:文件写入

四块里没有一块是新的。今天真正的新东西,是把它们按顺序装起来,并且装进函数里——让”查天气”变成一个可以反复调用的动作,而不是一段从头跑到尾的流水账。

组装图:函数就是”把动作打包”

回忆第7篇:函数是”代码的菜谱”,写一次,想用就”照着做”。今天的组装图长这样:

def 查天气(城市):
    """输入城市名,返回一个装着天气信息的字典;查不到返回None"""
    # 1. 拼网址(第37篇)
    # 2. 发请求、检查回执(第37篇)
    # 3. 拆JSON(第27、38篇)
    # 4. 把要的信息装进一个小字典(第8篇)
    return 小字典

# 主流程就三行:问城市 → 查天气 → 显示
城市 = input("想查哪个城市?")
结果 = 查天气(城市)
print(结果)

注意这个设计里藏着今天最值钱的一个思路:查天气 只负责”查”,不负责”显示”。它查完把结果交出来(return),怎么展示、要不要存文件,是主流程的事。这叫”各管一段”——将来你想加”存进记录文件”,只改主流程,查天气 一个字都不用动。

你可能觉得”这有什么,不就是分个工吗”。对,就是分工——但会分工,是脚本小子和正经程序员的分水岭。今天练的就是它。

三、跟AI说需求→读懂→改进:做出第一个”联网的正经软件”

第一步:跟AI说需求

“我是编程初学者,刚学完API调用。帮我写一个Python天气查询工具:用户输入城市名(比如沈阳),用 wttr.in 接口(https://wttr.in/城市名?format=j1)查天气,显示当前温度、体感温度、湿度、天气描述,中文输出。要求:把’查天气’写成函数,返回字典;带 timeout 和 try/except,出错不崩;城市名是中文,注意网址编码问题。代码完整可运行,加中文注释。”

跟第38篇一样,这段话里埋了四个关键信息:具体接口(wttr.in,免密钥)、具体字段需求(温度、体感、湿度、描述)、架构要求(写成函数)、防御要求(timeout、try/except、中文编码)。料给足,AI 给你的代码就能直接跑。

第二步:读懂(逐行对照)

AI 大概率给你这样的代码:

import requests
from urllib.parse import quote    # 把中文城市名翻译成网址能接受的样子

def 查天气(城市):
    """查指定城市的天气,成功返回字典,失败返回None"""
    网址 = "https://wttr.in/" + quote(城市) + "?format=j1"
    try:
        回应 = requests.get(网址, timeout=15)
        if 回应.status_code != 200:          # 第37篇的规矩:先看回执
            print("查询失败,回执编号:", 回应.status_code)
            return None
        数据 = 回应.json()                    # 第38篇:拆JSON餐盒
        现在 = 数据["current_condition"][0]   # wttr.in 把当前天气放在这里
        小字典 = {
            "城市": 城市,
            "温度": 现在["temp_C"],
            "体感": 现在["FeelsLikeC"],
            "湿度": 现在["humidity"],
            "描述": 现在["weatherDesc"][0]["value"],
        }
        return 小字典
    except requests.RequestException as 原因:
        print("网络出岔子:", 原因)
        return None

# 主流程:问 → 查 → 显示
城市 = input("想查哪个城市?")
结果 = 查天气(城市)
if 结果:                                    # 查到了才显示
    print(f"{结果['城市']}:{结果['描述']}")
    print(f"温度 {结果['温度']}°C,体感 {结果['体感']}°C,湿度 {结果['湿度']}%")

逐行对照一遍,你会发现每个零件都认识:

  • quote(城市) —— 唯一的生面孔,但它不是新知识,就是个”翻译官”:把”沈阳”翻译成网址能接受的 %E6%B2%88%E9%98%B3。中文直接拼进网址容易闹脾气(上一篇打过疫苗),一行 quote() 治好它;
  • requests.get + status_code + timeout —— 第37篇全套;
  • 回应.json() + 字典取值 —— 第27、38篇全套;
  • def 查天气(城市): + return —— 第7篇;
  • if 结果: —— 第5篇。

五块零件,四块是复习。这就是”组装课”的样子:学习量不在新语法,在”怎么摆”。

第三步:改进(这是拉开差距的一步)

拿AI的代码直接跑,能用,但离”正经软件”还差三口气。三口气全补上,今天就毕业:

改进1:return None 之后主流程什么都不说,用户一脸懵。查不到城市、网络断了,程序安安静静结束,用户不知道是没查到还是程序坏了。改进的话这么说:

“查询失败时给用户一句明确的提示,比如’没查到这个城市,换个名字试试’,不要静默结束。”

改进2:查完就忘,明天想对比昨天的温度还得重新查。加个”天气账本”,每查一次记一行进文件。改进的话:

“每次查询成功后,把’日期 城市 温度 天气’追加保存到 weather_log.txt,方便以后翻看。”

改进3:一次只能查一个城市,想对比沈阳和大连得跑两遍程序。改成可以连续查,输入 q 退出。改进的话:

“用 while 循环做成连续查询,输入 q 退出,每次查询都记录到文件。”

合起来的成品(完整可运行):

import requests
from urllib.parse import quote
from datetime import datetime

def 查天气(城市):
    """查指定城市的天气,成功返回字典,失败返回None"""
    网址 = "https://wttr.in/" + quote(城市) + "?format=j1"
    try:
        回应 = requests.get(网址, timeout=15)
        if 回应.status_code != 200:
            print("查询失败,回执编号:", 回应.status_code)
            return None
        数据 = 回应.json()
        现在 = 数据["current_condition"][0]
        # .get() 兜底:人家少端一个菜也不崩(第38篇的疫苗)
        return {
            "城市": 城市,
            "温度": 现在.get("temp_C", "?"),
            "体感": 现在.get("FeelsLikeC", "?"),
            "湿度": 现在.get("humidity", "?"),
            "描述": 现在.get("weatherDesc", [{}])[0].get("value", "未知"),
        }
    except requests.RequestException as 原因:
        print("网络出岔子:", 原因)
        return None

def 存记录(结果, 文件名="weather_log.txt"):
    """把一次查询结果记一行进天气账本"""
    时间 = datetime.now().strftime("%Y-%m-%d %H:%M")
    一行 = f"{时间} {结果['城市']} {结果['温度']}°C {结果['描述']}"
    with open(文件名, "a", encoding="utf-8") as f:
        f.write(一行 + "n")

print("=== 天气查询工具 ===")
while True:                                  # 第6篇:连续干活
    城市 = input("n想查哪个城市?(输入 q 退出)").strip()
    if 城市.lower() == "q":
        print("明天见!")
        break
    if not 城市:
        print("城市名不能为空哦")
        continue
    结果 = 查天气(城市)
    if 结果:
        print(f"{结果['城市']}:{结果['描述']}")
        print(f"温度 {结果['温度']}°C,体感 {结果['体感']}°C,湿度 {结果['湿度']}%")
        存记录(结果)
        print("(已记入 weather_log.txt)")
    else:
        print("没查到这个城市,换个名字试试")

跑起来是这样:

=== 天气查询工具 ===

想查哪个城市?(输入 q 退出)沈阳
沈阳:Sunny
温度 16°C,体感 15°C,湿度 42%
(已记入 weather_log.txt)

想查哪个城市?(输入 q 退出)q
明天见!

看一眼 weather_log.txt,账本已经在记账了:

2026-10-15 08:30 沈阳 16°C Sunny

这就是”脚本”和”软件”的差别:脚本跑完就没了,软件会留下痕迹、会等你下一次、坏掉时会说话。而把它从脚本升级成软件的全部改动,不过是”加个循环、加个函数、加个文件”——全是你前38篇学过的东西。

四、四个必踩的坑(先打疫苗)

坑1:中文城市名直接拼网址,十有八九查不到。“沈阳”直接塞进网址,有的环境会报错、有的会返回乱码、有的干脆查不到。疫苗:from urllib.parse import quote,拼网址时 quote(城市)——这不是可选项,是标配。顺带说一句:就算处理了编码,”沈阳”能查到,”铁西区”这种小地名未必能——wttr.in 对小地名支持有限,查不到先别怀疑代码,换个大城市名试试。

坑2:回应.json() 的结构是”别人家定的”,照抄字段名换个接口就崩。wttr.in 的当前天气在 数据["current_condition"][0],换一个天气接口,这个路径保证不一样。疫苗:字段结构看文档,别死记;拿不准就先写一行 print(数据) 把整包数据倒出来看看结构,再决定抠哪里——这是第38篇”摸底代码”的功夫,今天再用一遍。

坑3:不加 timeout,网络一卡程序就”假死”。requests.get(网址) 不写 timeout,赶上网络抽风,程序能挂在那里五分钟十分钟,你还不知道它是死了还是在等。疫苗:永远写 timeout,15秒够用了。这个坑第37篇打过疫苗,这里再强调一遍,因为组装的时候最容易忘——零件都对,装的时候漏了一个,最亏。

坑4:把天气接口当自家后院,写进 while True 猛刷。今天成品里的 while True 是”等你输入”,人不打字它就不发请求,很乖。但你要是改成”自动每秒查一次温度”,免费接口分分钟限流你。疫苗:给人用的查询随便跑,给程序自动跑的记得歇口气(time.sleep()),自动定时查询别写死循环猛刷。

五、跟AI协作的正确姿势(组装课特供)

今天这种”组装课”,跟AI打交道的姿势和之前有点不一样。零件你都认识了,AI的角色从”教你语法”变成”陪你组装”。三条特供建议:

  1. 先给AI看图纸,再要代码。动手前先把你的四块模块说给AI:”我要做一个天气工具,分四块:拿输入、查API、拆JSON、显示+存档,查天气部分写成函数。这样分工合理吗?”让AI先帮你审图纸,比直接要代码再返工省一半时间。
  2. 要求”零件对照表”。拿到代码后追问一句:”请逐行标注每段代码用了第几篇学过的基础知识(变量/条件/循环/函数/文件/JSON/requests)”。今天这种复习性质的课,这张对照表就是你的体检报告——哪块零件对不上号,说明哪篇没学牢。
  3. 改进时一次只改一个地方。三条改进一口气提给AI,出来的代码变了30行,跑挂了都不知道是谁改坏的。一次提一条,跑通了再提下一条——组装课的调试纪律。

六、今日小练习

练习1:给工具加上”华氏度”。在显示温度的同时,顺便显示华氏度。换算公式:华氏 = 摄氏 × 9 / 5 + 32。

提示:结果["温度"] 拿到的是字符串(JSON里是文本),要先 int(结果["温度"]) 转成数字才能算——这是个经典小坑,正好踩一踩。还记得第20篇单位转换器吗?同一个公式你写过。

练习2:给天气账本加个”翻账本”功能。程序启动时,如果 weather_log.txt 已经存在,先把里面的最后3条记录打印出来,让用户知道”上次查过什么”。

提示:第9篇的读文件 + 第8篇的列表切片 行们[-3:],两行代码的事。

练习3(动脑题):画你自己的”组装图”。回头翻翻第38篇练习3里你列的”点餐清单”,挑一个(比如”每天查汇率”),照今天的样子画一张四块模块的图纸:哪块拿输入、哪块发请求、哪块拆数据、哪块展示存档?画在纸上或打字发给AI都行。

提示:这题不用写代码——但请认真画,下一篇我们就要学”拆别人的网页”,到时候你一眼就能看出:哪些活儿API干不了,得爬虫上。

下篇预告

下一篇(第40篇):网页结构入门 — 看懂网页的”骨架”。天气工具靠的是 API 这个”官方点餐窗口”,可世上不是每家网站都开窗口——窗口不开的时候,就得自己看懂网页的骨架(HTML),才谈得上从里面取东西。下一篇不写爬虫,先带你像”看懂房间布局”一样看懂网页结构:标签、层级、class 和 id。看懂了骨架,第41篇的爬虫就是顺手的事。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注