1. 为什么做这个小程序:给"AI课程自用"一个清晰定位
1.1 项目的真实用途:从单纯练习到顺手可用的天气工具
先说实话,这个项目的起因特别朴素:我在给一门AI入门课程当助教时,学员前两周都在折腾Python环境和基础语法,作业无非是打印九九乘法表、算个水仙花数,课堂反馈就一个字——闷。很多同学学完"请求网页接口"和"处理JSON"这两个知识点后,完全没有实感,不知道这东西到底能干什么。我于是琢磨着设计一个跨章节的小练习,把变量、函数、条件判断、字符串格式化、网络请求、异常处理、第三方库安装全部串起来,同时输出的结果又得让人一眼觉得"有点东西"。
所以就有了这个程序的核心定位:输入名字和城市,输出彩色个性化问候,再附上实时天气。它表面上是"能跑就行"的玩具,实际背后覆盖了Python基础阶段几乎全部重点。对零基础的人来说,输入自己名字和城市那一刻,屏幕上跳出带颜色的字和真实的天气温度,这种正反馈比任何练习题都強得多。对于有基础但没做过小工具的人,它也是一个极好的"最快产出可用程序"样板。
1.2 需求确认:名字、城市、彩色、天气,四件事缺一不可
有些同学拿到这个题目第一反应是"print一下不就行了吗"。真动手就会发现问题没那么简单:
- 名字:要支持中文,也要考虑用户可能输入空字符串或带空格。
- 城市:必须能传给天气API,而且用户在命令行输入的城市名可能带"市"字,也可能不带(比如"北京"和"北京市"),API未必都认识。
- 彩色:Windows命令行默认不支持ANSI颜色,直接print彩色字符会输出一堆乱码或
[31m这样的裸转义码。这需要处理。 - 天气:去哪拿数据?要不要注册?免费额度够不够?接口频繁调用会不会被限流?
这个程序名字里"自用"两个字很关键,意思是不要为了做而做,而是把它当成一个能反复使用、能改着玩的小工具。所以后面所有设计决策都围绕"省事、稳定、可改、不花钱"这八个字来。
我第一次做完的时候,是拿它在宿舍楼下跑了一遍,输入室友名字和城市,显示"晴,22度,空气质量良"外加一行橙色字,确实比黑底白字好看不少。后来干脆把它放在系统计划任务里,每天早上弹一次天气问候,变成了一个真正在用的程序。这个过程里遇到的问题,下面逐一拆开讲。
2. 天气数据从哪来:免费API的选型对比与注册细节
2.1 候选API盘点:免key方案、少key方案、商业方案
写这个程序的第一步不是写代码,是找天气数据源。我扫了一圈市面上常用的方案,整理成表格:
| 方案 | 是否需要注册key | 中文城市识别 | 返回中文状态 | 免费额度 | 备注 |
|---|---|---|---|---|---|
| 心知天气 | 需要 | 支持 | 支持 | 免费版每天500次请求 | 国内可直连,响应快,文档清晰 |
| OpenWeatherMap | 需要 | 部分支持(城市拼音) | 不支持 | 免费版每分钟60次 | 注册流程略繁琐,需要英文城市名,返回全英文 |
| wttr.in | 不需要 | 支持 | 支持 | 无限制 | 海外服务,部分网络环境请求不稳定,返回格式多样 |
| 和风天气 | 需要 | 支持 | 支持 | 免费版每天1000次 | 功能最强,但API路径多,初学者容易迷失 |
仅代表我个人观点,对"AI课程自用"这种学习项目,最不该选的是OpenWeatherMap,因为城市名要先转拼音,返回的温度单位换算还要再处理一步,容易把初学者绕晕。和风天气本身很好,但它的接口分类太细:实况天气、逐小时预报、空气质量、生活指数,每类一个地址,如果一个初学者看文档看得怀疑人生,那就不太适合作为第一次项目。
当时也考虑过一些完全免key的公共接口,比如某些网页版的天气查询接口,直接GET就能返回JSON。这类接口最大的问题是不稳定,可能今天能用明天就加了防盗链,或者返回的数据结构改了,代码昨天还跑着今天就报KeyError。既然是课程教学用的程序,稳定性比节省注册时间重要得多。
2.2 最终还是选了它:决策依据和免费额度说明
最终我选择了心知天气,理由有三个:
第一,国内服务器直连稳定,不需要额外处理网络代理问题,这对初学者极其友好。某些海外天气API在国内网络环境下响应时快时慢,一个发请求的练习题如果经常超时,学员就会开始怀疑自己代码写错了,排查方向跑偏。
第二,返回结果自带中文。心知的实况天气接口返回的JSON里直接就有"text": "多云"这样的字段,省去了把Clouds翻译成"多云"的映射逻辑。别小看这一步,它省掉的虽然只有几行字典映射代码,但给零基础学员减少了很多挫败感。
第三,免费额度非常适中。心知免费版每天500次请求,对于一个自用的、最多早晚各跑一次的小程序来说,一天2次调用根本用不完这个额度。课程班级里30个学员同时测试也没问题,每人一天试十几遍都够用。它每周还有免费短信提醒,可以绑定手机号,额度快用完了会通知你——当然,自用项目基本碰不到这个提醒。
2.3 注册与获取key的完整路径:心知天气实操
去心知天气官网注册账号是免费的,进入控制台之后创建一个"个人版"项目,创建完能看到两个关键信息:API URL和API KEY。这里有一个很多教程没写清楚的细节:心知的API地址有两种形式,一种是用公网地址https://api.seniverse.com/v3/weather/now.json,另一种是控制台里直接给你拼好的域名,形如https://x-api.seniverse.com/v3/weather/now.json。两种都能用,但前者更通用,文档里也主要用这个。
按照官方文档,请求实况天气只需要三个参数:
key:你的API密钥location:城市名,可以是中文城市名、拼音或城市IDlanguage:返回语言,固定传zh-Hans就是简体中文
完整的请求URL长这样:
https://api.seniverse.com/v3/weather/now.json?key=你的密钥&location=北京&language=zh-Hans把这个地址直接粘到浏览器地址栏里访问,就能看到一串JSON,里面有实时温度、天气现象文字、湿度、风速、体感温度、观测时间等字段。注册这一步对于整个项目来说是最繁琐的,但只需要做一次。一旦确认浏览器能返回数据,就可以确定网络和key都没问题,之后再写代码就是水到渠成的事。
提示:在浏览器里测试接口时,一定注意URL里别出现中文字符乱掉的情况。有些浏览器会自动对中文做URL编码,这会给初学者造成干扰,建议测试时城市名直接用拼音,比如
location=beijing,返回结果和中文城市名一模一样。
3. 程序主体拆解:从输入两个人名到输出完整问候
3.1 名字和城市的获取:三种输入方式的取舍
拿到key之后,第一版程序我直接用了两个input():
name = input("请输入你的名字:").strip() city = input("请输入你所在的城市:").strip()这里必须加.strip(),因为用户在控制台输入时经常带着意外的空格,光标随手一碰就多了一个空格。不处理的话,城市名"北京 "(注意末尾空格)传到API,大概率返回"城市不存在"。
对于课程教学,这种顺序输入的方式思路直接,但从工程角度看,每次运行都要手输一遍名字和城市,挺烦的。所以第二版我加了命令行参数支持,让程序可以这样运行:
python weather_greeting.py --name 小明 --city 上海用argparse标准库解析参数,如果用户没传参数,再回退到input()交互式输入。这个设计的好处是:上课演示的时候一条命令行就直接出结果,不用现场敲字;平时自用也可以配系统计划任务时把参数固定写在命令里。
判断逻辑很简单:
import argparse parser = argparse.ArgumentParser(description="个性化天气问候小工具") parser.add_argument("--name", type=str, help="你的名字") parser.add_argument("--city", type=str, help="城市名") args = parser.parse_args() name = args.name if args.name else input("请输入你的名字:").strip() city = args.city if args.city else input("请输入你所在的城市:").strip()3.2 调用天气接口:requests发送请求与响应解析
Python标准库里的urllib能用,但写起来确实繁琐,设置超时、处理状态码都得手动折腾好几行。所以我的第一建议仍然是requests(注:如果你的Python环境连不上外网或whl包安装困难,用urllib也可以,代码逻辑不改变,只是写法上requests更简短)。安装方式:
pip install requests国外服务器或公司内网环境如果pip源不通,可以加-i参数指定国内镜像源(比如清华PyPI镜像),这个属于环境问题,网上有大量说明,这里不展开。
核心请求代码就四行:
import requests API_URL = "https://api.seniverse.com/v3/weather/now.json" API_KEY = "你自己的密钥" params = { "key": API_KEY, "location": city, "language": "zh-Hans", } resp = requests.get(API_URL, params=params, timeout=10) data = resp.json()这里有几个很多初学者会犯的错误,我提前说:
params参数是requests库自动编码的,不需要手动拼URL字符串。手动拼接时如果城市名含中文,必须自己处理好URL编码,否则请求会失败。用params字典,requests会搞定一切。- 一定要设置
timeout。不设置的话,如果网络断了,程序可能卡在那里两三分钟才报错,体验很差。设置10秒,超时就进入异常处理分支,给用户一个友好提示。 resp.json()要求响应内容必须是有效JSON。如果API返回了{"status": "error"}这种错误结构,.json()不会报错,但后续取字段时才会炸。所以更稳妥的写法是先把JSON存到变量里,判断一下有没有results字段,再来取数。
心知天气返回的JSON结构大致是这样的:
{ "results": [ { "location": { "name": "北京", "path": "北京,北京市,河北" }, "now": { "text": "多云", "code": "4", "temperature": "22", "feels_like": "23", "humidity": "55", "wind_direction": "南", "wind_speed": "5.2" }, "last_update": "2024-04-01T08:30:00+08:00" } ] }注意temperature字段是字符串类型,不是数字。没关系,我们输出时本来就是要拼进句子里。但是如果你要做"温差计算"之类的操作,就必须先float()转类型,这一步最容易漏。
3.3 天气数据的二次加工:汉化状态、气温体感、风力和湿度
拿到JSON后,取出关键字段并拼成一行可读的天气描述:
now = data["results"][0]["now"] weather_text = now["text"] temperature = now["temperature"] feels_like = now["feels_like"] humidity = now["humidity"] wind_direction = now["wind_direction"] wind_speed = now["wind_speed"] weather_desc = (f"实时天气:{weather_text},气温 {temperature}℃," f"体感 {feels_like}℃,湿度 {humidity}%," f"{wind_direction}风 {wind_speed}km/h")心知的text字段本来就是中文,比如"多云""晴""小雨",所以不用翻译映射。这里我要强调一下"体感温度"这个字段的用途:气温22度,体感23度,看起来差别不大,但它能告诉用户一个真实感受,尤其冬夏温差大的城市,体感和实际气温能差出四五度。这个小细节在问候语里很能体现"个性化"三个字。
另外,根据天气现象为问候语加上一点简单逻辑,比如:
if any(word in weather_text for word in ["雨", "雪", "冰雹"]): tip = "出门记得带伞,多穿一点。" elif weather_text == "晴": tip = "阳光不错,适合出去走走。" elif "雾" in weather_text or "霾" in weather_text: tip = "空气质量不太好,建议戴口罩。" else: tip = "天气中庸,该干嘛干嘛。"这段逻辑写得像规则引擎的雏形,对初学者来说也是很好的思维训练——怎么根据有限字段生成差异化提示。虽然AI课程后面会学习怎么用大模型生成自然语言,但用最朴素的if-elif写出第一版提示逻辑,反而能让学员理解"模板+规则"和"生成式"之间的区别。
3.4 兜底逻辑:网络异常、城市不存在、返回空数据
没有异常处理的小程序,就像没买保险的司机,大部分时间没事,一有事就是大事。我在这个程序里重点处理了三种异常:
第一种:网络层异常(DNS解析失败、连接超时、SSL证书问题)
try: resp = requests.get(API_URL, params=params, timeout=10) except requests.exceptions.Timeout: print("网络超时了,稍后再试吧。") return except requests.exceptions.ConnectionError: print("网络连接失败,检查一下网络设置。") return except requests.exceptions.RequestException: print("请求出错,程序退出。") return这里注意requests.exceptions.RequestException是上面两个异常的基类,所以如果嫌分支太细,可以只写这一个except,然后打印具体的错误对象。但课程里我故意把超时和连接失败分开,因为这是初学者最容易遇到的两个网络问题,分开能让学员看到错误信息时直接对症下药。
第二种:API返回业务错误(key无效、城市不存在、请求超限)
data = resp.json() if "results" not in data: error_status = data.get("status_code", "未知错误") print(f"天气服务返回错误:{error_status}") return心知天气的错误返回是{"status": "error", "status_code": "APILIMIT"}之类的结构。这里还应该打一下resp.text看看原始返回,很多初学者一看到resp.json()解析成功就高兴地往下写了,结果取data["results"]时直接KeyError,其实只要先判断一下"results" in data就不会这样。
第三种:数据字段缺失或格式异常(比如API返回的temperature是None)
这种比较少见,但一旦发生,程序直接崩溃。简单处理方式是取数时给默认值:
temperature = now.get("temperature") or "N/A".get()方法比[]安全,键不存在时返回None;or "N/A"又能在值为空字符串时兜底。这一行代码对初学者来说非常提神,很多人在日常工作里也是用这种写法来处理"可能缺失"的字段。
另外还要处理一种很隐蔽的情况——用户输入的城市名带"市"字而API不认。我测试过心知接口,"北京市"和"北京"都能返回数据,但个别城市比如"吉林市"如果不加"市"字会被识别成吉林省,加"市"字才精确到吉林市这个城市。这类细节只能在实际使用中积累,做法是在请求前把用户输入原样传给API,失败后再尝试加"市"字,或者让用户自己二选一。课程项目做到"原样传参+友好报错"即可,不必过度设计。
4. 彩色输出实现:ANSI转义序列与Windows终端适配
4.1 ANSI颜色原理:从控制台字符到RGB前景色
现在到了这个程序的颜值担当——彩色输出。很多刚接触Python的人感觉终端就是一个黑底白字的黑白世界,其实完全不是这样。现代终端(包括Windows Terminal、VS Code集成终端、macOS的Terminal.app)都支持ANSI转义序列,用一串特殊字符就能改变后面文本的颜色和样式。
ANSI转义序列的格式是\033[代码m。\033是ESC键的十六进制表示(十进制27),后面跟着以m结尾的控制代码。比如:
\033[31m:红色前景\033[32m:绿色前景\033[1m:加粗\033[0m:重置所有样式
日常使用中,打印一行红色字只需要:
print("\033[31m这一行是红色的\033[0m")这个\033在Python字符串里要写成\033(八进制转义)或\x1b(十六进制转义),效果一样。为了代码可读性,我推荐定义一组常用颜色常量:
RESET = "\033[0m" BOLD = "\033[1m" GREEN = "\033[32m" YELLOW = "\033[33m" BLUE = "\033[34m" MAGENTA = "\033[35m" CYAN = "\033[36m" RED = "\033[31m"这些是基础色(8色或16色方案)。现代终端还支持256色,格式是\033[38;5;数字m;更高级的是24位真彩色,格式\033[38;2;R;G;Bm,其中R、G、B是0到255的十进制整数。也就是说,理论上你可以在终端里用任意RGB颜色画图。
4.2 渐变和分段着色:让问候语真正"个性"
我设计的最终输出是这样的:
======================================== 亲爱的 小明,早上好!今天北京天气如下 ======================================== 实时天气:多云,气温 22℃,体感 23℃, 湿度 55%,南风 5.2km/h 出门记得带伞或注意防晒。 ========================================其中"小明"用青色+加粗,城市名用黄色,天气温度行用绿色,提醒行用红色(如果天气恶劣)或黄色(普通提醒),框线用蓝色。实现起来很简单,就是拼接字符串:
greeting = (f"{CYAN}亲爱的 {BOLD}{name}{RESET},早上好!" f"今天{YELLOW}{city}{RESET}的天气如下")这里有个小坑:{BOLD}加粗之后,如果后面接{RESET}会把加粗也重置掉,所以要先重置再换颜色。我的经验是每次颜色切换都老老实实写{RESET},然后接新的颜色码,不要指望"上一段的颜色能自动延续",各终端对状态栈的处理并不完全一致。
再进一步,可以做一个简单的渐变效果。比如把"早上好"三个字分别染成红橙黄三色,用256色或RGB实现:
def gradient_text(text, start_color, end_color): result = "" length = len(text) for i, char in enumerate(text): ratio = i / (length - 1) if length > 1 else 0 r = int(start_color[0] + (end_color[0] - start_color[0]) * ratio) g = int(start_color[1] + (end_color[1] - start_color[1]) * ratio) b = int(start_color[2] + (end_color[2] - start_color[2]) * ratio) result += f"\033[38;2;{r};{g};{b}m{char}" return result + RESET调用时传两个RGB三元组,比如从金色(255, 180, 50)到红色(255, 50, 50),就能输出一行字从橙色渐变到红色。这个功能纯属装饰,但对初学者的震撼力很强,能立刻勾起"我也要做一个"的兴趣。课程里我一般把这个当进阶题留作业,让学员自己写RGB插值逻辑。
4.3 Windows下的经典坑:colorama初始化、GBK字符问题
如果你是在Windows的命令行(cmd.exe)里直接运行这个程序,会遇到一个很尴尬的情况:打印出来的不是颜色,而是一堆←[32m之类的乱码,或者干脆就显示[32m。原因很简单:老版Windows控制台的ConHost不默认支持ANSI转义序列,它把这些字符当普通文本显示了。
解决方案是安装一个colorama库,在程序最开始调用初始化函数:
pip install coloramaimport colorama colorama.init()colorama.init()做了一件神奇的事情:它会拦截所有输出,把ANSI转义序列转换成Windows控制台能识别的API调用,从而让颜色在CMD和PowerShell里都能生效。而且这个库用起来是透明的——初始化之后,我们正常使用\033[或\x1b[即可,colorama在处理后会自动恢复。
不过要注意,如果你用的是Windows Terminal(Win11自带的那个新终端)或VS Code的集成终端,即使不初始化colorama也能正常显示颜色。但为了兼容性,我建议无论如何都加上colorama.init(),代码多了两行,却免去了一堆"为什么我这台电脑没颜色"的疑问。
另一个Windows经典坑是编码问题。现代Windows的cmd默认代码页是GBK(代码页936),而心知天气返回的JSON是UTF-8编码,requests库会自动解码成Python的str对象,所以内存里没有问题。但如果你把print()出来的中文重定向到文件里,或者作为子进程输出传递给别的程序,就可能出现GBK和UTF-8互相打架的情况。
最稳妥的做法是在代码开头强制设置标准输出编码:
import sys sys.stdout.reconfigure(encoding="utf-8")sys.stdout.reconfigure是Python 3.7引入的方法,不需要重新赋值sys.stdout,直接原地改配置。如果你用的还是老版本Python,就只能用sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")这种方式。新方法更干净。
还有一个容易被忽略的点:emoji天气图标。我最初版本想用☀️🌧️❄️这样的表情符号做天气图标,测试下来在Windows终端里显示效果极差——新终端勉强支持,旧CMD会显示成方框。最后干脆放弃emoji,改用纯文本加颜色区分。课程里我也会跟学员强调:跨平台程序里符号选择要保守,不要为了好看而牺牲兼容性。
5. 运行实测与问题排查:一批真实跑出来的结果
5.1 三种场景实测:晴天、阴天刮风、城市找不到
写完全部代码后,我做了几组真实场景测试,这里记录一下真实输出,方便大家对最终效果有直观感受。
场景一:北京,晴天,随机名字"王小明"
======================================== 亲爱的 王小明,早上好!今天北京天气如下 ======================================== 实时天气:晴,气温 28℃,体感 29℃, 湿度 20%,北风 15km/h 阳光不错,适合出去走走。 ========================================这一组在VS Code集成终端里运行,颜色正常,换行位置也符合预期。晴天的提示词"阳光不错,适合出去走走"是规则分支里判断weather_text == "晴"时输出的。
场景二:重庆,雨天,名字"李小雨"
======================================== 亲爱的 李小雨,早上好!今天重庆天气如下 ======================================== 实时天气:中雨,气温 19℃,体感 18℃, 湿度 95%,西北风 6km/h 出门记得带伞,多穿一点。 ========================================符合预期。注意这里"体感18℃"比气温还低1度,这在雨天很常见——湿度大导致体感比实际温度更低。
场景三:城市输入"火星"
请输入你所在的城市:火星 天气服务返回错误:NOT_EXIST 请检查城市名是否正确,或换一个城市试试。这个要走异常分支。心知天气返回的状态码NOT_EXIST表示城市不存在,我的代码检测到"results" not in data后,打印友好提示并优雅退出,不抛堆栈。对用户来说这比一串英文Traceback友好一百倍。
5.2 实测中踩到的坑:星号在CMD里变框、终端颜色被禁用
这一节专门记录我踩过的几个坑,都很有代表性。
第一个坑:分隔线字符的显示问题。我最初用一整行********做分隔线,因为*在等宽字体下对齐好、打字方便。结果在Windows旧CMD里测试,*居然显示成了竖条和方框——这是CMD把部分ASCII字符当作制表符绘制符导致的。换成=号或-号就完全正常。我最后选用了=号,因为-号在行首有时会被某些终端当成列表标记。
第二个坑:终端颜色被"自动禁用"。有一次脚本作为计划任务运行时,所有颜色都不生效了,输出全变成白底黑字。排查半天发现是Windows计划任务默认使用"服务账户"运行程序,该session没有桌面终端的颜色配置。解决方案是计划任务里勾选"只在用户登录时运行",并且运行程序时用cmd /c包一层:
cmd /c python weather_greeting.py --name 王小明 --city 北京有些时候终端的NO_COLOR环境变量也会导致颜色失效。如果你的程序输出颜色没问题,但别人的环境里不显示颜色,可以检查是否设置了NO_COLOR。这是个社区约定的标准环境变量,一旦设置为非空值,程序就应该禁用颜色输出。我们这个小工具没做这个检测,但如果你想把它往"严谨工具"方向打磨,这是一个值得做的细节。
第三个坑:打印换行多了一个空行。之前说过我在开头用sys.stdout.reconfigure(encoding="utf-8")改编码,实测完全没有问题。但在Windows上,如果终端本身设置了滚动缓冲区特别小,输出内容超过一屏后,前面的内容会被截断,看起来像"多了一个空行"。其实不是空行,是终端翻页时丢了一行。解决办法很简单:把字体调小一点,或者把滚动缓冲区调大。这种问题不属于代码bug,但测试时遇到了别慌。
5.3 打包成exe的思路和注意点
课程作业交到后期,总有学员想把程序做成exe发给朋友直接双击运行。打包工具我用过PyInstaller和Nuitka,这里的经验是:
- 用
PyInstaller打包最简单,一条命令搞定:pyinstaller -F weather_greeting.py。加-F参数打包成单文件。但单文件启动时会把临时文件释放到系统临时目录,所以第一次启动会慢一点,几秒到十几秒不等。 - 打包出的exe体积会比较大(因为把Python解释器和所有依赖库都打进去了),大概5~10MB。别嫌弃,这是正常的。
- 如果你的程序用了
colorama、requests,PyInstaller会通过import关系自动收集依赖,不需要额外配置。但有心知天气API的key是写死在代码里的,打包前记得把key用--key参数或配置文件方式传入,避免别人反编译你的exe直接拿到key。 - 反编译这个问题,实际上exe里嵌的Python字节码很容易被提取,所以不要把任何重要的API密钥放进去。如果只是自用,把exe分享给朋友,那就在程序里加一句"如果key失效请联系我",别把密钥当宝贝藏代码里,因为藏不住。
一个更优雅的做法是让程序从环境变量或配置文件里读取key:
import os API_KEY = os.getenv("SENIVERSE_API_KEY", "默认key")这样即使exe被反编译,别人看到的也只是读取逻辑,拿不到真实key。当然这只是入门级的保护,别指望它能挡住高手。
6. 还能怎么玩:个人实际使用中的扩展方向
6.1 从"用一次"到"每天开机自动运行"
程序跑通之后,我做的第一件实事是把它挂到系统启动流程里。在Windows上可以用"任务计划程序"新建一个基本任务,触发器选"计算机启动时"或"登录时",操作选"启动程序",程序填python.exe的路径,参数填weather_greeting.py --name 王小明 --city 北京。这样每次开机系统都会弹出一个窗口,显示今日天气问候。我实际用了两个月,每天早上洗漱前扫一眼屏幕,比打开天气App还省事。
macOS或Linux上做法类似,用crontab或launchd。例如在Linux/云服务器上想每天早晨8点发一条天气到终端日志或通知里,cron表达式就是0 8 * * * python /home/user/weather_greeting.py --name xxx --city xxx。有条件的话,也完全可以把它接到企业微信、Slack机器人、钉钉群机器人,无非是请求天气后再POST一个webhook。这一步能很自然地引导初学者接触自动化运维的初步概念。
考虑到AI课程的属性,也有的同学会把这个程序放进Jupyter Notebook环境里跑,输入框用!python调用脚本或直接调用自定义函数,试验配色效果。个人测试下来,在Notebook里ANSI颜色支持要看前端,尤其VSCode的Notebook对颜色支持不是很好,建议在Notebook阶段只做逻辑测试,最终效果放到终端里看。
6.2 与AI课程结合起来:把天气数据喂给模型做上下文
这个程序如果叫"AI课程自用",那它最好真的能和AI有点关系。目前我做的第一层结合是:把天气数据当成结构化文本,配合一个简单的提示词模板,交给AI生成更生动的问候语或出行建议。
代码逻辑不复杂,本质是把之前if-elif规则生成的提示词,替换成大模型API调用:
prompt = ( f"当前城市:{city},天气:{weather_text},气温:{temperature}℃," f"体感:{feels_like}℃,湿度:{humidity}%,风速:{wind_speed}km/h。" f"请用一句轻松活泼的话问候{name},并给出恰当的出行建议(30字以内)。" )把prompt交给大模型API,返回的文本再套上彩色输出框架。这里要注意的是,AI生成文本可能很长,所以在提示词里明确约束"30字以内",输出后也要做一次截断或长度检查,防止排版被撑乱。
第二层结合更有意思:把天气数据作为"工具调用"训练素材。很多课程讲Function Calling时用的例子要么是定的闹钟,要么是查数据库,而我们的天气程序天然就是一个可以被大模型调用的外部工具。按照OpenAI等平台的Function Calling格式,把"查询天气"声明成一个可被模型调用的function,模型在用户问"今天北京天气怎么样"时,会先调用我们的API获取数据,再组织回答。
这样原本一个"自用的小玩具",就能串联起AI课程里"提示词工程""上下文构造""工具调用""结构化输出"四个核心章节,而且全部建立在学员已经写过的真实代码上。课程反馈里,很多学员说做完这个小程序,再去看大模型API文档,心理上完全没有距离感了——因为数据请求、JSON解析、异常处理这些底层能力,他们已经在天气程序里练过了。
6.3 个人使用中的进一步打磨:换肤、日志、多城市
趁这个项目还在手上,我又给它加了一些很小的功能,这里分享两个最实用的:
多城市支持。把城市名从一个改成列表,逗号分隔,一次性查询多个城市的天气。心知天气接口的location参数支持城市列表,多个城市用逗号分隔,返回的results数组里每个元素对应一个城市。这样早上起来能同时看到自己所在的城市和老家城市的天气,尤其适合离开家乡上学工作的场景。
日志记录。每次运行把时间、城市、天气结果追加写入一个weather.log文件,便于后面做简单的天气统计。用标准库logging或直接open("weather.log", "a", encoding="utf-8")都行。加上日志后,程序的行为更接近一个真正的小服务,而不是一次性脚本。对AI课程来说,日志数据积累下来也能成为后续数据分析、可视化练习的数据源。
我在实际使用中发现,这个程序的最佳运行频率是每天一次到两次(早晨和午间),千万别设置成每分钟跑一次,浪费自己的免费额度不说,频繁请求还有可能被API服务商临时限流。毕竟是自用工具,稳定温和比啥都强。
最后再说一个小技巧:程序里如果你想把问候语按星期几来变化(周一打鸡血、周五摸鱼快乐提醒、周末轻松一点),直接用datetime.now().weekday()就能拿到0~6的整数,然后做映射。这个功能我加了之后,每天早上开屏的内容不再千篇一律,使用粘性会高很多。