资讯动态

20行Python代码获取历史天气数据:从接口请求到CSV落盘

发布时间:2026/10/14 5:23:16 来源:尧图企业网站定制
1. 为什么我要用20行代码去碰历史天气数据先坦白讲我一开始做这件事的动机特别朴素家里老人要搬家想挑一个气候舒服的城市网上搜到的都是“四季如春”“冬暖夏凉”这种形容词没有量化依据。我想知道的是——某地过去五年里七月平均高温多少、一月极端低温多少、全年降水天数占比多少。这些数据气象网站都有但一个个手动查、复制、整理太折磨人了。后来我发现很多公开气象数据平台其实提供了结构化的历史数据接口返回的就是规整的表格或JSON。这意味着用一段极短的脚本就能把“查天气”这件事自动化。标题里说的“20行代码”不是噱头核心逻辑确实只需要二十行左右——请求、解析、存表三步走完。剩下的篇幅我用来处理那些真正会卡住你的细节字段含义、时间范围限制、反爬边界、数据清洗。这篇文章适合谁看如果你会一点点Python基础知道pip install怎么用那就能直接抄作业。如果你完全没写过代码也没关系我会把每一步在干什么讲清楚你照着改城市名和日期就能跑。最终你会得到一个CSV文件里面是某个城市逐日的历史天气记录可以直接丢进Excel做透视表或者喂给可视化工具画趋势图。需要提前说明的是我下面讲的所有平台和接口都是公开可访问的气象数据服务不涉及任何需要特殊手段才能访问的资源。我们只做合规的数据获取遵守目标站点的使用条款控制请求频率不给人家的服务器添麻烦。这一点在后面“注意事项”里我会反复强调。2. 整体思路拆解为什么是“请求—解析—落盘”三段式2.1 核心链路的选型逻辑任何数据采集任务本质上都是三个动作的串联把数据拿回来、把数据拆开、把数据存下来。历史天气数据也不例外。我见过不少人一上来就想着用复杂的爬虫框架装一堆依赖结果调试半天跑不通。其实对于这种“接口返回结构化数据”的场景标准库加一个请求库就够了。我选择的技术组合是requests负责发HTTP请求json或csv模块负责解析和写入pandas可选——如果你后续要做分析用它存表更方便如果只想快速拿到文件标准库的csv模块零依赖更轻。这个选型的核心考量是降低环境门槛。你不需要装浏览器驱动不需要处理JavaScript渲染因为气象数据接口返回的通常是纯文本格式直接解析就行。为什么不用Selenium或者Playwright这类浏览器自动化工具因为那些工具是为“页面内容由JS动态生成、且没有公开接口”的场景准备的。历史天气数据这种规整信息平台方往往提供了直接的数据出口用浏览器去模拟点击反而是杀鸡用牛刀速度慢、资源占用高、还容易被识别为异常流量。能用接口就不用页面解析这是我做数据采集的第一原则。2.2 数据源的选择与边界公开的历史天气数据来源大致分几类一类是气象部门面向公众开放的数据服务一类是聚合型天气网站提供的历史查询页面还有一类是科研机构发布的再分析数据集。对于个人使用、小批量查询的需求前两类足够。我通常会优先选择返回JSON格式、字段命名清晰、有明确时间范围说明的接口。这里有个关键判断你要先确认目标数据源是否允许程序化访问。很多平台在页面上提供了查询功能但服务条款里写明了禁止自动化抓取。这种情况下即使技术上能做到我也不建议去碰。合规的做法是找那些明确提供数据接口、或者发布了开放数据集的平台。我在实操中会先看平台的robots协议和API文档确认没有禁止性条款再动手。另一个边界是数据量。历史天气数据按天存储一个城市一年365条十年也就3650条。这个量级对任何服务器来说都不算压力。但如果你要爬全国所有城市、所有年份那就是百万级请求必须考虑分批次、加延时、甚至申请正式的数据授权。我下面演示的是单城市、指定年份区间的查询属于合理使用范围。2.3 20行代码的骨架长什么样在动手之前先把骨架搭出来你就能理解为什么行数可以这么少。整个脚本分四块第一块参数配置。把城市名称、起始日期、结束日期定义成变量方便修改。第二块请求构造。拼接出目标接口的URL带上必要的查询参数发送GET请求。第三块数据解析。把返回的JSON或CSV文本转换成Python能操作的结构。第四块写入文件。把解析后的数据逐行写入CSV或者用pandas一次性导出。这四块里真正占行数的是异常处理和字段映射。如果一切顺利核心逻辑确实二十行上下。但我在实际写的时候会多留几行做错误捕获和日志打印因为网络请求这件事失败是常态成功才是偶然。下面我会把每一块展开讲并且告诉你哪些行可以省、哪些行千万别省。3. 核心细节解析字段、参数与那些容易踩的坑3.1 历史天气数据里到底有哪些字段很多人以为天气数据就是“温度天气状况”实际拿到接口返回后会发现字段比想象中多。以常见的日粒度历史数据为例通常包含以下内容字段名示例含义单位/格式注意事项date日期YYYY-MM-DD注意时区部分平台用UTCtemp_max当日最高温摄氏度有的平台返回华氏度temp_min当日最低温摄氏度同上precipitation降水量毫米可能是小时累计或日累计wind_speed风速米/秒或公里/小时单位不统一是常见坑humidity相对湿度百分比可能是均值或特定时刻值condition天气状况文本中文/英文、编码格式需确认我特别要提醒的是单位问题。不同平台对同一物理量的单位选择不同有的用摄氏度有的用华氏度风速有的用米每秒有的用公里每小时。如果你把不同来源的数据混在一起分析不统一单位就会得出荒谬结论。我的做法是在解析阶段就做一次单位换算统一成摄氏度、毫米、米每秒并在CSV表头里标注清楚。还有一个隐蔽的坑是缺失值表示方式。有的平台用空字符串有的用null有的用-999这种哨兵值。如果你直接把这些值当数字参与计算平均值会被严重拉偏。我在写入文件前会做一次清洗把哨兵值替换成空并在后续分析时用dropna跳过。3.2 请求参数的构造与日期范围限制构造请求时最关键的参数是地点标识和时间区间。地点标识有的平台用城市名有的用内部编码你需要先查一次“城市列表”接口拿到编码。时间区间通常有上限比如单次查询最多返回一年数据超过就要分段请求。我一般会写一个循环把大区间拆成按年或按月的小段逐段请求后合并。这样做的好处是即使某一段失败也不会影响其他段而且方便控制请求间隔避免触发频率限制。下面是一个参数构造的示意base_url https://example-weather-api.com/history params { location: beijing, start: 2020-01-01, end: 2020-12-31, format: json }注意format参数有的平台支持json和csv两种返回格式。如果你只是想快速拿到表格直接请求csv可以省去解析步骤但如果要做字段筛选和清洗json更灵活。我通常选json因为结构清晰不容易因为分隔符问题导致解析错位。提示请求头里带上一个正常的User-Agent是基本礼貌也避免被误判为异常流量。但不要伪造身份信息用通用的浏览器标识即可。3.3 解析环节的两种典型数据结构JSON返回的数据通常是两种结构一种是数组套对象每个对象是一条日记录另一种是对象套数组字段名作为键值是一个按日期排列的数组。前者处理起来直观后者需要做一次转置。对于数组套对象的结构用列表推导式就能提取出需要的字段records data[daily] rows [(r[date], r[temp_max], r[temp_min]) for r in records]对于对象套数组的结构需要先确定日期序列的长度再按索引逐条组装dates data[date] temps data[temp_max] rows list(zip(dates, temps))两种方式都不复杂关键是先打印一条样本看看结构。我养成的习惯是拿到响应后先print(json.dumps(data, indent2)[:500])确认字段名和嵌套层级再写解析逻辑。这一步花三十秒能省掉后面半小时的调试。3.4 写入CSV时的编码与表头处理写入CSV有两个细节容易翻车编码和追加模式。中文城市名和天气状况描述涉及非ASCII字符如果用默认编码写入在Windows上打开可能乱码。我的做法是统一用utf-8-sig编码这样Excel直接双击打开也不会乱码。追加模式方面如果你分段请求后逐段写入要用modea而不是modew否则后一段会覆盖前一段。但表头只需要写一次所以第一段用w后续段用a并且后续段设置headerFalse。这个逻辑用一个小计数器就能控制。import csv with open(weather.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([日期, 最高温, 最低温, 降水量]) writer.writerows(rows)newline这个参数也别省否则Windows下每行之间会多出一个空行。这些都是小细节但堆在一起就是“为什么我的文件打开不对劲”的根源。4. 完整实操过程从零跑通一个城市的历史数据4.1 环境准备与依赖安装我假设你用的是Python 3.7以上版本。打开终端执行pip install requests pandasrequests是发请求用的pandas是可选但强烈建议装的后面做数据合并和导出会方便很多。如果你不想装pandas只用标准库也能完成只是代码会多几行。验证安装是否成功python -c import requests; print(requests.__version__)能打印出版本号就说明环境没问题。如果报错检查一下pip是否对应你当前使用的Python版本有时候系统里有多个Pythonpip装到了另一个上面。4.2 第一步确认数据源与城市编码打开你选定的气象数据平台找到“历史天气”或“气候数据”入口。大多数平台会提供一个城市搜索框你输入城市名后浏览器地址栏或网络请求里会出现该城市的编码。把这个编码记下来它就是后续请求的location参数。如果你用的是提供开放数据集的平台通常会有一个说明文档列出所有可用城市及其编码。我建议把常用城市的编码存成一个小字典写在脚本开头CITY_CODES { 北京: beijing, 上海: shanghai, 广州: guangzhou, }这样切换城市只需要改一个键名不用去翻文档。4.3 第二步构造请求并获取原始响应下面这段是核心请求逻辑我加了注释说明每一行在干什么import requests import time def fetch_weather(city_code, start_date, end_date): url https://example-weather-api.com/history params { location: city_code, start: start_date, end: end_date, format: json } headers { User-Agent: Mozilla/5.0 (compatible; DataCollector/1.0) } resp requests.get(url, paramsparams, headersheaders, timeout15) resp.raise_for_status() return resp.json()timeout15是必须的不设超时的话网络卡住时脚本会一直挂着。raise_for_status()会在HTTP状态码不是200时抛出异常避免你把错误页面当成数据去解析。请求之间加一个time.sleep(1)给服务器留出喘息时间。这不是技术必需是基本的网络礼仪也能降低被限流的概率。4.4 第三步解析JSON并提取目标字段拿到JSON后先看一眼结构data fetch_weather(beijing, 2023-01-01, 2023-12-31) print(data.keys()) print(data[daily][0])假设返回结构是{daily: [{date: ..., temp_max: ...}, ...]}提取逻辑就是rows [] for item in data[daily]: rows.append([ item[date], item.get(temp_max, ), item.get(temp_min, ), item.get(precipitation, ) ])用.get()而不是直接索引是为了在字段缺失时返回空字符串而不是报错。历史数据里偶尔会有某天某个要素缺测直接索引会让整个脚本崩掉。4.5 第四步分段请求与结果合并如果时间跨度超过平台单次限制就需要分段。我通常按年切分from datetime import date def year_ranges(start_year, end_year): for y in range(start_year, end_year 1): yield f{y}-01-01, f{y}-12-31 all_rows [] for s, e in year_ranges(2019, 2023): data fetch_weather(beijing, s, e) for item in data[daily]: all_rows.append([...]) time.sleep(1)这样五年数据分五次请求每次间隔一秒总耗时也就几秒钟。合并后的all_rows包含所有日记录顺序是按年份递增的。4.6 第五步导出CSV并做初步校验最后写入文件import csv with open(beijing_weather_2019_2023.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([日期, 最高温, 最低温, 降水量]) writer.writerows(all_rows)写完之后我习惯做一次快速校验用pandas读回来看行数是否等于日期跨度天数看温度范围是否在合理区间比如-50到50摄氏度之间看有没有大量空值。这一步能发现大部分数据质量问题。import pandas as pd df pd.read_csv(beijing_weather_2019_2023.csv) print(df.shape) print(df.describe()) print(df.isnull().sum())如果行数对不上可能是某次请求失败了如果温度出现极端异常值可能是单位没统一如果空值集中在某几个月可能是那段时间数据源本身缺测。这些都需要你根据实际情况判断和处理。5. 常见问题与排查技巧实录5.1 请求返回空数据或报错怎么办这是最常见的问题原因通常有三类参数错误、频率限制、数据源变更。排查顺序建议从参数开始。先确认城市编码是否正确再确认日期格式是否符合平台要求有的要YYYYMMDD有的要YYYY-MM-DD。如果参数没问题看HTTP状态码403通常是频率限制429是请求过多500是服务端问题。遇到频率限制最直接的办法是降低请求速度把time.sleep从1秒加到3秒甚至5秒。如果还是不行考虑换时间段请求比如避开白天的高峰期。我一般不推荐用代理池去绕过限制那既不合规也不稳定而且对公开数据服务来说正常使用根本不需要走到那一步。5.2 解析时字段名对不上平台改版是常有的事今天叫temp_max明天可能改成tempMax或者max_temp。我的应对策略是把字段映射写成配置而不是硬编码在解析逻辑里FIELD_MAP { date: [date, Date, 日期], temp_max: [temp_max, tempMax, max_temp, 最高温], temp_min: [temp_min, tempMin, min_temp, 最低温], }解析时遍历候选名找到第一个存在的就用。这样即使平台微调字段名你只需要在配置里加一个别名不用改核心代码。5.3 数据单位不一致导致分析出错前面提过单位问题这里给一个具体的排查方法拿到数据后先看数值的量级。如果最高温出现“80”这种数字基本可以确定是华氏度如果风速出现“30”以上可能是公里每小时而不是米每秒。确认单位后在解析阶段做换算def f_to_c(f): return round((f - 32) * 5 / 9, 1) def kmh_to_ms(kmh): return round(kmh / 3.6, 1)换算后的值写回CSV并在表头注明单位比如“最高温(℃)”。这样后续无论谁拿到这份数据都不会误读。5.4 常见问题速查表现象可能原因排查动作解决方式返回空列表日期范围无数据换一个已知有数据的日期测试调整日期区间403错误请求头缺失或被限流检查User-Agent降低频率加请求头加延时字段缺失平台改版或数据缺测打印原始响应看结构更新字段映射用get容错数值异常大单位不是摄氏度看数值量级做单位换算CSV乱码编码不匹配用文本编辑器查看改用utf-8-sig行数对不上某段请求失败检查每段返回条数加重试逻辑5.5 几个我踩过的坑和对应心得第一个坑是时区。有的平台返回的日期是UTC时间如果你按本地时间理解可能会差一天。我的做法是看返回数据里有没有时区标识字段没有的话就默认按平台文档说明处理并在CSV里保留原始日期不做转换。第二个坑是闰年。按年分段请求时闰年有366天如果你用固定365去校验行数会误判为数据缺失。校验逻辑要用calendar.isleap判断。第三个坑是重复请求。调试阶段我经常反复跑同一段代码结果给平台发了大量重复请求。后来我加了一个简单的本地缓存请求过的URL对应的响应存成文件下次先查缓存没有再发请求。这既省时间又减少对服务器的压力。注意缓存文件要定期清理否则会越积越多。我一般按天建目录跑完就删。6. 数据拿到之后能做什么几个实用的延展方向6.1 做城市气候对比的量化分析拿到多个城市的CSV后最直接的应用就是对比。比如你想知道哪个城市夏天更凉快可以算七月平均最高温想知道哪个城市冬天更难熬可以算一月平均最低温和极端低温。用pandas的groupby按月份聚合几行代码就能出结果df[month] pd.to_datetime(df[日期]).dt.month monthly df.groupby(month)[[最高温, 最低温]].mean()这个表可以直接画成折线图两条线一对比哪个城市气候更温和一目了然。比看那些“四季如春”的形容词靠谱得多。6.2 识别极端天气事件的频率历史数据里藏着极端天气的规律。比如统计每年最高温超过35度的天数、最低温低于零下10度的天数、日降水量超过50毫米的天数。这些计数能帮你判断一个地方的气候极端程度。hot_days df[df[最高温] 35].groupby(df[日期].str[:4]).size()这个结果可以做成柱状图看极端高温天数是否在逐年增加。当然单城市的数据不足以说明趋势但作为个人参考已经足够。6.3 为出行和生活决策提供依据如果你要选一个城市长期居住或者计划某个月去某地旅行历史天气数据能帮你避开最不舒适的时段。比如你怕热就查目标城市七月的历史高温分布你怕干燥就看相对湿度的月均值。这些信息比任何攻略都客观。我自己的做法是把目标城市近五年的月度统计做成一张表横轴是月份纵轴是平均高温、平均低温、降水天数、平均湿度。一眼扫过去哪个月最适合去清清楚楚。6.4 数据可视化的轻量方案不想装复杂可视化工具的话pandas自带的plot就能出图monthly[最高温].plot(kindline, title月度平均最高温)如果要更好看的图可以用matplotlib或seaborn但那是另一个话题了。对于快速探索数据pandas的默认绘图完全够用。7. 关于合规与频率控制再多说几句做数据采集这些年我越来越意识到一件事技术能力不等于使用权限。一个接口你能请求通不代表你可以无限制地请求。公开数据服务是公共资源每个人都应该节制使用。我的原则是单次任务请求数控制在合理范围内请求间隔不低于1秒遇到429状态码立即停止并延长等待时间不伪造身份绕过限制不将获取的数据用于商业用途或二次分发。这些不是技术限制是使用者的自我约束。另外如果你发现某个平台明确禁止自动化访问那就不要用脚本去碰。可以手动查询、可以申请数据授权、可以找其他开放数据集但不要明知故犯。合规这件事平时看不出差别一旦出问题就是大问题。提示很多平台提供批量数据下载功能比逐条请求更高效也更合规。动手写爬虫之前先找找有没有现成的数据导出入口。8. 我在实际使用中的几点体会这套“20行代码”的方案我从第一次写到现在前后改过十几版。最初版本确实只有二十行但跑起来各种报错后来慢慢加上了超时、重试、字段容错、单位换算、分段请求、本地缓存代码量翻了几倍。但核心逻辑始终没变请求、解析、落盘。我的体会是短代码适合演示长代码适合生产。你在学习阶段可以用最简版本快速跑通理解整个链路但真正要稳定获取数据那些看似“多余”的异常处理和边界检查一个都不能少。还有一个心得是先手动查一次再写代码。我见过有人上来就写脚本结果连目标平台返回什么格式都不知道调试半天。正确的顺序是打开浏览器手动发起一次查询看返回内容长什么样确认字段和结构然后再用代码复现这个过程。手动能跑通代码才有可能跑通。最后分享一个小技巧把城市编码、字段映射、日期范围这些容易变的东西全部抽到脚本开头的配置区。这样下次换城市或换时间段只需要改配置不用动逻辑代码。这个习惯让我在后续维护上省了大量时间。数据这件事拿到手只是第一步洗干净、用起来、得出靠谱结论才是真正的价值所在。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑