Python调用天气预报API全流程:从请求到封装自己的天气服务
2026/9/18 10:55:23 网站建设 项目流程

前几天有个朋友问我:想在自己写的微信小程序里展示"明天会不会下雨",是不是得去爬天气网站?我告诉他千万别,网页结构说改就改,反爬机制越来越狠,正经做法是走天气预报API。后来我一想,这个需求其实特别典型——从天气预报网页API拿数据,再用到自己项目里,几乎是每个开发者都会遇到一次的入门必备技能。

这篇文章我就拿一个真实的免费天气API来做完整演示,从选型、环境准备、发起请求、解析返回、异常处理到封装成自己的服务,把一整条链路都走一遍。内容面向写过几行Python但没系统接触过API调用的同学,也适合想把手头天气需求快速落地的人。看完你就能自己写一个"查天气"的小工具,并且知道怎么让它稳定跑下去。

1. 先搞清楚要什么:从"看天气"到"取数据"

很多人在这一步就卡住了,不是不会写代码,而是没想清楚"网页上的天气"和"API返回的天气"根本是两码事。这个认知不建立起来,后面所有操作都是在碰运气。

1.1 浏览器页面是给人看的,API是给程序看的

你在浏览器里打开天气网站,看到的是排版好的页面:图标、温度、风速、降水概率,信息很直观。但这些数据经过了好几层加工:网页模板、CSS样式、JavaScript渲染,甚至有些数据还是异步加载的。程序想直接从这种页面里抠数据,等于在别人家里翻箱倒柜找一张纸条,费劲且不保险。

天气预报网页API的思路完全不同。它把天气数据以结构化格式直接返回给你的程序,通常是JSON,里面清清楚楚写着"城市名、温度、湿度、风速、降水概率"。程序拿到这个结构体,想怎么用就怎么用。简单说:网页是别人帮你做好了饭端上来,API是把原材料和菜谱直接给你,自己动手丰衣足食。

这也是为什么我说"天气预报网页API获取数据"这件事的本质,是你和远程服务器之间的一次HTTP请求-响应过程。你的程序发一个请求,服务器根据你给的参数(城市、坐标、时间范围等)查好天气数据,然后以JSON格式回复。整个过程通常几百毫秒就能完成。

1.2 一次请求-响应里藏着哪些信息

一个标准的HTTP请求,核心就三样东西:URL地址、请求方法(GET还是POST)、必要的请求头或参数。对于天气API这种只读数据接口,绝大多数情况用GET就够了。构造URL的时候,把查询参数拼在问号后面,比如这样:

https://api.example.com/weather?latitude=39.90&longitude=116.40&current_weather=true

服务器收到请求之后,返回一段JSON。典型的返回内容长这样:

{ "latitude": 39.90, "longitude": 116.40, "utc_offset_seconds": 28800, "current_weather": { "temperature": 23.5, "windspeed": 12.4, "weathercode": 1, "time": "2024-05-20T08:00" } }

看到没有,这里面没有一个字是给人看的"晴"或"多云",只有数字和代码。weathercode: 1代表什么?需要去查接口文档。这也是很多新手第一次接触API时最不适应的地方:数据是拿到了,但读不懂。所以我在第4节会专门讲字段解析和映射。

1.3 全流程路线图:从URL到能用的天气预报文本

为了让你不迷路,我先把这篇实操要走的路线摆出来:

  1. 选一个合适的天气预报数据源,确定它的接口文档和请求格式。
  2. 搭建本地开发环境,安装必要的Python库。
  3. 构造请求URL,用程序发起第一次请求,拿到原始JSON。
  4. 解析JSON,把天气代码、温度、风速这些字段变成人能看懂的中文天气预报。
  5. 处理异常情况和请求频率限制,让脚本能长期稳定运行。
  6. 封装缓存与定时逻辑,把临时脚本升级成一个小型天气服务。

这六步走完,你对"API调用"这件事的理解就基本成型了。以后不管接什么API,支付接口也好、大模型接口也好,底层逻辑都是相通的。

2. 天气API选型:免费源、注册门槛与可靠性怎么平衡

选数据源是决定项目生死的第一步,但反而是最多人随便选的。我不夸张地说,选错天气API的后果,轻则天天报错,重则被限流封号,项目上线第一天就跑不起来。

2.1 主流天气数据源横向对比

市面上的天气预报数据源大概分三类:商业天气服务商、地图/云厂商附带能力、开源或非营利组织提供的公共接口。我做了个对比表格,方便你直观感受差异:

数据源类型代表是否需要Key免费额度适合场景
商业天气服务和风天气、心知天气需要注册有限免费调用对数据精度、稳定性要求高的商业项目
云厂商附带高德开放平台、腾讯位置服务需要注册按量免费,有QPS限制已经用了该厂商其他服务的项目
开源公共接口Open-Meteo通常不需要宽松,但要求合理使用学习、个人项目、低频小流量应用

商业服务商的优势是数据准确、文档规范、服务稳定,但需要注册账号、实名认证、申请Key,还要理解配额和计费规则。云厂商的天气API通常是他们整体服务的一部分,如果你已经在用高德地图SDK,顺带接个天气接口非常顺滑。开源公共接口的优势是零门槛,不用注册就能用,适合拿来学习和快速验证想法。

很多人在这一步有个误区:以为"免费=随便用"。完全不注册的接口往往有更严格的访问限制,只是限制写在文档里,没人在你注册时提醒你。我见过有人拿免费接口写了个定时脚本每分钟请求一次,跑了一个小时就被封IP了,这就是没搞清楚接口的合理使用边界。

2.2 为什么用Open-Meteo做教学示例

这篇实操我选Open-Meteo作为示例数据源,理由很直接:不需要注册、不需要API Key、文档清晰、返回结构标准,完全符合"从天气预报网页API获取数据"的学习目标。它提供的接口基于经纬度查询天气,同时支持当前天气、逐小时预报和未来7-15天的每日预报,对做一个完整的天气应用来说足够了。

这里要特别强调一下:选它当示例,不代表我建议所有商业项目都去用它。生产环境的选择逻辑应该是"你的业务对数据的时效性、准确性、合规性要求有多高,你的预算有多少",而不是"哪个免费用哪个"。但作为学习API调用、理解数据结构的入门示例,它几乎是零成本且无痛的。

用Open-Meteo还有一个好处:它是标准的HTTP GET接口,没有任何烧脑的鉴权逻辑。有些天气接口需要你在请求头里塞签名、时间戳、加密串,那套流程会把新手直接劝退。而我们从最朴素的"拼一个URL,GET一下,解析JSON"开始,先把API调用的主干逻辑学会,以后再接触那些需要鉴权签名的接口,只是往主干上加枝加叶而已。

2.3 商业/半商业API接入时的合规意识

说完免费接口,我还是得提醒一句:如果你做的是对外发布的产品,或者在公司项目里用,务必先看清楚接口的使用条款。有些天气数据源要求署名,有些限制商用,有些要求最低充值才能调用,这些条款如果没看清,等产品做大了被人追责,那就不只是改代码能解决的事了。

实际操作中,我的建议是:学习阶段用Open-Meteo这种无需注册的接口;进入正式开发阶段,再根据项目的实际需求选择合适的商业服务商。换接口的代价其实没有想象中那么大,因为我们已经把"获取数据-解析数据-组装逻辑"解耦了,到时候只需要改请求地址和字段映射层就够了。

3. 环境准备与第一个请求:让天气数据落到本地

前面说了一堆选型和设计思路,现在进入动手环节。这一节的目标很明确:在本地跑通第一个脚本,成功从天气预报API拿到真实数据。

3.1 工具链选型与准备

做这个案例,你需要Python 3.8以上版本,以及两个库:requests用于发起HTTP请求,json是Python标准库,用来解析返回结果。之所以用Python,是因为它处理这种网络请求和JSON数据最直接,代码量少,适合演示核心逻辑。你要是熟悉Node.js、Go、Java,思路完全一样,只是语法不同。

安装requests库,在终端里执行:

pip install requests

如果你用的是国内网络环境,pip下载慢或者失败的话,可以换用国内镜像源加速:

pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple

装完可以顺手验证一下版本,确保环境没问题:

python -c "import requests; print(requests.__version__)"

能打印出版本号就说明环境OK了。接下来我建议你建一个干净的目录,比如weather_demo,在里面新建一个weather.py文件,所有代码都写在里面。别贪多,先跑通一个文件再说。

3.2 构造请求URL:参数决定你能拿到什么

Open-Meteo的接口地址是https://api.open-meteo.com/v1/forecast,核心参数有两个:latitude(纬度)和longitude(经度)。这两个坐标值决定了你查的是哪里的天气。

我这里用北京的坐标做示例:纬度39.90,经度116.40。然后按需追加一些参数,比如current_weather=true表示返回当前天气,hourly=temperature_2m,precipitation_probability表示获取逐小时温度和降水概率,daily=weathercode,temperature_2m_max,temperature_2m_min表示获取每日天气代码和最高/最低温,timezone=Asia/Shanghai表示用东八区时间。

把这些参数拼起来,最终的URL长这样:

https://api.open-meteo.com/v1/forecast?latitude=39.90&longitude=116.40&current_weather=true&hourly=temperature_2m,precipitation_probability&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=Asia/Shanghai

你可以直接在浏览器里打开这个URL,会看到一串JSON。这一步强烈建议先做,因为让你先在浏览器里看到返回数据,你才会知道程序拿到的是什么。很多新手一上来就写代码,结果程序报错都分不清是网络问题、参数问题还是字段问题。

3.3 第一次请求:代码让数据落到本地

浏览器能访问,说明接口通、参数对。现在写代码。先写一个最小版本,把数据请求回来并打印出来:

import requests url = "https://api.open-meteo.com/v1/forecast" params = { "latitude": 39.90, "longitude": 116.40, "current_weather": "true", "hourly": "temperature_2m,precipitation_probability", "daily": "weathercode,temperature_2m_max,temperature_2m_min", "timezone": "Asia/Shanghai", } resp = requests.get(url, params=params) print(resp.status_code) print(resp.json())

这里有个细节值得说:不要自己手动把参数拼到URL字符串里,而是用params字典让requests库帮你拼。这样做的好处是,库会自动对参数做URL编码,你不用操心特殊字符转义问题,代码也更清晰。

如果一切正常,resp.status_code会打印200,然后控制台会输出一大串JSON。这个JSON结构会比我们在第1节看到的更丰富,因为同时包含了当前天气、逐小时预报和每日预报。先别急着处理,睁大眼睛仔细看看它的层级——外层是latitudelongitude这些坐标信息,然后是current_weather对象、hourly对象、daily对象,每个对象下面又有一组数组。理解了这个嵌套结构,下一步的解析就是顺水推舟。

4. 数据解析与字段筛选:把JSON转成能用的天气预报

拿到原始JSON只是万里长征第一步。现在这堆数据还不是"天气预报",只是一堆字段。真正让数据产生价值的,是把字段映射成你能直接使用的信息。

4.1 解析JSON的整体思路

解析JSON的核心就一句话:先看结构,再取字段。不要一股脑地print(resp.json())看半天,而是先用一个变量把解析结果接住,然后分层去取。

data = resp.json()

然后想拿当前温度,就先取current_weather对象,再取它里面的temperature字段:

current = data["current_weather"] temp_now = current["temperature"]

想拿今天最高温和最低温,就得去daily下面的temperature_2m_max数组里取第0个元素:

daily = data["daily"] today_max = daily["temperature_2m_max"][0] today_min = daily["temperature_2m_min"][0]

为什么数组取第0个?因为daily返回的是未来几天的预报,第0个元素是今天(取决于你的时区参数),第1个是明天,依此类推。这个索引逻辑是你解析这类接口时最容易搞错的地方,务必心里有数。

我建议你把解析过程分成三段:先取当前天气,再取未来几天的每日预报,最后取逐小时数据。每一段都独立成函数或代码块,这样即使某一段的数据格式变了,也不会影响其他部分。

4.2 常用字段映射表与取舍

Open-Meteo返回的字段很多,但不是每个都要用。我的经验是,做天气预报应用,优先关注这几个字段:

字段含义使用建议
weathercode天气代码,对应晴、雨、雪等必须映射成中文文案和图标
temperature当前温度最核心字段
temperature_2m_max/min每日最高/最低温展示每天的温差
precipitation_probability降水概率判断要不要带伞
windspeed风速户外活动参考
winddirection风向一般应用可以忽略

这里重点说weathercode。API返回的是数字代码,比如0代表晴、1代表基本晴、2代表多云、3代表阴、80代表有阵雨。不同天气服务商的代码表不一样,用之前一定要查对应文档。我在实战中见过有人拿着A家的代码表去解析B家的数据,结果晴天显示成雷阵雨,场面很尴尬。

为了不踩这个坑,你可以建立一张映射表,把这几个常用的代码先映射好:

weather_code_map = { 0: "晴", 1: "基本晴朗", 2: "多云", 3: "阴", 45: "雾", 51: "小毛毛雨", 61: "小雨", 63: "中雨", 65: "大雨", 80: "阵雨", 95: "雷阵雨", }

不需要把所有代码都映射,先覆盖你所在地区最常见的情况,后续再按需补充。

4.3 组装一份中文可读的天气预报文本

解析的最终目的,是把数据变成能用的信息。我最常用的方式,是组装成一段可以直接展示或打印的中文文本。这样不管是输出到终端、日志,还是存到数据库、发给用户,都很方便。

def build_weather_text(data): current = data["current_weather"] daily = data["daily"] code_map = { 0: "晴", 1: "基本晴朗", 2: "多云", 3: "阴", 61: "小雨", 63: "中雨", 65: "大雨", 80: "阵雨", } today = daily["time"][0] today_max = daily["temperature_2m_max"][0] today_min = daily["temperature_2m_min"][0] temp_now = current["temperature"] weather_desc = code_map.get(current["weathercode"], "未知") text = ( f"【{today} 天气预报】\n" f"当前天气:{weather_desc}\n" f"当前温度:{temp_now}°C\n" f"今日最高:{today_max}°C\n" f"今日最低:{today_min}°C\n" ) return text

调用一下:

print(build_weather_text(data))

输出效果:

【2024-05-20 天气预报】 当前天气:晴 当前温度:23.5°C 今日最高:28.0°C 今日最低:17.0°C

到这一步,"从天气预报网页API获取天气预报数据"这个核心需求已经跑通了。但别高兴太早,我见过太多项目就死在这一步之后:脚本跑通了一两次,以为大功告成,结果放到服务器上跑几天就各种报错。下一节说的,才是让这个脚本真正活下来的关键。

5. 稳定运行的关键:限流、异常处理与真实踩坑

从"能用"到"稳定用",中间隔着一条名为"异常处理"的河。这一节我把自己实际踩过的坑和你可能会遇到的问题,一次性讲清楚。

5.1 请求频率与缓存策略:对接口保持礼貌

免费天气API最忌讳的就是频繁请求。有些同学为了"实时更新",把脚本设成每5秒请求一次,结果就是IP被封。

我的经验是:天气预报这种数据的时效性并没有那么强,大部分场景下15到30分钟更新一次完全够用。如果你是个人项目,我建议:

  • 定时任务间隔不低于15分钟。
  • 同一个城市的数据,本地缓存一份,缓存未过期前不发起新请求。
  • 多城市场景下,每轮任务串行请求,不要并发打请求。

下面是一个简单的缓存策略示例:

import time cache = {} def get_weather_with_cache(city_key, params, ttl=900): now = time.time() cached = cache.get(city_key) if cached and now - cached["ts"] < ttl: return cached["data"] resp = requests.get(url, params=params) data = resp.json() cache[city_key] = {"ts": now, "data": data} return data

ttl=900代表缓存15分钟。只要缓存没过期,程序就直接用内存里的结果,根本不会发起网络请求。这种策略既保护了接口资源,也让你自己的程序响应更快——本地内存取数据,总比走一趟网络快得多。

5.2 异常处理:不要裸奔式调用

写API调用代码的时候,我会默认三件事可能会出问题:网络不连通、服务器返回错误、返回的数据格式不符合预期。如果你不做异常处理,任何一个问题都会让你的脚本直接崩溃退出。

第一层是网络异常。用try-except包住请求,捕获requests.exceptions.RequestException

try: resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() except requests.exceptions.Timeout: print("请求超时,稍后重试") except requests.exceptions.RequestException as e: print(f"网络请求失败: {e}")

这里有两个细节值得注意:一是timeout=10,强制设置超时时间,避免某个请求卡死导致整个脚本挂起;二是resp.raise_for_status(),如果接口返回4xx或5xx状态码,它会主动抛异常,不会让你拿着错误状态码继续往下解析。

第二层是数据格式异常。即使请求成功,也不能保证返回的JSON里一定有你要的字段。比如服务器临时调整了返回结构,或者某个字段为空,你的程序如果直接data["current_weather"]就会抛KeyError。稳妥做法是用.get()方法并给默认值:

current = data.get("current_weather", {}) temp_now = current.get("temperature", "N/A")

这样做的好处是,即使数据缺失,程序也有一个兜底值,不至于直接崩溃。

第三层是业务层面的异常——比如天气代码不在你的映射表里。这种情况我会在解析时打一条警告日志,然后用"未知"代替,保证整个程序不会因为一个没见过的新代码就中断。

weather_desc = code_map.get(current.get("weathercode"), "未知") if current.get("weathercode") not in code_map: print(f"收到未映射的天气代码: {current.get('weathercode')}")

5.3 真实踩坑记录:UTC时间、坐标精度与字段缺失

接下来是我的私藏踩坑清单。这三个坑,我几乎见一个同事踩一次,写出来帮你省点时间。

第一个坑是时间时区。很多国际通用天气API默认返回UTC时间,如果你没传timezone参数,你会惊讶地发现数据里的time字段比当地时间慢了8个小时。解决办法就是在请求参数里显式指定timezone=Asia/Shanghai,或者timezone=auto让服务端根据坐标自动判断时区。这个坑的隐蔽之处在于,有时候你只看温度和天气,根本注意不到时间差了,直到你按时间索引去匹配数据,才会发现对不上。

第二个坑是坐标精度。有些地图服务给你返回的坐标是6位小数的,有些只给你2位小数。对于天气接口来说,0.01度的坐标偏差大约对应1公里左右,一般来说影响不大。但如果你拿到的是一串来路不明的坐标,最好先在地图上验证一下坐标点是不是你想查的城市,不要凭感觉。

第三个坑是字段缺失。同一个接口在不同时间、不同参数组合下有可能会少返回某些字段。比如你只请求了current_weather=true,那么返回的JSON里就没有hourlydaily这两个对象。这时候如果你依然傻乎乎地去取data["daily"]["temperature_2m_max"][0],肯定会报错。解决思路还是那句老话:先判断字段存在,再决定取不取。

拿我当时遇到的一个实际问题举例:脚本跑得好好的,突然某天早上报了一个KeyError: weathercode,排查后发现是接口在某个特殊维护时段返回了一个精简版结构,current_weather对象都没了。从那次之后,我每个字段都加了兜底判断,再也没有因为天气接口的意外结构而凌晨爬起来修脚本。

6. 把临时脚本变成长期可用的天气服务

如果你只是想在本地跑一次看效果,到第5节就可以收工了。但如果你的目标是让这个天气预报API数据真正服务到你自己的应用里,那还有几步收尾工作要做。

6.1 轻量缓存层:给程序加个记忆

前面第5节已经提到了缓存策略。在实际项目里,我一般会根据使用场景来决定缓存粒度:

  • 个人小工具:内存缓存即可,程序重启就清空,无所谓。
  • 常驻服务:用文件缓存或数据库缓存,比如把最近一次请求结果存为JSON文件,下次启动时先读文件,文件过期了再重新请求。

文件缓存的实现也很简单:

import os import json import time CACHE_FILE = "weather_cache.json" def load_cache(): if not os.path.exists(CACHE_FILE): return {} with open(CACHE_FILE, "r", encoding="utf-8") as f: return json.load(f) def save_cache(cache): with open(CACHE_FILE, "w", encoding="utf-8") as f: json.dump(cache, f, ensure_ascii=False, indent=2)

把最近请求的数据和请求时间存到文件里,下次启动先看缓存,不急着打网络请求。这个方法特别适合跑在云服务器上的长期任务。

6.2 定时拉取与历史留存

定时拉取我推荐用系统的crontab,而不是在Python代码里写死循环加sleep。原因很简单:crontab由系统调度,脚本崩溃了系统会按计划重新拉起;而while True + sleep一旦崩了,下次请求永远都不会来。用crontab的方式,在终端执行:

crontab -e

然后加一行,意思是每30分钟执行一次脚本:

*/30 * * * * /usr/bin/python3 /path/to/weather.py >> /path/to/weather.log 2>&1

如果要留存历史数据,就在脚本里把每次拉到的数据追加到一个JSONL文件或SQLite数据库里。存下来之后可以做很多事情:对比预报准确率、生成一周气温趋势图、分析某个月下雨天数。这些都是"从天气预报网页API获取数据"之后很自然的延伸,但前提是你有历史数据在手。

这是我用的一个简单的SQLite写入思路:

import sqlite3 conn = sqlite3.connect("weather.db") conn.execute(""" CREATE TABLE IF NOT EXISTS weather_log ( query_time TEXT, temp REAL, weather_code INTEGER, precipitation_probability INTEGER ) """) conn.execute( "INSERT INTO weather_log (query_time, temp, weather_code, precipitation_probability) VALUES (?, ?, ?, ?)", (current_time, temp_now, weathercode, pop) ) conn.commit() conn.close()

6.3 进阶方向:多城市监控、天气预警与自然语言输出

等基础版跑通后,你可以按自己的需要往这些方向扩展:

多城市天气监控:把城市和坐标放在一个配置表里,循环遍历去请求,每个城市一份缓存,再按城市分别展示。这里要注意的还是请求频率,城市多了之后更得控制速率,每两个城市请求之间最好间隔一两秒。

天气预警接入:Open-Meteo这类接口本身也提供天气预警相关的扩展数据,或者你可以对接更专业的气象预警接口,关注暴雨、大风、暴雪等极端天气,在自己的应用里做提示。

自然语言输出:把组装好的中文字段再接到大模型接口上,让AI基于天气数据生成一段更人性化的播报,比如"今天午后有阵雨,出门记得带伞"。这也是很受欢迎的一种玩法。

想清楚自己的核心场景再做扩展。如果只是为了自己出门看天气,做一个命令行脚本就够用了;如果是产品功能,那就要认真考虑数据源、缓存和服务稳定性。

最后分享一个我在实际项目中养成的小习惯:接到任何一个API之后,第一件事不是写调用的核心逻辑,而是先写一个最小请求脚本,把返回结果完整打印出来,然后用眼睛读一遍JSON结构,再动笔解析。这个"先看数据、再写代码"的顺序,帮我省掉了大量因为猜字段结构而返工的时间。天气预报API只是开始,这套方法论你可以平移到任何API上继续用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询