ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python调用天气预报API全流程:从请求到封装自己的天气服务

Python调用天气预报API全流程:从请求到封装自己的天气服务 前几天有个朋友问我想在自己写的微信小程序里展示明天会不会下雨是不是得去爬天气网站我告诉他千万别网页结构说改就改反爬机制越来越狠正经做法是走天气预报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?latitude39.90longitude116.40current_weathertrue服务器收到请求之后返回一段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到能用的天气预报文本为了让你不迷路我先把这篇实操要走的路线摆出来选一个合适的天气预报数据源确定它的接口文档和请求格式。搭建本地开发环境安装必要的Python库。构造请求URL用程序发起第一次请求拿到原始JSON。解析JSON把天气代码、温度、风速这些字段变成人能看懂的中文天气预报。处理异常情况和请求频率限制让脚本能长期稳定运行。封装缓存与定时逻辑把临时脚本升级成一个小型天气服务。这六步走完你对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接口没有任何烧脑的鉴权逻辑。有些天气接口需要你在请求头里塞签名、时间戳、加密串那套流程会把新手直接劝退。而我们从最朴素的拼一个URLGET一下解析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_weathertrue表示返回当前天气hourlytemperature_2m,precipitation_probability表示获取逐小时温度和降水概率dailyweathercode,temperature_2m_max,temperature_2m_min表示获取每日天气代码和最高/最低温timezoneAsia/Shanghai表示用东八区时间。把这些参数拼起来最终的URL长这样https://api.open-meteo.com/v1/forecast?latitude39.90longitude116.40current_weathertruehourlytemperature_2m,precipitation_probabilitydailyweathercode,temperature_2m_max,temperature_2m_mintimezoneAsia/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, paramsparams) print(resp.status_code) print(resp.json())这里有个细节值得说不要自己手动把参数拼到URL字符串里而是用params字典让requests库帮你拼。这样做的好处是库会自动对参数做URL编码你不用操心特殊字符转义问题代码也更清晰。如果一切正常resp.status_code会打印200然后控制台会输出一大串JSON。这个JSON结构会比我们在第1节看到的更丰富因为同时包含了当前天气、逐小时预报和每日预报。先别急着处理睁大眼睛仔细看看它的层级——外层是latitude、longitude这些坐标信息然后是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, ttl900): now time.time() cached cache.get(city_key) if cached and now - cached[ts] ttl: return cached[data] resp requests.get(url, paramsparams) data resp.json() cache[city_key] {ts: now, data: data} return datattl900代表缓存15分钟。只要缓存没过期程序就直接用内存里的结果根本不会发起网络请求。这种策略既保护了接口资源也让你自己的程序响应更快——本地内存取数据总比走一趟网络快得多。5.2 异常处理不要裸奔式调用写API调用代码的时候我会默认三件事可能会出问题网络不连通、服务器返回错误、返回的数据格式不符合预期。如果你不做异常处理任何一个问题都会让你的脚本直接崩溃退出。第一层是网络异常。用try-except包住请求捕获requests.exceptions.RequestExceptiontry: resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() except requests.exceptions.Timeout: print(请求超时稍后重试) except requests.exceptions.RequestException as e: print(f网络请求失败: {e})这里有两个细节值得注意一是timeout10强制设置超时时间避免某个请求卡死导致整个脚本挂起二是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个小时。解决办法就是在请求参数里显式指定timezoneAsia/Shanghai或者timezoneauto让服务端根据坐标自动判断时区。这个坑的隐蔽之处在于有时候你只看温度和天气根本注意不到时间差了直到你按时间索引去匹配数据才会发现对不上。第二个坑是坐标精度。有些地图服务给你返回的坐标是6位小数的有些只给你2位小数。对于天气接口来说0.01度的坐标偏差大约对应1公里左右一般来说影响不大。但如果你拿到的是一串来路不明的坐标最好先在地图上验证一下坐标点是不是你想查的城市不要凭感觉。第三个坑是字段缺失。同一个接口在不同时间、不同参数组合下有可能会少返回某些字段。比如你只请求了current_weathertrue那么返回的JSON里就没有hourly和daily这两个对象。这时候如果你依然傻乎乎地去取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, encodingutf-8) as f: return json.load(f) def save_cache(cache): with open(CACHE_FILE, w, encodingutf-8) as f: json.dump(cache, f, ensure_asciiFalse, indent2)把最近请求的数据和请求时间存到文件里下次启动先看缓存不急着打网络请求。这个方法特别适合跑在云服务器上的长期任务。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 21如果要留存历史数据就在脚本里把每次拉到的数据追加到一个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上继续用。
返回列表