气象观测站点数据下载与处理:清洗、质控与可复现工程实践

发布时间:2026/10/26 21:51:58

气象观测站点数据下载与处理:清洗、质控与可复现工程实践 做气象数据这行绕不开的一件事就是你要的那份数据大概率不在一个地方格式也不统一甚至同一份数据前后两次下载还不一样。我这些年做过农业气象服务、做过光伏功率预测的输入数据准备、也帮人搭过小区域的历史气候统计几乎每一次都是从“气象观测站点数据下载与处理”这一件事开始的。这个活看着简单——不就是下个文件、读个表吗但真落到实操里站点清单对不上、时间列时区串了、缺测值被当成真实观测参与平均、单位差了十倍导致曲线整体平移这些坑一个都躲不掉。这篇内容我按实际项目的推进顺序拆开讲先认数据形态再谈怎么把下载做稳然后是解析、清洗、加工、存储最后是我踩过的典型故障和排查思路。读者对象我设想成两类一类是刚接手数据准备工作的同学能照着步骤直接跑通另一类是想把流程工程化的同行可以重点看任务清单设计和校验那几节。文中代码是示意性的参数和阈值需要按你的数据源实际情况调整但思路可以直接搬。1. 先搞清楚“站点数据”到底长什么样1.1 三类数据形态与它们各自的脾气在动手写下载脚本之前我建议先花半小时把拿到的样本文件翻一遍。气象观测站点数据最常见的形态有三类处理方式差别很大。第一类是长表结构一行一条观测记录字段大致是站号、观测时间、要素编码、观测值、质量控制码。这种结构的好处是扩展性好加要素不用改表头坏处是你拿到手的第一反应往往是“这怎么用”必须做一次透视pivot才能变成时间序列。很多开放接口默认返回的就是这种格式因为服务端不用为每个要素单独设计表结构。处理长表的关键是搞清楚要素编码的含义通常数据源会附一份要素字典编码可能是数字也可能是字符串别自己猜。第二类是宽表结构一行一个时次列就是各个要素气温、气压、相对湿度、风速、风向、降水量各占一列。这种结构最贴近人的直觉导入表格软件就能看但要素一多列就上百而且不同数据源对同一要素的列名写法五花八门temp、TEM、气温、air_temperature都可能出现跨源合并时列名映射表必须提前准备好。第三类是嵌套或半结构化格式接口返回 JSON站点信息里嵌一个观测数组或者干脆是压缩包里面按站点分文件。这类数据不能直接喂给表格工具得先做扁平化展开。提示不要假设同一数据源的不同年份格式一致。我遇到过一次某数据源在某年升级了接口字段名从下划线风格改成了驼峰风格值从字符串变成了数字。脚本没做兼容跑了一晚上产出的表里一半是空列。1.2 选数据源时我优先看哪几个指标数据源的选择不是“哪个免费用哪个”我这里有一套固定的评估顺序实际用下来能省很多返工。评估维度具体看什么为什么重要时间覆盖起始年份、是否有断档、更新频率决定你能不能做长期趋势分析空间覆盖站点数量、分布密度、是否有搬迁记录站点搬迁会造成序列不连续必须知道要素完整度要素清单、逐时还是逐日、是否有质控码质控码是判断数据可信度的关键访问方式接口、批量文件、是否需要申请权限决定工程复杂度稳定性历史文件是否会被追溯修改影响结果可复现性其中我最看重的是站点搬迁记录和质控码这两项很多人会忽略。站点位置挪动几百米周边下垫面就变了前后序列不能直接当一条连续序列用没有质控码的数据你根本分不清某个 60℃ 是真实极端值还是传感器故障。注意如果数据源没有提供质控码你必须自己在清洗阶段补一套启发式规则否则异常值会直接污染后续的统计和建模。2. 下载环节的设计把“能下”变成“下得稳”2.1 接口鉴权与限流的基本套路下载这一步的核心矛盾是你想快服务端想稳。硬刚的结果通常是被限流甚至封掉访问权限然后你花更多时间排查。我的做法是主动给自己上枷锁。鉴权部分密钥一律放在请求头里不要拼在 URL 查询参数上。原因很实际URL 会进服务端访问日志、会进浏览器历史、会进你本地的调试打印任何一个环节泄露都麻烦。常见的写法是请求头里放一个X-API-Key之类的字段具体字段名以数据源文档为准。限流部分我一般按下面这套参数起步然后根据实际响应调整import time import random import requests SESSION requests.Session() SESSION.headers.update({X-API-Key: API_KEY, User-Agent: station-fetch/1.0}) def fetch_with_retry(url, params, max_retry5, timeout30): backoff 1.0 for attempt in range(max_retry): try: resp SESSION.get(url, paramsparams, timeouttimeout) if resp.status_code 200: return resp # 429 限流 和 5xx 服务端错误值得重试 if resp.status_code in (429, 500, 502, 503, 504): wait backoff * (1 random.random() * 0.3) # 加抖动避免齐步重试 time.sleep(wait) backoff * 2 continue # 4xx 参数错误重试没意义直接抛出 resp.raise_for_status() except requests.Timeout: time.sleep(backoff) backoff * 2 raise RuntimeError(f重试耗尽: {url})几个参数背后的考虑并发数我一般控制在 3 到 5 之间超过这个数很多服务端就会开始返回 429重试间隔从 1 秒开始翻倍最长不超过 30 秒加随机抖动是为了让多个线程的请求错开避免同时打过去形成新的峰值。这些都是常见实践具体上限要看你用的数据源文档里有没有写明配额。2.2 批量任务的组织方式清单驱动比循环遍历靠谱新手最常见的写法是两层 for 循环站点一层、时间一层边循环边下载边处理。跑小样本没问题一旦任务量上去中途断一次你就得全部重来而且完全不知道哪些下过、哪些没下。我的做法是清单驱动第一步先把所有待下载任务展开成一张清单表落盘成 CSV 或数据库表第二步让下载器只负责从清单里取任务、执行、回写状态。清单的字段我一般设计成这样task_id | source | station_id | start_date | end_date | element_set | request_url | expect_rows | local_path | sha256 | status | retry_count | fetched_at这张表的价值在于状态是外置的进程崩溃不会丢每个任务都有唯一的task_id天然幂等重复执行不会产生重复文件expect_rows记录预期记录数下载完立刻能对账。清单生成的时候有个细节容易出错——时间分片。有些接口对单次请求的时间跨度有限制比如一次最多返回一年数据。这时候按年切片切片边界要小心如果接口的时间参数是左闭右开年末最后一天就会漏掉。我一般统一用左闭右开切片写成[当年1月1日, 次年1月1日)拼接时也不会重复。2.3 断点续传与本地缓存的三层校验断点续传分两种情况。如果是接口返回流式文件可以用 HTTP 的Range请求头先看本地已经有.part文件多少字节从那个位置继续请求服务端返回 206 就说明支持。如果接口不支持 Range那就退化成“任务级续传”以task_id为单位失败就整个任务重来靠清单里的status字段跳过已完成的。比续传更重要的是校验。我习惯做三层缺一层都可能把坏数据放进下游第一层是传输层校验比对Content-Length和实际写入字节数不一致就判定失败。有些服务端在出错时会返回一个 200 状态码加一段错误提示的 HTML这层能拦住一部分。第二层是内容指纹对文件算 SHA256 写回清单。同一任务重复下载时比对指纹一致就跳过。这层的副作用是能发现“数据源追溯修改了历史文件”这种情况虽然少见但一旦发生你至少知道是哪个时间点变的。第三层是语义校验最容易被忽略也最有用。文件大小对得上不代表内容对我一般会快速解析一遍检查三件事表头字段是否齐全、解析出的记录数和expect_rows是否在一个合理偏差内、时间列的起止是否落在请求区间内。第三项经常抓到问题——服务端有时会忽略你传的时间参数直接返回全量数据光看大小完全看不出来。3. 数据解析编码、缺失值与质控码3.1 编码与分隔符的坑文本类气象数据在国内场景下编码问题几乎是必踩。常见的是 GBK 或 GB18030用默认的 UTF-8 读会直接抛异常或者更糟——某些字节恰好能解码成乱码字符不报错但内容全错。我的固定写法是def read_station_file(path): # 先探测 BOM再按候选编码依次尝试 for enc in (utf-8-sig, gb18030, utf-8): try: with open(path, r, encodingenc, newline) as f: head f.read(4096) if 站号 in head or station in head.lower(): return head, enc except UnicodeDecodeError: continue raise ValueError(f编码无法识别: {path})优先试utf-8-sig是因为带 BOM 的文件用普通utf-8读第一个列名前面会多出一个不可见字符导致按列名取数据时莫名 KeyError这个错我排查过两次才反应过来。分隔符的问题更隐蔽。有些数据源导出的文件表头用逗号、数据行用制表符或者反过来。还有一种情况是数值里带千分位逗号1,234被当成分隔符切开行字段数就多了。我的处理是先按行统计字段数取众数作为真实列数字段数异常的行走单独的错误清单人工看一眼再决定。3.2 缺失值的三种写法与统一处理气象数据里的缺失值至少有三种面孔必须全部识别出来再统一替换成NaN否则它们会以数字身份参与计算哨兵值型用999999、9999、-999这类极大或极小值表示缺测这是最经典的做法。符号型////、****、M、-、空字符串表示该时次无观测或记录缺失。物理不可能型比如相对湿度填了-1气压填了0这类严格说不是缺失标记但处理方式一样。还有一个特殊值必须单独说微量降水。有些数据源用T或trace表示“有降水但小于记录精度”这不是缺失直接当缺失会把降水日数算少。我的做法是把微量统一按0.05 mm半个记录精度处理并在旁边留一列标记方便后续按需回退。统一替换的代码本身简单重点是替换要发生在类型转换之后。如果先转数值////会直接变成异常如果先替换再转换999999这种在字符串状态下的匹配要注意前后空格。我一般先strip()再做映射。3.3 质量控制码怎么用如果数据源自带质控码恭喜你这比任何事后检测都靠谱。常见编码是 0 表示正确、1 表示可疑、2 表示错误、9 表示缺测不同数据源的具体定义有差异一定要查文档。使用策略我分三档质控码为 0 的正常使用为 1 的保留但加标记做统计分析时可以选择剔除或者保留取决于你对精度的要求为 2 和 9 的一律置为缺失不要试图拯救。我见过有人把“可疑”数据也当正常值用结果月度极值统计里冒出一堆离谱的数回头查才发现质控码早就提示了。提示质控码这一列千万别在合并、透视的过程中丢掉。我习惯在最终宽表里给每个要素配一列对应的质控标记名字用qc_要素名宁可表宽一点也别丢了这个信息。4. 清洗与加工从原始记录到可用序列4.1 单位换算与时区统一原始数据里存整数的做法非常普遍为了省存储空间观测值往往乘了 10 或 100 再存。典型的情况气温按 0.1℃ 存235表示 23.5℃气压按 0.1 hPa 存降水量按 0.1 mm 存风速按 0.1 m/s 存。如果忘了除画出来的温度曲线会是几十万度一眼能看出来还好可怕的是某些要素数量级接近比如能见度按米存而你以为是千米数据看着“正常”结论全错。我的做法是维护一张换算表从数据源文档里抄写死在配置里不要凭记忆要素原始单位存储放大倍数目标单位换算气温0.1℃10℃÷10气压0.1 hPa10hPa÷10相对湿度1%1%不变风速0.1 m/s10m/s÷10降水量0.1 mm10mm÷10能见度1 m1km÷1000时区是另一个高频雷区。国内很多站点数据用的是北京时间而国际数据集的常见约定是 UTC。两者差 8 小时直接混用会导致日平均、日最高最低温整体偏移尤其是日界线的处理。比如北京时间凌晨 2 点的观测换算成 UTC 是前一天 18 点如果你用它算“日最高温”结果会落在错误的日期上。我的统一策略是入库一律存 UTC另存一列本地时间用于展示。所有聚合计算先按 UTC 分组需要按本地日统计时先用本地时间列做日期分组再聚合。这个规则一次定死后面所有环节都遵守能避免 90% 的时区问题。顺带提一句露点。很多数据源只给气温和相对湿度不给露点。露点可以用 Magnus 公式算a17.27, b237.7先算中间量γ (a*T)/(bT) ln(RH/100)再算Td (b*γ)/(a-γ)。注意RH要转成 0 到 1 之间的小数T必须是摄氏度单位搞错算出来的露点会明显偏高或偏低。4.2 异常值识别物理阈值加统计方法双保险清洗异常值我用的是一套双层方案先物理后统计。第一层是物理阈值过滤这个最硬也最有效超出物理可能范围的值必须干掉。我常用的阈值范围是这样的要素合理范围说明气温-60 至 60℃极地到极端高温的边界气压500 至 1100 hPa站点海拔高时下限要上调相对湿度0 至 100%超过 100 一律异常风速0 至 75 m/s超过记录极值要重点核查小时降水0 至 300 mm超过要核查是否为累积值错位第二层是统计方法用滑动窗口算中位数和 MAD绝对中位差偏离超过若干倍 MAD 的标记为可疑。这里我用 MAD 而不是标准差因为标准差本身会被异常值拉偏一个 200℃ 的错值就能让整段数据都“看起来正常”。MAD 的鲁棒性更好工程上实现也就多两行。还有一类异常统计方法抓不到必须靠时序逻辑相邻时次跳变过大比如一小时气温涨了 15℃以及长期恒定值连续 12 小时所有要素完全相同。后者在自动化站点传感器卡死时非常常见数值本身完全不越界但它显然不是真实观测。4.3 缺失值插补的取舍插补这件事我的态度是能少插就少插。原因很实在插补出来的数据会放大后续统计的置信度让模型以为样本很完整。我的分级策略缺口不超过 3 个连续时次的用线性插值够用且不会引入奇怪形状缺口在 3 到 24 小时之间的优先用邻近站回归或者同期气候态但必须打标记超过 24 小时的缺口不插补保留缺失在日值统计时按有效样本数判断该日是否给出结果。比如日平均气温要求当日至少 20 个有效时次不够就置空而不是用残缺样本硬算。所有被插补的位置我都会在宽表里加一列imputed_flag值为 1 表示该值来自插补。后续做数据质量报告时可以直接算出一段时间的插补比例超过 5% 就要回头查数据源是不是出了问题。5. 存储与复现让处理过程可追溯5.1 分层存储设计数据治理里有个概念叫分层我觉得放在个人项目里同样适用因为它解决的是“我三个月后还认不认得这份数据”的问题。我的目录结构大致是三层第一层raw 原始层按数据源、年份、月份、站号分目录文件只读绝不修改。所有清洗和加工都读这一层写到别的层去。好处是任何时候发现问题都能回到原始数据重新跑一遍而不用重新下载。第二层staging 清洗层把多源数据解析、统一列名、统一单位、统一缺失值表示之后落成 Parquet 格式按年份分区。Parquet 的好处是列式存储压缩比高读取时只取需要的列一个几千万行的表也能秒级响应。分区字段选年份而不是月份是因为我的查询绝大多数是按站点加年份过滤的。第三层mart 应用层面向具体用途的宽表比如逐时全要素宽表、日值汇总表、月值统计表。这一层是给别人用的字段名要人话单位要标准缺失值要有明确约定。5.2 用元数据清单保证可复现光有数据文件还不够还要有一张“这批数据是怎么来的”的说明。我一般维护两个文件一个是manifest.json记录每一步的参数配置包括数据源版本、下载时间范围、阈值设定、插补策略另一个是run_log记录每次运行的时间戳、输入文件指纹、输出行数。为什么要做到这个程度因为我吃过亏。半年前做的一份统计结果被质疑回头想复现发现当时用的阈值是手工改过的代码里现在写的是新值怎么都跑不出当年的数。从那以后所有阈值类参数一律进配置文件代码只读配置不写死。注意站点元数据要单独存一份包括站号、站名、经纬度、海拔、建站撤站时间、搬迁记录。这张表看着不起眼但在做插值时海拔是必需的协变量在解释异常值时搬迁记录经常是答案。6. 常见问题与排查实录6.1 典型故障速查表下面这张表是我这些年遇到最多的几类问题按现象归档遇到时可以直接对照。现象大概率原因排查动作处置方式下载成功但文件只有几百字节服务端返回了错误提示页打开文件看前 200 字符加状态码加内容双重判断解析报编码错误文件是 GBK 或含 BOM看文件头字节按候选编码依次尝试列名取不到KeyErrorBOM 或前后空格打印repr(列名)统一 strip 并去 BOM数值列全部变成字符串列里混了////等符号看去重值先清洗再转类型温度曲线整体偏高十几度没做 0.1℃ 还原看取值范围补换算日最高温出现在错误日期时区混用核对原始时间列格式统一存 UTC某站点连续多日数值完全一致传感器卡死或数据被填充算连续相同值长度标记为无效并置缺失同一任务两次下载结果不同数据源追溯更新比对 SHA256记录版本并固定使用某一版月度统计值缺失偏多有效样本数阈值过严统计每日有效时次数分布调整阈值或说明原因6.2 几个踩过的坑与实战心得第一个坑是分页漏数据。某接口用page参数分页我按固定页大小循环结果服务端在某些条件下实际返回条数少于页大小我的循环条件写的是“返回条数等于页大小才继续”于是提前终止静默丢了一部分数据。后来改成判断返回条数是否为零同时用总数做最终对账才算稳。教训是分页终止条件不要依赖“刚好满页”这种脆弱假设。第二个坑是多源站号不一致。同在一个区域的站点在 A 数据源里站号是 5 位数字在 B 数据源里是 6 位前面补了个 0。合并时对不上看起来像“两个源没有重叠站点”。解决办法是建一张站点映射表用经纬度距离做模糊匹配距离小于 1 公里且海拔接近的判为同一站人工复核一遍再固化下来。第三个心得是先跑小样本再全量。我现在的固定流程是任新数据源先挑 3 个站、1 个月的数据完整走一遍全链路把解析、清洗、统计、出图都跑通确认没有结构性错误再放开全量。这一步能省掉大量“跑了一晚上发现列名全错”的时间。第四个心得是给数据质量出一份报告。每次处理完我会自动生成一份简单统计各站点记录数、缺失率、质控码分布、插补比例、异常值数量。这份报告不用多精美一张表就够但它是我判断“这批数据能不能用”的第一道关口。看多了会发现数据源出问题时这份报告的异常会非常明显——某个站的缺失率突然从 2% 跳到 40%那基本就是数据源那边动了。最后说一个容易被忽略的细节风向的特殊编码。有些数据源把风向和风速合成一个数存比如 6 位数字前 3 位是风向、后 3 位是风速或者用特定值表示静风、不定风。这类编码如果不拆直接当普通数值用算出来的平均风向毫无意义。风向本身也不能简单算术平均0 度和 360 度是同一个方向得转成矢量分量正弦余弦再平均最后反算角度。这个坑我在做风玫瑰图的时候踩过一次图上出现了一个完全不存在的“北偏西高频方向”排查半天才发现是数值平均的问题。我个人在实际操作中的体会是气象站点数据处理这件事技术难度其实不高难的是细节的完备性和流程的可追溯性。真正拉开差距的不是你会不会用某个库而是你有没有在每一个环节都留一手下载留指纹、清洗留标记、加工留配置、输出留报告。这一套做下来前期多花两天后面每次数据被质疑、被追问、需要重跑的时候都能省下好几倍的时间。
延伸阅读

更多相关文章

2026/10/26 21:51:58

HyperFrames:面向AI代理的确定性视频渲染框架解析

HyperFrames这个名字第一次出现在我面前的时候,我正在折腾一个很头疼的问题:让AI代理反复执行同一个策略,它每次看到的画面都不一样。不是那种细微的色调差,而是渲染出来的物体位置、光照角度、画面时序全都变了。最直接的影响是强…

2026/10/26 21:51:58

2026后端进化:从CRUD工程师到AI原生架构师

1. 为什么2026年不是“后端消亡元年”,而是架构师的黄金分水岭我去年带团队重构一个日均300万订单的电商履约系统时,发现一个反直觉现象:越是把CRUD写得滴水不漏的工程师,越难在新需求里活过两周。不是代码质量差——他们用Spring…

2026/10/26 21:46:57

LSTM+Transformer混合模型实现时间序列预测

简介:本资源是一套基于PyTorch实现的LSTM与Transformer融合架构的时间序列预测完整方案,面向机器学习与深度学习初学者及工程实践者,适用于风电功率、光伏发电、设备剩余寿命、环境浓度等多场景单变量预测任务。压缩包共132个文件&#xff0c…

2026/10/26 22:57:18

e2e测试从选型到落地:搭建稳定可复现的端到端测试体系

做前端开发和测试的同学,对“e2e”这三个字母肯定不陌生,尤其是这两年,它在技术社区和招聘要求里出现的频率越来越高。但说实话,很多人对它的理解还停留在“模拟用户点点点”的层面,真正上手搭过一套稳定、可复现、能进…

2026/10/26 22:57:18

e2e测试:端到端质量保障的核心实践与工程落地

1. “e2e”不是缩写游戏,而是软件质量防线的最后一道闸门你打开一个网页,点登录按钮,输账号密码,跳转到首页——这个过程在你眼里是“顺手一刷”,但在开发团队眼里,它是一条由几十个接口调用、状态变更、DO…

2026/10/26 22:57:18

ComfyUI与LoRA技术:AI图像生成的高效工作流

1. ComfyUI与LoRA技术概述ComfyUI作为一款基于节点式工作流的AI图像生成工具,正在成为专业创作者的新宠。其可视化编程界面让复杂的工作流搭建变得直观可控,而LoRA(Low-Rank Adaptation)技术则是当前AI绘图领域实现模型微调的最高…

2026/10/26 22:57:18

seedance2:本地化短剧生成工作流引擎详解

简介:这是一套开源的本地化AI短剧与漫剧生成工具seedance2接入方案,面向影视创作者、AI应用开发者及短剧内容生产者,解决传统短剧制作流程长、依赖云端服务、数据隐私难保障等痛点。资源包共280个文件,含106个JavaScript核心逻辑文…

2026/10/26 22:57:18

多任务学习中的任务冲突解析与实战解决方案

1. 多任务学习中的任务冲突现象解析第一次接触多任务学习(MTL)时,我像发现新大陆一样兴奋——用一个模型同时解决多个任务,这简直是计算资源的完美利用。但现实很快给我上了一课:当我在图像识别项目中同时训练分类和分…

2026/10/26 22:52:15

AI智能体记忆管理:ReMe工具包架构与应用解析

1. ReMe项目概述:AI智能体的记忆管理革命在AI智能体开发领域,记忆管理一直是制约系统性能的关键瓶颈。传统智能体往往采用单一记忆存储模式,导致在处理复杂任务时出现信息检索效率低下、上下文关联断裂等问题。ReMe工具包的诞生,正…

2026/10/26 0:20:20

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/26 1:53:47

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/26 0:20:17

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/26 0:00:48

Nested Learning:解决AI灾难性遗忘的分层记忆架构

1. 为什么AI需要"记忆"能力?在传统深度学习模型中,我们常遇到一个根本性问题:模型在处理新任务时,往往会完全遗忘之前学到的知识。这种现象被业界称为"灾难性遗忘"(Catastrophic Forgetting)。想象一下&#…

2026/10/26 0:00:48

企业级大模型应用:OCR预处理、Dify编排与DeepSeek落地实战

1. 这不是“用大模型”,而是重构工作流的起点“大模型的应用和工具”——这八个字现在被贴在无数技术分享、招聘JD、内部立项书的标题栏里,但绝大多数人点进去后看到的,是零散的API调用示例、几个开源UI截图、或者一句“接入LLM就能降本增效”…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑