简介这是一款基于Python编写的天气查询小程序面向Python初学者及对API调用感兴趣的开发者用于解决实时天气信息获取与展示问题。资源包为RAR压缩格式包含2个文件一个Python脚本和一个SQLite数据库文件整体仅31KB结构轻量适合快速阅读源码也可作为课堂教学示例已有463人学习/下载。程序核心利用requests库发送HTTP GET请求配合json模块解析返回的JSON数据可提取温度、湿度、风速等字段同时通过sys.argv接收命令行地点参数支持灵活查询不同城市并预设了默认地点便于直接运行。数据库文件用于存储城市索引或编号数据便于快速匹配查询。代码中还涉及API密钥配置及基础错误处理完整展示了网络请求、数据解析与命令行交互的流程是一份不错的Python实践样例。1. 天气查询小程序一个命令行工具的“最小完整闭环”很多新手学 Python 时都会有一个困惑语法看懂了但不知道一个真正的软件是怎么串起来的。这个天气查询小程序恰好解决了这个问题——它不是一个只输出“Hello World”的教学片段而是一个能实际运行的命令行工具。你输入城市名它去请求天气服务商的开放接口解析返回的 JSON 数据再按整齐的格式把温度、湿度、天气状况打印到终端里。整个链路覆盖了“用户输入 → 网络请求 → JSON 解析 → 格式化输出 → 异常处理”五个环节可以说是 Python 入门阶段性价比最高的练手项目之一。这个项目适合两类人一是学到函数和模块阶段、想找个完整项目练手的新手二是需要快速获取命令行天气信息、想二次开发成定时提醒或大屏展示的开发爱好者。它的代码量不大但五脏俱全能让你一次性看清一个小型 Python 工程从入口到输出的完整写法。2. 功能拆解与架构设计从城市名到终端天气数据经历了什么2.1 项目定位与核心功能这个项目的定位非常清晰一个以城市名为参数、在终端输出实时天气的命令行工具。它的核心功能集中在三个方面。第一是城市输入。你可以在命令后面直接带上城市名比如“python weather.py 北京”也可以在没有任何参数时进入交互模式程序会提示你输入一个城市名。这个设计兼顾了两种使用场景想在脚本里调用它或者只是临时查一下天气。第二是天气数据获取。程序会拿着城市名去请求第三方的天气 API拿到实时温度、体感温度、天气描述、湿度、风向风力、空气质量等字段。这里涉及一个小知识点中文城市名不能直接拼进 URL需要先做一次 URL 编码。第三是格式化输出。程序不是把 JSON 原样打印出来而是把它整理成类似下面这样的可读格式城市北京 天气晴 温度25°C体感 26°C 湿度42% 风向东南风 3级 空气质量良这个输出格式看似简单但涉及中文字符对齐的问题是很多新手第一次接触终端排版的地方。2.2 技术选型requests 拿到数据再交给标准库处理整个项目用到的库非常克制除了网络请求依赖第三方库 requests 之外其余全部是 Python 标准库。这样选型有它的道理。requests 是 Python 生态里最流行的 HTTP 客户端库它比标准库里的 urllib 好用得多——不需要手动处理编码、不需要构造复杂的 Request 对象、超时和重试也都有现成参数。对于这个项目来说请求天气 API 就是一次简单的 GET 调用requests 可以让你三行代码拿到响应省去大量模板代码。至于 json 标准库用来解析返回的数据argparse 标准库用来接收命令行参数。有趣的是如果用户没有输入城市名程序会用 input() 进入交互模式这意味着 argparse 只处理“可选参数”的场景入口逻辑上做了一个双模式兼容。2.3 目录结构与模块职责一个只有几百行的 Python 项目也值得把代码拆成模块这是我在这个项目里看到的一个好习惯。它的目录结构大致如下weather_query/ ├── weather.py # 入口文件负责参数解析和流程编排 ├── query.py # 网络请求模块封装天气 API 的调用 ├── parser.py # 数据解析模块把 JSON 转成格式化的天气对象 ├── formatter.py # 输出模块负责把天气对象渲染成终端文本 ├── config.py # 配置文件存放 API 地址和密钥 ├── requirements.txt # 依赖清单 └── README.md # 使用说明这个拆分思路值得借鉴。入口文件不写业务逻辑只做参数解析和流程调度网络请求单独成模块方便将来替换 API 服务商解析和格式化分离意味着你可以把解析后的天气对象输出到网页模板而不是只能输出到终端。换句话说只要数据层和展示层分开这个项目的扩展空间就大了很多。3. 核心模块拆解一次天气查询从进入到返回的完整链路3.1 入口模块双模式命令行设计入口文件是理解整个项目的最佳起点因为它把整个流程串了起来。我用伪代码的形式还原它的核心逻辑# weather.py import argparse from query import fetch_weather from parser import parse_weather from formatter import format_weather def main(): # 第一步解析命令行参数拿到城市名 parser argparse.ArgumentParser(description命令行天气查询工具) parser.add_argument(city, nargs?, help要查询的城市名例如北京) args parser.parse_args() # 第二步如果命令行没有传入城市名进入交互模式 city args.city if not city: city input(请输入城市名).strip() if not city: print(城市名不能为空) return # 第三步调用网络请求模块 raw_data fetch_weather(city) # 第四步解析原始 JSON weather parse_weather(raw_data) # 第五步格式化输出 print(format_weather(weather)) if __name__ __main__: main()argparse 的 nargs? 是这里的关键参数它表示“这个参数是可选的如果提供了就赋值给 city如果没提供city 就是 None”。这个设计让程序同时支持“直接查询”和“交互式查询”两种方式是一种很常见的命令行工具设计模式。这种把流程拆成五个步骤的写法最大的好处是每个模块都能独立测试。比如你不想真的发网络请求可以 mock 一个 fetch_weather 的返回值直接测试后面的解析和输出逻辑。3.2 网络请求模块API 对接中的实战细节网络请求模块是整个项目里最容易翻车的地方也是最有实战价值的地方。它要做的事情简单来说只有一件把城市名拼进 URL发 GET 请求拿到 JSON 响应。但这里面有几个容易被忽略的细节。# query.py import requests from config import API_URL, API_KEY def fetch_weather(city: str) - dict: 根据城市名请求天气数据返回原始 JSON 字典。 params { city: city, key: API_KEY, output: json, # 明确要求返回 JSON 格式 } try: resp requests.get(API_URL, paramsparams, timeout5) resp.raise_for_status() # 状态码不是 200 时抛出异常 return resp.json() except requests.exceptions.Timeout: print(请求超时请检查网络连接) raise except requests.exceptions.HTTPError as e: print(fHTTP 错误{e}) raise except requests.exceptions.JSONDecodeError: print(返回数据不是合法的 JSON可能是接口限流或改变了返回格式) raise这里的第一个细节是 params 参数。requests 会把 params 里的字典自动拼接到 URL 上并对中文城市名做 URL 编码省去了手动调 urllib.parse.quote 的步骤。第二个细节是 timeout5这是血泪经验——如果不设置超时一旦 API 服务商挂了你的程序会卡在那里像死机一样。注意到这里还捕获了 JSONDecodeError。很多新手只处理网络异常忘了处理“请求成功但内容不对”的情况。实际上 API 服务商在限流或密钥失效时经常返回一段 HTML 错误页而不是 JSON这时候如果没有这个捕获程序会直接抛出异常崩溃。3.3 数据解析模块把宽泛的 JSON 变成结构化的天气对象拿到原始 JSON 之后不能直接把它塞给输出模块。因为第三方 API 返回的字段名往往不够直观有的叫“temp”有的叫“temperature”还有的是嵌套结构直接使用会导致输出模块写得很痛苦。解析模块存在的意义就是把这种“脏”数据结构转换成一个干净、可读性好的对象。# parser.py from dataclasses import dataclass dataclass class Weather: city: str condition: str temperature: int feels_like: int humidity: int wind_dir: str wind_level: int air_quality: str def parse_weather(raw: dict) - Weather: 把天气 API 返回的原始 JSON 解析成 Weather 对象。 # 大多数天气 API 会把有效数据放在 data 这个字段下面 data raw.get(data, raw) # 用 .get() 而不是 [] 取值避免某个字段缺失时整个程序崩溃 return Weather( citydata.get(city, 未知), conditiondata.get(condition, data.get(weather, 未知)), temperaturedata.get(temperature, data.get(temp, 0)), feels_likedata.get(feels_like, data.get(felt_temp, 0)), humiditydata.get(humidity, data.get(rh, 0)), wind_dirdata.get(wind_dir, data.get(wind_direction, )), wind_leveldata.get(wind_level, data.get(wind_scale, 0)), air_qualitydata.get(air_quality, data.get(aqi, 未知)), )有两个点值得说明。一是 dataclass它是 Python 3.7 之后标准库提供的装饰器让你少写很多init模板代码定义数据类时非常方便。二是 .get() 方法带默认值这种防御性写法。对于外部 API 的返回数据你必须假设它随时可能少字段、改名、变成 null。写不写这些默认值决定了你的程序在 API 变更时是“优雅降级”还是“当场崩溃”。转换后的 Weather 对象就是一个不依赖任何 API 服务商的具体类。你以后想换成另一个天气数据源只需要改 fetch_weather 和 parse_weather 两个模块formatter 完全不用动。3.4 格式化输出模块一个解决中文对齐的小技巧输出模块的原理很简单把 Weather 对象里的各个字段拼成字符串打印出来。但在终端里做“键值对齐”这件事中文会遇到一个特有的麻烦。# formatter.py def format_weather(weather) - str: 把 Weather 对象渲染成终端友好的字符串。 lines [ f城市{weather.city}, f天气{weather.condition}, f温度{weather.temperature}°C体感 {weather.feels_like}°C, f湿度{weather.humidity}%, f风向{weather.wind_dir} {weather.wind_level}级, f空气质量{weather.air_quality}, ] return \n.join(lines)这就是一个典型的 json 模块 join 拼接的解决方案。之所以用 join 而不是多次 print是为了让整个输出作为一个字符串方便将来保存到日志文件或发送到消息队列。这个细节体现了“输出模块只负责渲染不负责 IO”的好习惯。这里可以提一下中文对齐的问题。如果你尝试用 \t 或空格来做对齐会发现不同的终端下宽度不一样。解决这个问题有两个方向一是用全角空格填充二是使用第三方库 like tabulate。这个项目选择了最朴素的做法——按固定顺序输出不强行对齐这个取舍在小工具里是完全合理的。4. 从零跑通与避坑指南安装、运行、排错全流程4.1 环境准备与依赖安装这个项目的运行环境要求非常低。你需要 Python 3.7 或以上版本因为用到了 dataclass以及一个 requests 库。安装过程非常简单pip install requests如果你有 requirements.txt也可以用pip install -r requirements.txt需要提醒的是如果你同时装有 Python 2 和 Python 3建议用 pip3 而不是 pip否则可能装到了旧版本的环境里运行时提示 import requests 失败。4.2 运行方式两种启动形态运行项目有两种方式对应入口模块设计的两种模式。第一种是直接带城市名运行python weather.py 北京第二种是不带参数进入交互模式python weather.py程序会提示你输入城市名。这两种方式的核心逻辑都在 main() 里只是参数来源不同。实际上你还可以在第二个人的基础上配合 Shell 的管道来使用比如 echo 上海 | python weather.py这在做脚本集成时非常实用。4.3 我的踩坑记录三个高频问题的排查方案这个项目虽然小但我踩过的坑不比大项目少。挑三个最有代表性的记录下来。第一个坑是请求超时导致程序卡死。现象是运行后终端没有任何输出光标一直闪烁看起来像死机。原因是 requests.get 没有设置 timeout 参数一旦 API 服务商响应慢程序就一直在等待。解决方法是所有网络请求都加上 timeout5并捕获 requests.exceptions.Timeout 异常。第二个坑是返回数据里没有 “error” 字段但城市名明明不存在。现象是查询一个不存在的城市名程序没有报错而是返回了一个默认城市的天气。原因是很多天气 API 对无效城市名不返回错误信息而是返回一个默认位置的天气数据。解决方法是解析时增加一个校验如果返回的 city 字段和你传入的城市名完全不一致就提示“城市不存在或名称有误”。第三个坑是中文乱码。现象是 Windows 终端下输出的中文变成了乱码。原因是 Windows 终端默认编码是 GBK而 Python 输出的字符串是 UTF-8 编码。解决方法是设置环境变量 PYTHONIOENCODINGutf-8 在运行前执行或者在输出前手动 encode。这个坑在不同操作系统下表现不同Linux 和 macOS 通常不会遇到。5. 二次开发思路从命令行工具升级成一个真正“有用”的程序这个天气查询程序最大的价值不是查天气本身而在于它的模块边界足够清晰为二次开发留足了空间。我在这里分享三个改造方向按难度递增排列。第一个改造方向是支持拼音和模糊匹配城市名。现在的程序需要输入完整的中文城市名在实际使用中不够友好。你可以在入口模块里增加一个城市映射表让 “beijing” 和 “北京” 都能查出来。具体做法是在 fetch_weather 之前加一个预处理函数用一个字典来维护别名关系。这个改造涉及数据结构的应用难度不高但非常实用。第二个改造方向是从命令行升级成 GUI 应用。由于解析模块和输出模块已经分离你只需要写一个 Tkinter 界面把用户输入的 city 传给 fetch_weather再把格式化后的文本显示在窗口的 Label 组件里。核心的业务代码一行都不用改只替换入口模块里的输入输出方式。如果你想把天气数据嵌入到一个更大项目里还可以把 fetch_weather 和 parse_weather 封装成一个返回 dataclass 的函数直接在别的代码里调用。第三个改造方向是增加定时查询和异常提醒。你可以用 schedule 库或者系统的 crontab 定时运行这个脚本然后让程序在天气变化时发送通知——比如检测到 parse_weather 返回的 condition 字段包含“雨”字就调用一个 webhook 发消息到手机。这只需要在 main() 里加一个判断分支完全不需要动其他模块。我从这个项目里学到最重要的一个习惯是写代码之前先把模块边界画好再开始动手。如果一开始就把网络请求、解析、格式化的代码全写在一个文件里后面想加 GUI、想换 API、想加推送都要重新拆一次代码。从那以后我每次写小工具都强制自己至少拆成三个模块入口、逻辑、展示。希望这个拆分思路能帮到你也能让你在用这个天气小程序时加功能不再害怕破坏原逻辑。本文还有配套的精品资源点击获取