NDBC 浮标站网 — 一千三百个点,测不出一张图

1,352 个活动站、17,049 个年归档、两套缺测写法

来源 NOAA National Data Buoy Center(NDBC) url: https://www.ndbc.noaa.gov/发布时间: 2026-10-29最后更新: 2026-10-29 阅读 8
NDBC 浮标站网 — 一千三百个点,测不出一张图

信息速览

数据集名称NDBC 浮标站网 — 一千三百个点,测不出一张图
数据类型1970 至今,1,352 个活动站,17,049 个年归档文件,定宽文本,MM 缺失标记,99.00 哨兵值,10 分钟采样,公有领域
规模不适用(海上原位观测站网,无个体记录;观测单位为一次站点观测,空间单位为站,时间单位为时刻)
接入方式NOAA National Data Buoy Center(NDBC) url: https://www.ndbc.noaa.gov/
AI 就绪度

NDBC 是海洋机器学习里最容易被当成「配角」的一份数据:它不生产图,只生产点。卫星产品给你整片海的温度,浮标只告诉你它脚下那一小块水面此刻几度、风多大、浪多高。

也正因为如此,它几乎是所有格点产品的校验源。想判断一颗卫星的反演有没有偏、想给再分析场找一个不参与同化的独立参照、想确认某个海湾的养殖水温到底是多少,最后都要落到这些点上。

这份条目只讲 标准气象数据(stdmet)这一路接口:站表、实时目录、归档目录。不展开波浪谱密度、连续风、声学多普勒流剖面这几条平行通道,也不涉及 NetCDF/THREDDS 镜像。

项目 数值 来源
官方全名 NOAA National Data Buoy Center observations(Standard Meteorological Data) 官方度量说明页
运营方 NOAA 国家气象局 / 国家数据浮标中心 站页页脚
活动站数 1,352(2026-10-29T10:45:03UTC) 站表 count 属性
站型构成 固定 710 / 浮标 438 / DART 76 / 钻油 54 / TAO 48 / 其它 24 / USV 2 站表 type 属性统计
气象在测站 863(met="y") 站表属性统计
站表体积 272,488 字节 HTTP Content-Length
归档站点 1,350 站、17,049 个年文件 归档目录索引计数
归档年份 1970–2025 文件名年份解析
主记录列数 18 列定宽文本 文件表头
时间步长 整点逐小时;部分站 10 分钟 文件间隔
实时窗口 约 45 天 官方度量说明页
缺测写法 实时 MM;归档 99.00/999 官方度量说明页
访问门槛 无注册,无令牌 目录可直接列出
许可 公有领域(联邦作品),无 SPDX 无独立许可页

§1 速览:它是点,不是面

§1.1 一句话定义

NDBC 是一张由一千多个海上观测点组成的网:每个点固定在一个有名字的坐标附近,按小时(少数按十分钟)报一次风、气压、气温、水温、浪高,从 1970 年代起陆续开张,一直报到今天。

它没有网格,没有插值,没有「每一格都有值」这件事。它给你的是一行一行的时间戳,每一行背后是一次真实发生的测量,或者一次明确的失败。

§1.2 为什么它不是「又一份海洋数据」

第一层原因,它不可被插值出来。格点产品的价值在于覆盖,浮标的价值在于它不是算出来的。当你想知道某个模型在开阔洋面的偏差有多大,能拿来当尺子的只有不参与建模的独立观测——而开放海域的独立观测,基本上就是这些浮标。

第二层原因,它的缺失是一种信息。卫星产品缺值意味着「今天云太厚」,浮标缺值意味着「这个传感器坏了」或「这个站根本没装这个传感器」或「这个站上月被回收了」。三种情况在文件里长得一模一样,要区分只能去站页看载荷与维护记录。这一点是格点产品里没有的维度。

第三层原因,它的历史不是一条连续的线,而是一串彼此不重叠的线。每个站有自己的起止,站与站之间不复用坐标,退役的站还在归档里,新开的站不在旧文件里。想拼一条四十年的序列,得先在四百多个已退役的站里找到那个一直活着的。

§1.3 三十秒决策卡:你的处境,它能帮你什么

你的处境 它能帮你什么
要验证海温格点产品准不准 逐站的现场水温,独立于卫星反演与再分析同化
要训练一个海浪预报或补缺模型 有效波高、主波周期、平均周期三联,逐小时,含大量失败样本
要做飓风、风暴潮的路经与强度复盘 风暴中心擦过的浮标给出不中断的高频气压与风速序列
要一份「某片海到底多冷」的判决依据 可以,但要先确认那个站今天还在,且当年装了水温传感器
要一张全球覆盖的海温图 给不了,这是点集,太平洋腹地、南大洋、印度洋近乎空白
要等间隔、无洞、可直接喂进时序模型的整齐表 给不了,每个站的列构成与时间步长都不同

§1.4 不适合谁

如果需求是空间连续场,这份数据会让人做无用功。全部站里,七百多个固定在北美沿岸、五大湖与内河河口,只有半数浮标在开阔洋面。把它们的经纬度画在地图上,看到的是一条沿美国海岸与北大西洋航线的带状分布,中间大片留白。用它做「全球平均海表温度」,得到的是「北美沿海浮标平均水温」。

如果需求是开箱即用的规整表,同样不合适。官方给的是定宽文本,不是关系表。同一列在不同年代的列名不同(WD 后来叫 WDIR,BAR 后来叫 PRES,H0 后来叫 WVHT),同一年份不同站的时间步长不同(有的整点,有的每十分钟),同一张表头下各站有效列不同。把它读进来这一步,本身就是大部分工作量。

如果需求是短期业务预报,实时流能用但不该盲信。实时流只经过 NWSTG 侧的自动量程与连续性检查,归档流还要过 NDBC 侧更严的一套限值检查和人工复核。同一天的同一时刻,两条流给的值可能不一样。

如果需求是已退役站的历史,必须绕开站表。站表只列当前活动站;2026 年 10 月,一份曾经长期在报的东海岸浮标已被回收,但它仍留在站表里,页面上写着回收日期,实时文件返回 404。这类边界情况在站表里看不出来。

1.5 贯穿本文的案例:一枚已经回收、却还在站表里的浮标

后面几节会反复回到同一个站:41001,代号「EAST HATTERAS」,在北纬 34.738、西经 72.503,距哈特拉斯角以东约 150 海里,脚下水深 4,462 米。

它的站页写着三件互相矛盾的事。第一,它在今天的活动站表里,type="buoy",坐标齐全。第二,它的实时数据接口返回 404,最近二十四小时、五天、四十五天全部写着「无数据」。第三,页面正文里有一句「Buoy recovered 9/9/26」——2026 年 9 月 9 日,浮标被回收了。

这三件事同时为真,正好构成理解这份数据的入口。站表是「名录」,不是「心跳」;实时流看淡的是「还在不在」,归档流看的是「曾经报过什么」;而一个站的生命周期,只有把站表、实时目录、归档目录、站页四份材料叠起来才看得全。

§3.3 会用它讲站号与坐标,§4.2 会用它讲回收与缺测的关系,§5.4 会用它讲取数时的探测手法,§7.3 会用它讲「看得见但取不到」这一类边界,§8.4 会用它讲为什么这份数据必须靠「站」而不是「数据集」来维护。

§2 数据构成:十八列,两套哨兵,三种目录

§2.1 规模:哪些是官方口径,哪些是数出来的

官方从不公布总站数、总体积这类数字——它每天在变,写死就会过期。站表自己在 count 属性里给了这一刻的数。

维度 数值 怎么得到的 性质
活动站 1,352 站表 count 属性与 station 元素计数一致 官方
其中固定站 710 type="fixed" 属性统计
其中浮标 438 type="buoy" 属性统计
其中海啸 DART 76 type="dart" 属性统计
其中钻油平台 54 type="oilrig" 属性统计
其中 TAO 锚系 48 type="tao" 属性统计
其中其它与 USV 26 type="other" 24 个、type="usv" 2 个 属性统计
气象在测 863 met="y" 属性统计
海流在测 少数 currents="y" 另计,与 stdmet 无关 属性统计
归档站点 1,350 归档目录文件名去重 目录
归档年文件 17,049 归档目录计数 目录
归档中最老的年份 1970 文件名年份解析,全目录仅 1 个该年文件 目录
归档中 2024 年文件 784 按年份分桶 目录

三个数字值得单独说。

一个是站型与「是不是浮标」不是一回事。438 个 buoy 只是系泊浮标;710 个固定站里九百公里外的灯塔、河口验潮站、大湖边的码头上都算,它们测的是同一个 18 列气象量,只是仪器高度与代表性完全不同。一个把「浮标」当筛选条件的脚本,会漏掉 863 个在测站里的 554 个。

另一个是归档站数略少于活动站数。1,350 对 1,352,看着像是几乎相同,实际上交集只有 908 个:有 440 个站在归档里有记录、在今天的站表里没有;反过来,有四百多个站在站表里有、在归档目录里一个文件也没有。两组数字接近纯属巧合,不能拿总数去想象「这批站就是那批站」。

第三个是年文件不是全覆盖。这些文件分给全部一千三百多个归档站,平均 12.6 个,中位数只有 4 个——大量站只报过不到五年就被拆了。活过二十年的站大约一百个。这与「NDBC 有五十多年历史」这句宣传不矛盾:历史属于机构,不属于任何一个站。

§2.2 一个文件里到底有什么

stdmet 文件是纯文本,两行以 # 开头的表头(第一行是列名,第二行是单位),下面是数据行。当前格式 18 列。

列 单位 含义 常见状态
YY 年 四位(早年两位) 总有
MM 月 1–12 总有
DD 日 1–31 总有
hh 时 UTC 总有
mm 分 当年格式有,早年无 总有
WDIR 度 风来向,真北 0 顺时针 多数有
WSPD 米每秒 8 分钟平均(岸站 2 分钟) 多数有
GST 米每秒 阵风,5 或 8 秒峰值 多数有
WVHT 米 有效波高 只有带波浪计的站有
DPD 秒 主波周期 随 WVHT
APD 秒 平均波周期 随 WVHT
MWD 度 主波向 随 WVHT
PRES 百帕 海面气压 多数有,早年少
ATMP 摄氏度 气温 多数有
WTMP 摄氏度 海表温度 多数有
DEWP 摄氏度 露点 多数有
VIS 海里 能见度 浮标站限 0–1.6,常年缺
PTDY 百帕 三小时气压倾向 归档文件中常缺
TIDE 英尺 潮位,相对 MLLW 极少数站有

表里最后一列「常见状态」是你最该先看的一列。十八列摆在那里,会让人以为每个站都测十八样东西,实际上一个典型的系泊浮标只报风和气压、气温水温,波浪要看有没有装波浪计,能见度与潮位基本是别的设备的事。

§2.3 缺测不是空,是 MM,或者一串 9

这是全篇最需要记住的一条,官方文档把它写在度量说明页的第二段:

Missing data in the Realtime files are denoted by “MM” while a variable number of 9’s are used to denote missing data in the Historical files, depending on the data type (for example: 999.0 99.0).

两个字母 MM,两句之隔就是月份列 MM。同一个文件里,第 2 列叫 MM(月份),数据区里却也出现 MM(缺测),位置不同、含义无关。用空格切分后按列号取值不会出错,因为列号是固定的;但用正则去替换「所有 MM」就会连月份一起换掉。

年份分用 9 也不统一。2026 年 9 月的一份归档文件里,同一行会同时出现 99.00(波高、波周期缺)与 999(波向缺)与 99.0(能见度缺)与 999.0(露点缺)与 99.00(潮位缺)。五种写法,五个不同的小数位数,取决于这一列的物理量纲与历史上被写死进格式的位数。

这就是为什么直接用 float() 读归档文件会出一场静默的灾难:99.00 能顺利解析成 99.0,程序不报错,于是「浪高 99 米」与「气温 99 摄氏度」顺利进入统计。实时文件里的 MM 反而安全,因为 float("MM") 会当场抛异常。

位置 缺测写法 直接 float 的后果
实时文件 MM 抛 ValueError,当场暴露
归档文件(当年月包) 99.00 / 999 / 99.0 / 999.0 解析成功,静默变成极大值
归档文件(早年年度文件) 99.00 / 999 / 99.0 / 999.0 同上
表头内 MM(月份列名) 若整行参与解析会把表头当数据

有一个更隐蔽的坑:归档文件的缺测不是「这一列没测」,而是「这一列的哨兵值」。同一列在不同站上哨兵长度不同——波高写 99.00,但早年有的站写 99.0;气压缺测写 999.0,而真实气压 999.0 百帕是合法值(约等于 1999 年那次东太平洋飓风时的中心气压量级)。所以判缺测不能只比较数值大小,得按列宽取、按列定义判。

§2.4 一条观测记录的逐项解剖

取 2026 年 10 月 29 日 10:00 UTC、浮标 42001(墨西哥湾中部)的一行实时记录:

层面 内容 要注意什么
原始行 2026 10 29 10 00 40 8.0 10.0 MM MM MM MM 1016.0 25.8 27.0 22.0 MM -1.0 MM 空格分隔,非严格定宽
站 42001,MID GULF,北纬 25.922 西经 89.638 坐标来自站表,不是这一行
时刻 2026-10-29T10:00Z UTC,无时区后缀
风 40 度、8.0 米每秒、阵风 10.0 来向 40 度即东北风
浪 全部 MM 这一刻没有波高,不等于这个站没有波浪计
气压 1016.0 百帕 合法
气温 25.8 摄氏度 合法
水温 27.0 摄氏度 比气温高 1.2 度,正常
露点 22.0 摄氏度 与气温差 3.8 度,相对湿度约 79%
能见度 MM 浮标站几乎永远这样
气压倾向 -1.0 百帕 三小时下降
潮位 MM 墨西哥湾浮标没有潮位测量

这一行里最容易被当成「数据坏了」的是连续四个 MM(波浪四项)。把它理解成「这个站坏了」会误导。同一份文件往前翻十分钟,WVHT 是 2.0;再往前十分钟,DPD 又是 MM。波浪观测的有效率天然低于风与气压——这一点在 §4.4 会展开。

§2.5 空间与时间的边界

时间只有 UTC,没有本地时。 官方度量说明页写明:站页默认显示站点本地时,实时文件与归档文件一律只给 UTC。跨越日界线的站在本地日历上会「提前一天」或「落后一天」,做日聚合时必须自己决定按哪个日历。

早年没有分钟列。 1985 年的归档文件表头是 YY MM DD hh WD WSPD GST WVHT DPD APD MWD BAR ATMP WTMP DEWP VIS,只有四列时间,步长本身就是一小时。1990 年代初开始出现分钟列,但早期仍按整点报。把两个年代的记录拼成时序,时间索引的分辨率会跳变。

坐标是名义坐标,不是逐条坐标。 站表给一个 lat 与 lon,通常三位小数,约合百米级;站页另给 watch circle radius,41001 是 4,638 码(约 4.2 公里)、42001 是 3,032 码(约 2.8 公里)。这个半径表示系泊浮标在锚点周围的正常摆动范围。想要逐条位置,只能看漂流平台格式——那种格式把 LAT 与 LON 写成两列,逐条给出。

站号不是绝对主键。 官方说明写明:五位 WMO 站号自 1976 年起使用,「ID’s can be reassigned to future deployments within the same 1 degree square」——同一个一度方格内,站号可以被后续布放的设备复用。所以「站 41012 的四十年序列」这个说法只在物理意义上勉强成立,它至少包含两次不同的浮标。

经度范围从 -179.797 到 180。 站表里既有西经,也有正值 180.0。这不是数据错,是东太平洋与太平洋岛屿站分别按各自习惯录入。做空间连接时必须先归一。

§3 定位层:站、站号与「这批站是哪批站」

§3.1 一个机构,七种站型

站表 type 字段只有七种取值,但它们背后的实体差别极大。

站型 数量 物理形态 测什么 经度分布
fixed 710 岸边码头、灯塔、河口验潮亭、大湖栈桥 风、气压、气温,少数加水温 北美沿岸与大湖高度密集
buoy 438 3 米或 10 米直径系泊浮标 全套气象加波浪 开阔洋面与五大湖
dart 76 海啸浮标,底压计加水面单元 主要报水位,气象多为空 环太平洋与印度洋
oilrig 54 墨西哥湾钻油平台 风、气压、气温 墨西哥湾
tao 48 赤道锚系 TAO/TRITON 阵列 风、气温、水温为主 赤道太平洋与印度洋
other 24 公私合作站点、Sofar Spotter 波浪浮子、Backyard Buoys 社区浮子 视合作方而定 分散
usv 2 无人水面艇 视载荷 旧金山外海

这张表说明一件常被忽略的事:NDBC 不是一个「浮标保有单位」,而是一个「观测汇集与分发单位」。站表里 234 个标 owner="NDBC",其余一千一百多个属于 IOOS 区域协会、州政府、大学、国际气象机构,甚至私人石油公司。NDBC 做的是把它们的数据统一成同一套 18 列格式。

§3.2 站号里的地理密码

五位站号的首位数字大致对应区块,虽然官方从未把它写成正式规则。

首位 大致区域 站表内计数(首位匹配)
4 西大西洋、墨西哥湾、加勒比、北美五大湖 约 400
5 东太平洋、夏威夷、太平洋岛屿 约 90
6 欧洲与大西洋东部(英法爱国际伙伴站) 约 40
1 / 2 / 3 南大洋、印度洋、国际伙伴 TAO 与 Moored Array 站 约 80

这套编号只是「大致」,反例很多:42001 在墨西哥湾、51001 在夏威夷西北、64045 在苏格兰以西的 K5 锚系。真正可靠的产地信息在站表的 owner 与 pgm 两个字段里。

§3.3 站号、坐标与显示名三者的关系

41001 这一枚浮标的三个身份标识,分别对应三种不同的用途。

标识 值 谁在用 稳定性
站号 41001 文件名、URL、所有下游数据库的外键 在同一个一度方格内可被复用
坐标 34.738 N / 72.503 W 空间连接、地图符号 名义值,实际摆幅 ±4.2 公里
显示名 EAST HATTERAS - 150 NM East of Cape Hatteras 人读、搜索 会随布放位置调整而改写

三者的不一致是常态。站表里 41001 的 name 是「EAST HATTERAS - 150 NM East of Cape Hatteras」,站页标题写「LLNR 635」,owner 是 NDBC,pgm 是「NDBC Meteorological/Ocean」。LLNR 是本地编号,不进文件名;档号在这里只出现在站页正文,不进主键。

所以按站号查、按坐标查、按名字查,会得到三套不同的结果集。做数据引用时唯一该写进元数据的是站号加取数日期——名字会被改,坐标会被微调,站号会被复用,三者都有风险,但站号至少是可检索的。

§3.4 名字、文件名与唯一指认

归档文件的命名极不规则,这是很多人第一次接触时会踩的坑。

目录 文件命名 例子
历史年归档 <站号>h<年>.txt.gz 42001h2024.txt.gz
历史连续风 <站号>c<年>.txt.gz 42a01c2003.txt.gz
历史波浪谱密度 <站号>w<年>.txt.gz 45T01w2018.txt.gz
历史海流 <站号>o<年>.txt.gz 32st1o2009.txt.gz
历史流剖面 <站号>a<年>.txt.gz 41008a2005.txt.gz
当年已封闭月 <站号><月号><年>.txt.gz 4200152026.txt.gz(5 月)
当年当月 <站号>.txt 42001.txt,位于 /data/stdmet/Sep/

七种命名,五种字母后缀,两种大小写(45T01 里的大写 T 真实存在)。写通配符时用 * 比写 h 安全,但要注意 h 与 c、w、o、a 不会同时出现在同一个站号下——这四个是不同数据类型,彼此不覆盖。

§3.5 站表与归档目录是两份清单,不是一份

这是全篇的第二条边界,也是长期序列最容易出错的地方。

口径 站数 说明
活动站表(2026-10-29) 1,352 此刻在册的站,含已回收但未摘牌的
归档目录 historical/stdmet 1,350 有至少一个年文件的站
两者交集 908 既在册、又报过历史
只在归档 440 已退役,历史还在
只在站表 444 在册,但当前目录里没有年文件

「只在站表」的四百多个站,多数是当年新开或近两年才接入的:它们的记录还在实时流与当年月目录里,还没被折叠成年文件。也就是说,同一个站的数据可能同时存在于三个位置:实时文件(最近 45 天)、当年月目录(本年度已封闭的月份)、历史年归档(上一年及更早)。三处不重叠,拼一份完整序列必须三处都取。

「只在归档」的四百多个站,是这份数据真正的历史价值所在:它们大多布放在 1970 年代到 1990 年代,物理上早已沉没或被拖回,但记录还在。任何以站表为起点的脚本,都会在第一步就把这批站漏掉。

§4 质量层:它最容易被误用的四个地方

§4.1 两套缺测写法会同时出现在一个目录里

先说结论:同一份数据里,MM 与 99.00 不是先后关系,是目录关系。实时目录用前者,归档目录用后者,两者不会交叉,但使用者往往会在同一个脚本里同时处理这两类文件——因为完整的一条序列必然要横跨两头。

这种安排带来一个反直觉的后果:没有值比有值更容易被发现。实时文件里的 MM 会抛异常,归档文件里的 99.00 不会。于是最常出错的反而是「历史上那段看起来最完整的数据」。

判别办法只有一条:按列宽读,不按空格切。官方声明的格式是定宽文本,虽然实际上不同年代、不同年份的行宽会有 ±1 字符的出入(2025 年的 42001 年文件里同时存在 88 与 89 字符两种行宽),但列位置是稳定的。按固定字符位置切片,99.00 与 99.0 会各自落回它该去的列,也不会把 999 误读成 99 与 9。

#-- 按官方声明的列位切归档文件,而不是 split();缺测按列定义判
COLS = [(0, 4), (5, 7), (8, 10), (11, 13), (14, 16),
        (18, 21), (23, 27), (29, 33), (35, 40), (42, 47), (49, 54), (56, 59),
        (61, 67), (69, 74), (76, 81), (83, 88), (90, 93), (96, 102)]
NAMES = ["YY", "MM", "DD", "hh", "mm", "WDIR", "WSPD", "GST", "WVHT", "DPD",
         "APD", "MWD", "PRES", "ATMP", "WTMP", "DEWP", "VIS", "TIDE"]

line = "2025 01 01 00 00 310  4.4  5.4 99.00 99.00 99.00 999 1012.7  25.7  26.1 999.0 99.0 99.00 "
rec = {}
for (a, b), name in zip(COLS, NAMES):
    tok = line[a:b].strip()
    # 归档文件的缺测是一串 9:整列全 9 即缺测,没有其它合法值能长得像它
    rec[name] = None if tok and set(tok) <= {"9", ".", "+", "-"} else tok
print(rec["WVHT"], rec["WTMP"], rec["PRES"])
#-- 期望:None, '26.1', '1012.7'

这段代码的两个要点:set(tok) <= {"9", ".", "+", "-"} 判的是「整串只由 9 和小数点构成」,99.00、999、999.0 全部命中;1012.7 里有两个 1 一个 2,不命中。第二个要点是列位表——它按 §2.2 的表逐列对齐,不是猜的。换年份时要先看一眼表头,早年文件的列位完全不同。

§4.2 §1.5 的案例:回收日期比站表更可靠

回到 41001。今天它仍在站表里,met="n",坐标齐全,但实时接口返回 404。要判断它是否还在服役,靠的不是站表,是站页正文里的那一句「Buoy recovered 9/9/26」。

这个案例揭示了一个更大的问题:站表记录的是「在册」,不是「在测」。官方在首页图例里区分了「最近 8 小时有数据的站」与「只有历史数据的站」,但站表 XML 里没有这个状态字段。只有 met、currents、waterquality、dart 四个「这一类传感器有没有配」的标记,没有「今天还在不在报」。

于是判断一个站是否活着,得靠三种间接证据。

证据 怎么查 判读
实时文件是否 404 直接请求 realtime2/<id>.txt 404 表示没有任何最近数据
实时文件最后一行时间 读文件末行 距今天超过数小时即停报
站页维护提示 抓站页正文找「recovered」「operational」等字样 有明确回收日期即可判定
当年月目录是否有该站文件 请求 /data/stdmet/<月>/<id>.txt 有文件说明本月至少报过
归档最新年份 查归档目录里该站的最大年份 决定历史序列的右端

五种证据各有盲区:404 可能只是当日文件还没生成;末行时间可能只是当日尚未到整点;站页提示是散文,需要解析;月目录文件存在不代表内容非空;归档右端滞后于实时。没有一种能单独定论。

§1.5 的案例还说明另一件事:41001 在 2026 年 9 月 9 日被回收,但它 2026 年 5 月到 7 月的归档数据仍然可以通过站页列出的链接取到(月包形式),实时接口却已全部 404。「看得见、取不到」与「取得到、看不见」在这份数据里同时存在,判断口径必须分开。

§4.3 §1.5 的案例之后:哪些记录其实不属于这个站

41001 站页还有一行常被忽略:2026 年 5 月起,它的数据源换成了另一种载荷,页面写明「3-meter foam buoy SCOOP payload」。同一站号下,前后两段数据的仪器不同、采样方式不同、传感器高度不同。

这种「同名不同器」在这份数据里是常态,且有三种不同程度的表现。

程度 表现 影响
同一站号,换载荷 41001 换 SCOOP;46012 从 Saildrone 换成 Orchard USV 仪器高度、采样率、精度全变
同一站号,换物理实体 站号在同一度方格内被重新布放 四十年序列至少含两次不同浮标
同一坐标,换站号 旧站退役、新站顶替(名字里常写「Site of 46012」) 按坐标查会命中两个站

第二种最隐蔽。官方明说站号可复用,但没有公布一张「某站号在哪些年份对应哪个物理实体」的映射表。唯一能查的是站页下方的历史记录,而那是逐站手工维护的散文,进不了自动化管线。

所以「站 42001 四十年的海温序列」这句话,在物理意义上应该读作「站号 42001 所在位置附近,四十年来由若干台不同设备测得的表层温度」。做趋势分析时,这个差别至少要在方法一节里写清楚。

§4.4 列头固定,但常年全空的列不一样

十八列的表头在所有站上完全相同,这是这份数据最友好的地方,也是最容易误判的地方。

取四个站在 2026 年 10 月 29 日实时文件里的有效情况:

站 行数 WVHT 缺测率 VIS TIDE 另有常年全空
42001(墨西哥湾浮标) 6,476 50.6% 100% 100% PTDY 83.2%
51001(夏威夷西北浮标) 6,457 波浪时有时无 100% 100% 视时段
44013(波士顿外海浮标) 6,474 约一半 100% 100% 视时段
64045(苏格兰以西 K5) 25 100% 100% 100% 全部气象也多缺

同一列 42001 的波高缺一半、64045 的波高全缺。如果按「某一列缺测率超过九成就删掉这一列」的规则清洗,前三个站保住波高,64045 会连风一起被删——而 64045 这一刻本来就没有风。

更麻烦的是列名的历史漂移。官方在度量说明页末尾专门列了一张「已废弃缩写」表:WD 与 DIR 都改叫 WDIR,SPD 改叫 WSPD,GSP 改叫 GST,GMN 改叫 GTIME,BARO 改叫 PRES,H0 改叫 WVHT,DOMPD 改叫 DPD,AVP 改叫 APD,SRAD 与 SRAD2 改叫 SWRAD,LRAD 与 LRAD1 改叫 LWRAD。

一张 1985 年的文件表头写 WD 与 BAR,一张 2026 年的写 WDIR 与 PRES。按列名解析的代码,跨 1990 年代就会断。

§4.5 浮标会漂,坐标有三层含义

51001 的站表坐标是北纬 24.475、西经 162.03。站页给的观察圈半径没有直接写在这份站表里,但同类站可以参照:41001 的 4,638 码约 4.2 公里,42001 的 3,032 码约 2.8 公里。

也就是说,一个名义坐标为 24.475 N 的站,实际观测点可能落在半径四公里内的任何地方。对海温这种空间尺度较缓的量,四公里几乎无影响;但对海面风与浪,四公里的位移足以改变有效吹程与水深。

三层坐标的含义要分清。

层 出处 含义 精度
名义坐标 站表 lat/lon 锚点位置 三位小数,约 100 米
观察圈 站页 watch circle radius 允许摆动的半径 通常 1–5 公里
实际坐标 漂流格式的 LAT/LON 列 逐条记录的位置 两位小数,约 1 公里

只有漂流平台与无人水面艇的 stdmet 里有第三层。系泊浮标没有——想要实际位置,只能从观察圈推一个不确定区间。这对把浮标当「点」用于卫星像元匹配的人是个实质限制:你只能证明「观测发生在锚点 4 公里内」,不能证明「发生在锚点正上方」。

§4.6 实时流与归档流是两条时间线

官方对这两条流的定性写得很清楚:实时文件是「went through automated quality checks and were distributed as soon as they were received」的数据,归档文件是「gone through post-processing analysis and represent the data sent to the archive centers」的数据。

两句话点出三个差别。

质控的深度不同。 实时流只过 NWSTG 侧的自动检查:传输奇偶校验、量程限值、时间连续性、阵风与风速比这类关系检查、气压传感器电池电压检查。归档流在 NDBC 侧还要过更严的量程与时间连续性限值、重复传感器一致性比对,以及人工机助复核(把风速与波浪谱能量画在一起看、看时序列图与天气图)。

时效不同。 实时流覆盖最近约 45 天,随收随发;归档流按年、按月生成,当月的数据要等月结束才封闭,上一年的年度文件要等下一年的处理周期。

缺测写法不同。 这一点在 §2.3 已经展开,此处只补一句因果:实时流的 MM 是「此刻没收到或没过自动检查」,归档流的 99.00 是「后处理时判定这一格不可用」。两者可能对同一格给出不同结论。

同一个时刻,两条流给的值也可能不一致——这不是错误,是两条不同处理链的正常产物。做近实时应用的人用的是前者,做完备分析的人用的是后者,把两者拼成一条序列而不做标记,等于在一条曲线上混入两种误差结构。

§5 工具层:怎么取、怎么读、怎么验

§5.1 四条获取路径

路径 形态 适合 注意
站表 XML 单文件,272,488 字节 拿当前站名录与坐标 每几分钟重生成,须记 created 时间
实时目录 每站一个 .txt,最近约 45 天 近实时监控、最近窗口回溯 MM 缺测,未过人工质控
当年月目录 /data/stdmet/<月英文>/ 取本年度已封闭的月份 已封闭月份是 .gz 月包,当月才是单站 .txt
历史年归档 /data/historical/stdmet/ 取上一年及更早的整年 一站一年一文件,1990 年代前格式不同

四条路给的字段同一套,格式同一套,只有缺测写法与目录结构不同。写死路径的脚本换条路就要改。

§5.2 先量站表,再谈取数

站表只有一份,两百多 KB,但它决定了后面所有循环的规模。

#-- 取站表并统计站型;count 属性与元素数应一致
curl -sS https://www.ndbc.noaa.gov/activestations.xml -o activestations.xml
grep -o '<station ' activestations.xml | wc -l
grep -o 'count="[0-9]*"' activestations.xml | head -1
#-- 期望:两者都是 1352(2026-10-29 抓取时)
#-- 按 type 与 met 两个属性交叉统计,决定后面要循环哪些站
import xml.etree.ElementTree as ET
from collections import Counter

root = ET.parse("activestations.xml").getroot()
st = root.findall("station")
print("created", root.get("created"), "count", root.get("count"))
print(Counter((s.get("type"), s.get("met")) for s in st).most_common(8))
#-- 期望:('fixed','y') 与 ('buoy','y') 占大头,('fixed','n') 也很多

这两段一起做的意义在于:你会发现 met="n" 的站有 441 个,它们即使有文件,多半也只有浪或只有水位。把它们全部纳入 stdmet 循环,会拉进来大量空文件。

§5.3 拉一个站,先看表头再决定解析器

不同年代的文件表头不同,解析器不能写死。

#-- 只读前两行,判断这是哪种年代的格式
import urllib.request

def head(url, n=3):
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=60) as r:
        return [next(r).decode("ascii", "replace").rstrip("\n") for _ in range(n)]

for u in [
    "https://www.ndbc.noaa.gov/data/realtime2/42001.txt",
    "https://www.ndbc.noaa.gov/data/stdmet/Sep/42001.txt",
    "https://www.ndbc.noaa.gov/view_text_file.php?filename=42001h2025.txt.gz&dir=data/historical/stdmet/",
]:
    print(u.split("/")[-1][:40], head(u))
#-- 期望:实时与当年月文件有 18 列、含 mm;年归档文件缺 PTDY 列(17 列)

第三段的结果会揭示一件官方文档没有明说的事:当年月文件的表头有 PTDY,年度归档文件的表头没有。度量说明页写「PTDY … (not in Historical files)」,但当年月文件严格说不算「historical files」——它们还没被折叠进年归档。于是同一个月的数据,在当月目录里带气压倾向、在下一年的年文件里就没了。这是一处真实的、会让「回补历史」与「滚动取数」得到不同列集的边界。

§5.4 §1.5 的案例在取数这一层的落地

给 41001 写一个「它还在吗」的探测函数,会把 §4.2 那张证据表变成具体代码。

#-- 三级探测:实时文件、当年当月文件、归档目录里最大年份
import re
import urllib.error
import urllib.request

def probe(sid, months=("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")):
    base = "https://www.ndbc.noaa.gov/data/realtime2/"
    try:
        req = urllib.request.Request(base + sid + ".txt", headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=30) as r:
            last = r.read().decode("ascii", "replace").splitlines()[-1]
        return "realtime", last[:16]
    except urllib.error.HTTPError as e:
        if e.code != 404:
            raise
    # 实时没了,看本年度已封闭月份是否还有归档
    for m in months:
        u = f"https://www.ndbc.noaa.gov/data/stdmet/{m}/{sid}.txt"
        try:
            urllib.request.urlopen(urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"}), timeout=30)
            return "monthly", m
        except urllib.error.HTTPError as e:
            if e.code != 404:
                raise
    return "archived-only", None

print(probe("41001"))
#-- 期望:('archived-only', None)——9 月被回收后,当月目录里的文件也已清空

这个函数的返回值只有三种,但对应五种现实情况:实时有数据(在测)、当年月份有数据(本月停报)、只有归档(已回收)、也许还有第四种「实时有但当年月份没有」(新开站,本月还没归档),以及第五种「三处都有」(正常在测的老站)。判断一个站的状态,需要多次请求,没有单一字段能回答。

§5.5 批量取归档:先列目录,再定并发

归档目录是一个 Apache 索引页,1.5 MB 上下的 HTML,包含全部年文件名。拿它一次就能建好完整的下载清单。

#-- 列目录、抽文件名、按站聚合;不下载数据
curl -sS https://www.ndbc.noaa.gov/data/historical/stdmet/ \
  | grep -o 'href="[^"]*\.txt\.gz"' \
  | sed 's/href="//; s/"//' > files.txt
wc -l files.txt
#-- 期望:17049
cut -d'h' -f1 files.txt | sort -u | wc -l
#-- 期望:1350(含少数站号里本身带字母、被 cut 切错的情况,需人工核对几行)

cut -d'h' 这一行是故意留的坑:站号里可能含字母 h(站表里确有 0y2w3、54nr7 这类编号),用字母切分会把它们切开。更稳的写法是用正则剥离末尾的 h<年>.txt.gz。

#-- 用正则剥离尾部年份,站号里的字母不会被误伤
sed -E 's/h[0-9]{4}\.txt\.gz$//' files.txt | sort | uniq -c | sort -rn | head -5
#-- 期望:前几行是报了几十年的老站,计数在 40 以上

§5.6 读一份年文件:形状、年份、缺测比例

拉回来之后先跑三项检查,比什么都重要。

#-- 形状、年份跨度、每列缺测比例;只依赖标准库
import gzip
import io
import urllib.request

url = ("https://www.ndbc.noaa.gov/view_text_file.php"
       "?filename=42001h2025.txt.gz&dir=data/historical/stdmet/")
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
raw = gzip.decompress(urllib.request.urlopen(req, timeout=90).read()).decode("ascii", "replace")
lines = raw.splitlines()
head, names = lines[0].split(), lines[1].split()
rows = [l for l in lines[2:] if l.strip()]
print(len(names), "columns;", len(rows), "rows;", rows[0][:10], "to", rows[-1][:10])
first = [r.split()[0] for r in rows]
assert len(set(first)) == 1, "一份年文件里出现多个年份"
print("rows/year =", len(rows))
#-- 期望:17 columns; 45335 rows; 2025 01 01 to 2025 12 31
#-- 期望:rows/year = 45335,约为 365 天 × 1440 分钟 / 10 分钟 = 52560 的 86%

最后一行那个 86% 是这份数据最重要的一个「规模」数字:一份看似完整的年文件,实际只覆盖理论可能时刻的八成半。缺的那一成半,是停报、传感器故障、与后处理剔除。

§5.7 一个不报错的错:把当年月文件当年文件用

当年目录的结构随时间变化,这是最容易写错的一处。

月份状态 位置 文件名 格式
本月(尚未封闭) /data/stdmet/<月>/ <站号>.txt 纯文本
本年度已封闭月 /data/stdmet/<月>/ <站号><月号><年>.txt.gz gzip
上一年及更早 /data/historical/stdmet/ <站号>h<年>.txt.gz gzip

2026 年 10 月 29 日的实际情况是:9 月目录里是 769 个 .txt(当月即将封闭),8 月、7 月、5 月目录里各七百多个 .txt.gz(已封闭)。同一个月目录下的文件扩展名会切换,<站号>.txt 在月份滚动后就变成 <站号><月号><年>.txt.gz。

写一个「每天取一遍当月文件」的脚本,如果写死 <id>.txt,跨月那天会静默开始 404;如果写死 .gz,当月取不到。安全的写法是两种都试,且把「本月」与「上月」都纳入。

§5.8 拼一条长序列:三个来源的交界处

拼一条从 1975 年到今天的序列,会在三个地方遇到边界。

交界 哪两边 遇到什么
归档 / 当年 historical/stdmet 与 data/stdmet 列数不同(年文件无 PTDY)
上年 / 本年 上年 12 月与本年 1 月 无重叠,靠日历对齐
当年 / 实时 上月月包与实时 45 天窗口 有重叠,须去重且注明来源

第二处看着最无害,其实最容易错位:归档按年切分,跨年那一条记录必然落在两个文件里各一次(12 月 31 日 23:50 与 1 月 1 日 00:00),按年拼接时如果不做时间索引去重,会在跨年处出现重复行。

第三处是真正的陷阱:实时窗口覆盖最近 45 天,其中会与「上一个已封闭月份的最后几天」重叠。同一时刻在两处都有,值可能不同(实时未过人工质控)。一条干净的序列必须在重叠区里只取一处,并在元数据里写明取的是哪一处。

§5.9 批量下载的纪律

一万七千余个文件、单文件从几百字节到几 MB,全部拉下来是几十 GB 的量级。真正会出问题的不是带宽,是「拉了一半、不知道缺哪一半」。

第一条纪律是从目录索引生成清单,不要凭年份区间推。归档里有的站只报过两个月,有的站中间断了几年;用「站号 × 年份」笛卡尔积去猜文件是否存在,会得到大量 404,且无法判断 404 是「本来就没有」还是「刚才网络抖了」。

第二条是先记清单,再下载,最后对数。files.txt 的行数与下载目录里的文件数必须相等。少了就是少了。

#-- 限四路并发、断点续传、失败重试两次;先建目录再落盘
base="https://www.ndbc.noaa.gov/data/historical/stdmet"
xargs -P 4 -I{} curl -sS --retry 2 -C - --create-dirs -o "data/{}" "$base/{}" < files.txt
find data -name "*.gz" | wc -l
#-- 期望:等于 files.txt 的行数

并发开到四而不是十六,是因为这条链路对单连接有节流,且失败重试会被放大。断点续传对 .gz 尤其重要:zip 与 gzip 都不能用「大小相等」判断完整性,中途断开的文件看起来大小对了,解压时会报错。

§6 层与层之间:它在本库中的位置

§6.1 库内的海洋与气候家底

这套库以医学数据为主,地球观测是近几年补进来的一块。能与 NDBC 形成上下游关系的,目前有三类:格点海温产品(OISST)、再分析场(ERA5)、陆地站点观测(GHCNd),另有一个在方法上同构的空气质量站网(AQS)。

四者的关系不是「同类替代」,而是「同一套物理量的不同采样几何」:两个是格点,两个是点集;两个在海上,两个在陆上。

§6.2 与 OISST 的分工:点与面

这是最该写清楚的一对关系。

OISST 是每天一张 0.25° 的全球海温图,720 乘 1440 个格点,处处有值——值不是测出来的,是插值出来的。NDBC 是几百个点,每个点只报自己脚下那一小块水面,绝大多数时刻没有值,因为那一格根本没人去过。

维度 NDBC OISST v2.1
几何 点集 规则格点
空间覆盖 北美沿岸与北大西洋为主 全球海洋
时间覆盖 1970 起,逐站不同 1981-09 起,全局统一
值从哪来 传感器测量 卫星反演加现场订正再插值
缺值含义 传感器坏了 / 没装 / 站没了 云遮蔽或被掩膜
适合做 真值校验、点尺度分析 场尺度建模、全球统计

实践中的分工是一句话:要面 → OISST;要点的真值 → NDBC;拿 NDBC 校 OISST、拿 OISST 补 NDBC 的空,是常见但不该混为一谈的两件事。

「拿 OISST 补 NDBC 的空」这件事尤其要小心:OISST 的插值本身就吸收了一部分浮标观测,用它来填浮标的缺口,等于用被浮标影响过的场来补浮标自己。填补之前至少要确认那批浮标是否参与了订正——官方文档没有公布逐站的参与清单。

§6.3 与 ERA5 的分工:观测与同化

ERA5 单层再分析给的是同一套气象量:风、气压、气温。它也在海上,也按小时给。

差别在于 ERA5 是一个模式的产物,浮标进了它的同化系统。于是「用 ERA5 验证 NDBC」这件事在逻辑上是倒的:同化的输入不能当同化的裁判。正确的做法是把 NDBC 当代替品——用 ERA5 看大尺度形势(哪几天有锋面过境、哪段时间气压在掉),用 NDBC 看那一格到底多高。

§6.4 与 GHCNd 的分工:海站与陆站

GHCNd 是全球陆地的逐日气温与降水站网,十三万个站。它和 NDBC 在方法上是同构的:都是点集、都是定宽或半结构文本、都有大量退役站与列不全的问题。

区别在采样高度与代表性。GHCNd 的站多在有人烟的地方,NDBC 的站多在人迹罕至的洋面。把两者配对,能得到一条横切海岸线的温度剖面——这正是海岸带城市热岛研究里最缺的一段。

§6.5 与 AQS 的分工:一个方法上的对照

AQS 是美国的环境空气质量地面站网,与 NDBC 没有物理关系,但两者的工程问题几乎一一对应。

问题 AQS 的说法 NDBC 的说法
站表是不是全部 站点清单含已关闭站 站表不含退役站,归档目录才有
缺测怎么标 有独立的 null 标记与符号 实时 MM、归档 99.00
两种数据流 原始数据与质控数据 实时流与归档流
站点位置 逐站固定坐标 锚点坐标加观察圈

这类问题的迁移性很强:在一个站点型数据集上踩过的坑,换一个领域往往还踩得到。

§6.6 检索路径建议

按需求找:要海表温度的格点场 → OISST;要海上的原位真值 → 本篇;要多要素大气背景 → ERA5;要陆地气温与降水 → GHCNd;要地面空气质量 → AQS。做「海温趋势加风暴事件」的组合研究,本篇提供事件时刻的高频观测,OISST 提供事件前后的场,两者在同一张时间轴上对齐的关键是时区——NDBC 全 UTC,OISST 的日值取 UTC 正午,对齐时要决定按哪个小时切片。

§1.5 的案例在这一层的意义是:当你要把本篇与格点产品拼起来做长期趋势时,已经退役的站会以两种方式同时消失——实时流 404,站表却还在。如果按站表遍历,那个站会一直返回空,而空值在下游的均值计算里被静默跳过,趋势线的端点就悄悄断了。

§7 质量评估与局限:它不该按什么标准评价

§7.0 阅读本节前需要知道的一件事

用「数据集」这个词去评价它,会得出错的结论。

它不是一个有版本号、有 DOI、有发布说明的产品。它是一个运营着的观测网:站会开、会关、会换载荷、会漂。今天抓下来的站表,明天就变了;今天写下的站数,下个月可能就少了十几个。

所以评价它该用的尺子是:作为一个持续运营五十多年的观测体系,它的口径是否可追溯、它的边界是否被写清楚、它的缺口是否被标注。

而不是:它的覆盖率有多高、它的缺失率有多低、它有没有现成的训练测试划分。

§7.1 三种常见的评价框架错位

常见错位 表现 应该怎么想
拿格点产品的标准套它 抱怨「到处都是洞」「没法直接算平均」 洞是本质,点集天生稀疏;要面就去用格点产品
拿成品数据集的标准套它 抱怨「没有版本号」「没有引用格式」 它是运营中的服务,不是出版物;引用靠站号加取数日期
拿自己脚本的标准套它 抱怨「昨天还能跑,今天 404」 站被回收了;这恰恰是它真实的样子

三种错位里,第一种最常见也最伤。一个团队拿 NDBC 去算「墨西哥湾 2020 年逐日平均水温」,把 42001 与 42002 两个站的观测求日平均,得到一条曲线。这条曲线实际上是两个点的平均,代表的是两片直径几公里的水面,不是「墨西哥湾」。

§7.2 DAIMS 二十四项逐条对照

DAIMS 原本面向社会科学调查数据,这里借用它的检查点,把这份观测站网的成熟度摊开。

# 检查项 状态 说明
1 宽格式 ⚠️ 定宽文本勉强算宽表,但列位随年代变,需要转换器
2 唯一标识 ⚠️ 站号加时间戳,但站号在同一度方格内可复用
3 特殊字符 ✅ 纯 ASCII,无 BOM,无空格
4 重复行 ⚠️ 跨年处与实时/归档重叠区会出现同一时刻两次
5 缺失编码 ✅ 两套哨兵值,官方文档明确声明
6 标签标识 ⚠️ 列名有历史漂移(WD 到 WDIR 等),文档给了对照表
7 罕见类分组 ✅ 无类别概念;站型与产物类型在站表里有字段
8 偏倚评估 ❌ 传感器更替与站位移无逐站量化
9 数据字典 ✅ 度量说明页逐列解释,含公式与采样方式
10 信息性缺失解释 ⚠️ MM 不区分「没装」「坏了」「站没了」三种成因
11 设备记录 ⚠️ 站页有人读的载荷说明,不进数据文件
12 共线性 ⚠️ WVHT、DPD、APD、MWD 同源同仪器,同作特征等于重复计权
13 编码映射 ✅ 站型、owner、pgm 三个字段在站表里
14 时间戳处理 ✅ 一律 UTC,无时区歧义
15 划分建议 ❌ 无训练测试划分建议;站间不独立,时序强相关
16 泄漏讨论 ❌ 官方未讨论站间共址与时序邻近导致的样本相关问题
17 标签分布 ⚠️ 无标签;值域由量程限值约束,但未写进文件
18 测量偏倚 ❌ 仪器高度、壳水线深度、载荷更替都影响量值
19 外部验证建议 ⚠️ 官方无统一交叉验证协议
20 版本记录 ❌ 无版本号,无 DOI,站表只有 created 时间戳
21 预处理脚本 ❌ 不发布官方读取脚本
22 合规要求 ✅ 公有领域,无使用限制
23 多模态对齐 ❌ 波浪谱、连续风、海流是平行通道,无统一键对齐说明
24 去标识化 ✅ 无个人数据;船位与站址均为公共设施

DAIMS 评分:10.5 / 24。按满项 1 分、半分项 0.5 分、零分 0 分计,七个满项、七个半分项、十个零分项。

评分解读:分数偏低,但低在「它不是为机器学习准备的数据集」这件事上,而不是低在观测质量上。满项集中在编码、时间戳、合规、字典这些「标准化」维度;零分集中在版本、划分、泄漏、脚本这些「产品化」维度。NDBC 是一套运营了半个世纪的观测服务,没有人需要给它发版本号。

对你意味着什么:不要指望官方替你解决「哪些站可以合并成一条序列」「哪些站之间不独立」这类问题。这些判断只能自己做,且必须写进方法的 Limitations。

§7.3 逐条局限

局限 具体表现 后果
空间极不均匀 710 个固定站集中在北美沿岸与五大湖 「区域平均」实质是沿海平均
站号可复用 同一度方格内站号可重新分配 长序列包含不同物理实体
载荷会换 41001 换 SCOOP,46012 从 USV 换 仪器高度与精度不连续
缺测不分因 MM 不区分没装、坏了、站没了 无法区分「结构性空」与「故障性空」
列名漂移 WD 到 WDIR,BAR 到 PRES 跨年代解析器要分叉
列数漂移 年归档无 PTDY,当年月文件有 回补与滚动取数得到不同列集
目录结构漂移 已封闭月是月包,当月是单站 跨月取值会静默断
两条流不等价 实时未过人工质控 重叠区同为两值时不知该信谁
无版本号 站表只有 created 时间戳 结果不可精确复现,只能记取数时刻
010 与 111 无交换 无 —

八行之外还有一条最实在的:没有一个文件记录「这个站哪年到哪年报了哪几类量」。想知道 41001 从哪年开始有波浪观测,只能逐年打开文件数非空列,或者去读站页的散文。这份元信息在这份数据里是缺失的,而它恰恰是做长期序列时最先要回答的问题。

§7.4 §1.5 的案例在这一层的意义

41001 这个例子在质量评估这一节给出的教训,不是「站会关」,而是**「关」这件事本身没有被结构化记录**。

站表没有状态字段,归档目录没有关闭日期,实时文件只有 404。一个站在 2026 年 9 月 9 日被回收,这件事只写在那一天的站页 HTML 里。下个月站页可能被改写成另一个措辞,明年可能连这句话都消失,只剩下「无数据」六个字。

于是「某站的水温序列到哪一年结束」这个问题,没有一个字段能回答。你能做的是:查归档目录里该站的最大年份,查当年月目录里该站最晚的月份,查站页有没有维护提示,三者取最晚的那个,并把它记在自己的元数据里。

这就是为什么 §5.4 那个三级探测函数是必要的——它不优雅,但它是唯一能逼近真值的手法。

§7.5 外部验证:怎么用别的数据检验它

参照 能验证什么 注意
OISST 格点 浮标与所在格点的偏差,检验格点产品的插值误差 取格点时要先确认浮标没参与该产品的订正
卫星 L2/L3 海温 单星反演的偏差 需按像元足迹匹配,浮标是点、像元是公里级
船报观测 同一海区的第二种独立观测 船报时空稀疏,且高度不一致
高频雷达海流 表层流速交叉验证 只覆盖近岸,与浮标的位置不重合
再分析场 大尺度形势的一致性 同化了浮标,不能当独立参照

这些组合都没有官方协议,所以「偏差多少度」这类说法必须附上时段、区域、匹配半径与是否剔除高缺测站,否则不可复现。

§8 生态与版本:一个机构怎么让五十年的点连起来

§8.1 没有版本号的时间线

它没有 release,只有事件。下面这些时间点是能查到的、会改变数据形态的节点的集合。

时间 事件 证据
1970 归档目录里最早的年份,只有 1 个年文件 目录文件名
1971–1973 每年 1 至 10 个年文件,早期站零星上线 目录文件名
1976 五位 WMO 站号开始使用 度量说明页
1980 年代 归档文件表头为两位年份,无分钟列,WD/BAR 旧列名 41002h1985 表头
1990 年代 出现分钟列,气压列逐步由 BAR 改称 PRES 41001h1990 表头
2026-09-09 41001 被回收,仍在站表 站页正文
2026-10-29 站表 1,352 站,归档 1,350 站 站表与目录

这条时间线里最值得注意的一格是 1980 年代。那个时期的归档文件是两位数年份、没有分钟列、采样每小时一次、列名完全是另一套。它不是「同一种数据的早期版本」,而是「另一种格式的数据」——任何跨 1990 年代的序列拼接,都要在这里写一段专门的转换。

§8.2 它在生态中的位置

NDBC 处在一个独特的位置:它既是业务(国家级海洋气象观测网,直接服务于天气预报与航行警告),又是研究原料(几乎所有海洋模型与卫星产品的校验源)。

这种双重身份有一个副作用:它的口径首先是为业务定的,不是为科研定的。十八列里为什么有能见度与潮位?因为海岸警卫队与引航需要。为什么气压对岸站要折算到海平面?因为预报要用。研究者想要的次表层温度、盐度、叶绿素,绝大部分不在这一套接口里。

§8.3 谁在依赖它

  • 业务层:国家气象局的海洋预报、飓风中心的强度分析、海岸警卫队的航行警告
  • 科研层:卫星海温产品的现场校验、海浪模型的初始场、海气通量估算
  • 工程层:海洋牧场与海上风电的环境基准、港口运营的风浪阈值
  • 教学层:定宽文本解析的经典练习——格式简单,坑足够多

§8.4 它为什么能持续

一套起步于 1970 年代、输出格式是定宽文本、缺测标记只有两个字母的观测体系,为什么没有被 JSON API 与云原生格式取代?答案有三层。

第一层是接口的稳定性本身就是资产。四十年前写的解析脚本今天还能跑(除了两处列名与列数的漂移)。对业务系统而言,不坏比先进重要。

第二层是它是唯一的上游。区域观测系统(IOOS)的站、州政府的站、大学的站,最终都要汇进这同一张站表与同一种格式。下游可以有很多,上游只有一个。

第三层是维护是按站做的,不是按数据集做的。每一次布放、回收、换载荷都是一次独立的维护工单,站页上写着回收日期、载荷型号、传感器高度、水深、观察圈半径。这种「逐个照看」的模式没法批量化,但也没法被整体替换。

三层叠加的结果是:它不是一份数据,是一个还在运转的设施。设施的价值不在于它此刻有多完整,而在于它每一天都还在产出新的一行。

§8.5 许可与引用

许可这件事,官方的表述比多数人想的更简单,也更含糊。

没有独立的许可页。 站内页脚只有「Disclaimer」「Information Quality」「FOIA」「Privacy Policy」四个链接,没有一个叫「License」或「Terms of Use」。

性质上属于美国联邦政府作品。 NDBC 由 NOAA 国家气象局运营,站页页脚的机构全称是「US Department of Commerce / National Oceanic and Atmospheric Administration / National Weather Service / National Data Buoy Center」。联邦政府的作品在美国版权法下不享有版权。

没有 SPDX 标识。 没有 CC0,没有 CC BY,也没有任何机器可读的许可字段。文件里没有,站表里没有,页脚里没有。

所以能写出的最准确表述是:公有领域性质,无使用限制,官方未给 SPDX 标识,也无单独使用协议。 写成 CC0 或 CC BY 都是替官方做主;写成「保留所有权利」则是反向的错。

引用方面,官方不给引用格式。实践中的做法是写站号、取数时刻与三个入口 URL 之一。站号比站名稳定,取数时刻比「最新数据」可复现。

§9 使用指引:三条路径,一份最小实现

§9.1 三种角色,三条路

你的角色 建议路径 关键动作
想快速验证一个格点海温产品 先取站表,再按区域筛出带波浪与水温的浮标 只取有 WTMP 的站,逐个拉年文件
要做海浪或风的短临预测 实时目录加当年月目录 先确认目标站本月还在报,再谈模型
要拼四十年的长序列 从归档目录反查站,不要从站表正查 先把 440 个只在归档里的站找出来

三条路的第一动作都不是写模型,是搞清楚「哪个站还在、测什么、从哪年到哪年」。

§9.2 在 AI 流水线中的位置

浮标传感器 → 自动量程检查(NWSTG)→ 实时文件(MM,45 天)
                     ↓
            人工机助复核(NDBC)
                     ↓
        当年月文件 / 历史年归档(99.00,定版)
                     ↓
  格点产品订正(OISST、卫星 L2)  /  模型训练目标  /  业务阈值

这条链说明一个常见误用:把浮标当「训练目标」,拿 OISST 当「输入特征」,然后宣称模型能从卫星预测浮标水温。如果 OISST 的插值里已经吸收了这批浮标,模型学到的是「复现浮标对 OISST 的贡献」,不是「从卫星推断现场温度」。

§9.3 一份最小可跑的读取实现

下面这段只依赖标准库,把「读文件—判缺测—拼成时间索引」这条链完全摊开。

#-- 依赖:仅标准库;输入为一个年归档文件的解压文本
import datetime as dt

COLS = ["YY", "MM", "DD", "hh", "mm", "WDIR", "WSPD", "GST", "WVHT", "DPD",
        "APD", "MWD", "PRES", "ATMP", "WTMP", "DEWP", "VIS", "TIDE"]

def is_missing_hist(tok):
    # 归档文件的缺测是一串 9;空字段也算缺
    return tok == "" or set(tok) <= {"9", ".", "+", "-"}

def parse_year(text):
    rows = [l for l in text.splitlines() if not l.startswith("#") and l.strip()]
    out = []
    for line in rows:
        cols = line.split()
        if len(cols) < 16:
            continue
        rec = dict(zip(COLS, cols))
        ts = dt.datetime(int(rec["YY"]), int(rec["MM"]), int(rec["DD"]),
                         int(rec["hh"]), int(rec["mm"]))
        out.append((ts, {k: None if is_missing_hist(v) else float(v)
                         for k, v in rec.items() if k not in COLS[:5]}))
    out.sort()
    return out

rows = parse_year(open("42001h2025.txt", encoding="ascii", errors="replace").read())
print(len(rows), rows[0][0], rows[-1][0])
print(sum(r[1]["WTMP"] is None for r in rows), "rows without sea temp")
#-- 期望:首尾跨度恰好一年,缺水温的行数在多数站上小于一成

两个要点:is_missing_hist 用集合判断,99.00、999、999.0 全部命中而 1012.7 不命中;out.sort() 是必要的,个别年文件里月份不是严格升序,跨月拼接时依赖排序。

§9.4 评估时最少要报的三件事

要报的 为什么 怎么报
站的选择规则 站与站不独立,邻近站高度相关 写明按区域、按站型、还是按数据完整度筛
时间划分方式 逐小时强烈自相关,随机划分高估精度 写明按年、按月块还是按事件切
缺测的判定口径 MM 与 99.00 两套,混用会静默污染 写明每列用哪条规则、剔除了多少行

§9.5 三种流水线的形态,与它们各自的错法

同一份数据,三种任务,三种切法。切错了,模型照样收敛,指标照样好看。

任务 数据形态 常见错法 该怎么做
单站时序预测 取一个站,几十年,一小时一条 随机抽时刻做训练验证 按年切,留出完整年份
多站联合建模 取一片海的十几个站,拼成长表 把邻近站当独立样本 按地理块分组,块内不跨训练测试
场重建 用浮标点重建格点场 用 OISST 当标签又当输入 标签与输入必须来自不同来源

第三种错法最隐蔽。用浮标训练一个「从卫星反演到现场温度」的模型,验证时却拿 OISST 当答案,而 OISST 里吸收了这批浮标——三边互相污染,指标会很漂亮,上线后不成立。

§1.5 的案例在这一层给出的提醒是:即使切分正确,样本量也会在你没注意的时候缩水。41001 在 2026 年 9 月后没有新数据,用它做的「近期验证集」会不声不响地变成空集。一个只按文件名拉取的流程不会报错,它只会给出一条停了三个月的曲线。

§10 AI 使用声明

栏目 内容
性质 海上原位观测站网,点集,非格点,非插值
用途 格点产品校验、海洋事件复盘、区域环境基准、时序建模
不建议用途 全球空间统计、等间隔规整表训练、把邻近站当独立样本
伦理 无个人数据;下游若用于海上作业决策,须披露观测中断与站位移
引用 站号 + 取数时刻 + 入口 URL,缺一不可复现
许可 公有领域性质,无使用限制;无 SPDX 标识,无独立协议页
版本 无版本号;站表有 created 时间戳;归档无版本概念
状态 在线运营,整点更新;实时流 45 天,归档流按年/月封闭

§11 常见误读与避坑清单

常见说法 实际情况 证据
「NDBC 有上千个浮标」 站表里浮标只有 438 个,固定站占一半以上 站表 count 与 type 属性
「缺测写 NaN」 实时写 MM,归档写 99.00/999,两套都不叫 NaN 度量说明页第二段
「归档文件是空格对齐的,直接 split 就行」 早期格式是定宽文本,split() 会错位;列位随年代变 1985 与 2026 年文件表头对照
「站表就是全部站」 站表只有活动站,归档里另有 440 个已退役站 站表与归档目录比对
「站表里的站都在报数据」 41001 已被回收,仍在站表里但实时接口 404 站页「Buoy recovered 9/9/26」
「同一个站号就是同一台设备」 站号在同一度方格内可被重新分配 度量说明页 Station ID 一节
「站表坐标就是浮标实际位置」 名义坐标,实际在观察圈半径内摆动,41001 约 4.2 公里 站页 watch circle radius
「十八列每列都有值」 各站常年全空的列不同,能见度与潮位绝大多数站全空 实时文件逐列缺测率
「实时流和归档流是一回事」 实时只过自动检查,归档过人工复核;缺测写法也不同 度量说明页与质控页
「年归档文件包含 PTDY」 年归档没有该列,当年月文件有 两处表头对照
「上个月的文件就在 historical 目录里」 当年数据按月放,已封闭月是 .txt.gz 包,当月才是 <id>.txt /data/stdmet/<月>/ 目录列表
「波浪谱、连续风、海流在同一份文件里」 各自独立的字母后缀(w、c、o、a),互不覆盖 归档目录文件名后缀
「NDBC 的站和 GHCNd 的站可以随便合并」 站号体系不同,NDBC 是五位 WMO 号,GHCNd 是本国编号 两个站表对照
「数据从 1970 年连续到今年」 1970 年只有 1 个年文件;站点数从个位数涨到一千多 归档目录按年计数

附录 A 速查卡

键 值
站表 https://www.ndbc.noaa.gov/activestations.xml
实时文件 https://www.ndbc.noaa.gov/data/realtime2/<站号>.txt
当年当月 https://www.ndbc.noaa.gov/data/stdmet/<月英文>/<站号>.txt
历史年归档 https://www.ndbc.noaa.gov/data/historical/stdmet/<站号>h<年>.txt.gz
缺测(实时) MM
缺测(归档) 99.00 / 999 / 99.0 / 999.0
列数 当年格式 18 列;年归档 17 列(无 PTDY)
时间 一律 UTC;早年文件无分钟列
站数(2026-10-29) 见 §2.1;活动与归档交集 908
许可 公有领域性质,无 SPDX,无独立协议页

附录 B 站型与产物对照

站型 站表数量 met="y" 有无 stdmet 文件 主要产物
fixed 710 554 多数有 风、气压、气温
buoy 438 272 多数有 全套气象加波浪
dart 76 0 少 水位,海啸事件
oilrig 54 23 部分有 风、气压、气温
tao 48 0 部分有 赤道锚系气象
other 24 13 部分有 视合作方
usv 2 1 有 视载荷

附录 C 术语表

术语 含义
NDBC 国家数据浮标中心,NOAA 国家气象局下属
stdmet 标准气象数据,本文的主接口
C-MAN 海岸海洋自动网,岸基固定站
LLNR 本地站号,出现在站页标题,不进文件名
观察圈半径 系泊浮标在锚点周围允许摆动的距离
有效波高 20 分钟波浪序列中最高三分之一的平均,不是最大波
主波周期 能量最大处的周期,与波高同源
哨兵值 用一个特定数值表示缺测,NDBC 归档用一串 9
定宽文本 按字符位置而非分隔符对齐的文本格式
当年月文件 本年度已封闭月份的数据,按月打包
年归档 上一年及更早的整年数据,一站一年一文件
WMO 站号 世界气象组织五位编号,1976 年起使用,可在同格内复用
NWSTG 国家气象电信枢纽,实时流的自动质控在此进行
TAO/TRITON 赤道太平洋与印度洋的锚系观测阵列
DART 深海海啸评估与报告系统
USV 无人水面艇

附录 D 常见问题

Q1:要注册才能下吗?
不用。站表、实时目录、归档目录都允许匿名访问,没有令牌,没有点击同意。也没有速率限制声明,但实际并发太高会被节流。

Q2:为什么同一个站昨天能下今天 404?
两种可能。一是站被回收了(41001 在 2026-09-09 被回收),二是月份滚动导致扩展名从 .txt 变成 .txt.gz。前者要查站页,后者要查目录。

Q3:为什么归档里的浪高是 99?
不是 99 米,是缺测。归档文件用一串 9 表示缺测,99.00 是该列的固定写法。按 float 读会得到 99.0,不会报错。

Q4:能算某个海区的平均水温吗?
能,但要点名是哪几个站、每个站代表多大范围。在没有站的区域,平均是不可得的。算出来的数字最好写成「N 个站的平均」而不是「某海区的平均」。

Q5:站表里的站数为什么每次抓都不一样?
站表每几分钟重生成一次,created 属性带时间戳。站会开、会关,数字会变。引用时带上时间戳。

Q6:波浪、风、海流为什么不在一个文件里?
官网把不同物理量分成不同产物,各有独立的字母后缀(w 波浪谱、c 连续风、o 海流、a 流剖面)。同一站同一年可能有多份文件,按产物分开。

Q7:41001 现在还有数据吗?
实时没有(404),2026 年 5 至 7 月的归档还能从站页链接取到,2026 年 9 月被回收。它的最后一个月数据在 /data/stdmet/ 里随月份滚动逐步转为包文件。

Q8:这份数据能不能当卫星海温的标签?
能,而且是常用的做法。要注意三点:浮标与卫星像元的空间匹配、浮标观测深度与卫星表层深度的差异、以及浮标本身是否参与了卫星产品的订正。

Q9:这算不算「AI-ready」?
按本文的口径算一半。它格式简单、文档齐全、匿名可取、无版权限制,这四条是 ready 的;它缺版本号、缺划分建议、缺逐站元数据表,这三条要使用者自己补。

编辑说明

核验时间 2026-10-29。方法:直接下载站表 activestations.xml(Content-Length 272,488 字节),解析 count 属性与全部 station 元素,按 type、met、owner、pgm 四个属性分桶计数,并计算经纬度极值;抓取归档目录 /data/historical/stdmet/ 索引(17,049 个 .txt.gz),按正则剥离年份得到 1,350 个站号,与站表做双向差集;抓取当年目录 /data/stdmet/ 下九个月份目录,确认已封闭月份为 .txt.gz、当月为 <站号>.txt;下载 42001 的实时文件与 2025 年年归档、2026 年 9 月月文件,逐列统计缺测率与行宽;下载 41002(1985)、46001(1975)、41001(1990)三个早年归档文件,取表头确认两位年份、WD/BAR 旧列名、无分钟列;读取站页 station_page.php 的正文,取 41001、42001、46012 的 watch circle radius、传感器高度、载荷型号与维护提示;读取度量说明页、归档问答页、质控页、小时文件页与五天文件页,取缺测写法、列定义、列名废弃对照表与两段质控流程的原文。未做任何分布统计,因此全文没有水温均值、浪高极值频率或区域偏差数字。当年九个月目录的文件计数(740–769)只用于说明「当月与已封闭月」的目录切换,未据此推算全年总量。实时文件的行数(6,457–6,478)为抓取当日窗口的实际长度,约 45 天、10 分钟一格,未作为官方口径。文中「约合公里」的观察圈半径换算按 1 码等于 0.9144 米计算,非官方表述。


相关数据集导航

以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:

  • noaa-cors-gnss — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
  • era5-single-levels — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
  • oisst-v2-1 — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
  • ibtracs — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
  • argo-gdac-floats — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
  • jhu-csse-covid-19 — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
  • aqs — 共享标签:公共卫生与流行病学 / 疾病监测
  • our-world-in-data — 共享标签:公共卫生与流行病学 / 时序数据
  • mics — 共享标签:公共卫生与流行病学 / 疾病监测
  • bts-on-time-performance — 共享标签:公共卫生与流行病学 / 时序数据

导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

返回 AI-Ready 数据集