鱼种水温数据采集|调研过程与决策速览

一个「爬两个字段」的工单,怎么变成三次推翻前提、九个数据源、最后把其中一半任务取消掉。

飞书「V3鱼总表」267 行 · 2026-09-18 → 09-20 · 表终态 rev 5047

5
实际落表列数
(原计划 2 列)
3
被推翻的
任务前提
9
调研过的数据源
采用 2 · 排除 7
267
行全部有值
0 空值

01起点:它看起来是个小任务

工单只有一句:给「V3鱼总表」补 偏好水温上下限两个字段,数据从 FishBase 爬。

预期路径很短:取鱼种清单 → 爬 FishBase → 写回表格。

而这件事从头到尾,没有一次「取回一个事实」。 每个值最后都是「用一条明确规则把位置填上,并标注它是什么」—— 找源失败不是执行问题,是这些量本来就不在源里。

02数据源全景:九个源,逐个交代

这是最花时间的一段——找源、发现源不对、换源。九个源逐个说清。

✓FishBase Preferred temperature (Ref. 123201)采用
出现在物种页 「Estimates based on models」区段,形如 16.9 - 31.1, mean 26.7。 它是 AquaMaps 模型推算的「海洋生态位」偏好水温——海洋/溯河鱼有值,纯淡水鱼系统性缺失。取数必须走 country/CountrySpeciesSummary.php,summary/<属>-<种>.html 那个页面不渲染这一行。 用于 海洋模型 51 行 + 淡水期缺 13 行。
✗FishBase 气候带温度范围(Ref. 12741 等)排除
Environment: 行里的 Subtropical; 10°C - 28°C。逐字能对上表内既有的 资料水温下限/上限。 它是按气候带查表,不是物种属性:267 行只有 141 个不同取值,17–27 一个值被 26 种互不相关的鱼共用;还含 −1.3℃ 这种水在 0℃ 就结冰的负值。ref 也不唯一(鲻鱼用的是 4944)。
✗FishBase 的其他温度字段不存在
扫描海洋物种页:只有 AquaMaps 那一行是结构化的温度字段。其余温度信息都是生物志正文的自由文本(“occurs where water temperature ranges 13°-29°C, optimum 17°-22°”这种),逐种散落、无法批量取。 ⇒ FishBase 侧没有物种特异的「可接受 / 耐受温度范围」字段。
✓Hasnain, Minns & Shuter 2010(CCRR-17)采用
安大略气候变化研究报告《Key Ecological Temperature Metrics for Canadian Freshwater Fishes》。 FTP = Final Temperature Preferendum(“鱼最终选择停留的温度”)——正是「偏好」语义、且是淡水口径,补上 AquaMaps 的缺口。 ⚠️ 该表页旋转 90°(x=物种,y=字段),必须按坐标解析,平铺文本会错配。实测提取 80 种(报告称 87)。 用于 野外实测 41 行 + UILT 作校验。
✗USGS FishTraits(10.5066/f7wd3zh8)排除 · 最险的坑
看起来完全对:809 种美国淡水鱼、有数值列 MINTEMP/MAXTEMP(℃、每种一对上下限)…… 但语义完全不对:MINTEMP = 分布区质心的「30 年 1 月最低温」,MAXTEMP = 「7 月最高温」,由 range map + PRISM 400m 栅格读出 ⇒ 陆地气温气候位,不是鱼的适温。
反证:MINTEMP 低到 −22.5℃——水 0℃ 就结冰。
最险的地方:源库自己把这组列放在 「Temperature Tolerances」标题下 ⇒ “去查文档”也救不了。
附带雷:缺失码 -1 不是「−1℃」而是「美国本土无分布图」。
✗USGS 的其他逐物种温度数据不存在
直连 sciencebase.gov/data.usgs.gov 被 Cloudflare 403,挂代理重试仍 403。 绕路核法:改走 DataCite REST API(不受 CF 影响),按 USGS 的 DOI 前缀 10.5066 全库枚举 + 十余组关键词 —— temperature preference marine fish → 0;thermal tolerance marine fishes → 0;preferendum → 全库 0 命中。 ⇒ USGS 的水温数据几乎全是站点/课题级观测,不是跨物种汇编。
? Morley 2019(686 种)/ Dahlke 2020(>600 种)备查未采用
查 USGS 时顺带发现的、覆盖更大的海洋候选。 未采用理由:性质与 AquaMaps 同类(建模值),并未解决「不是实测」的问题。
✗生产配置表 TempAffinity无边界字段
字段只有 temperature_fav(最喜欢的温度 ×10,单点)+ temp_affected_ratio(半宽)+ temp_threshold(门控)。 ⇒ 模型里没有任何「下限/上限」字段——「可接受水温」在生产模型里根本不存在这个概念。
✗三个「看着像」的字段空壳
查「可接受区有没有源」时另找到的三个近似物,全是「字段建了但没填」:
BasicFishHabitus.temperature_range只有 2 行真实数据,且两行值完全相同
FishWiki.water_temp49 行,有值的 0 行
PondList.hypolimnion_t13 个钓场全是 100(同一常量)
这三个比「搜索无结果」强得多:「我没找到」是关于我的陈述;「字段建了但全空」是关于系统的陈述——后者才证明“不是我查不到,是它不存在”。

03第一次转折:28% 不是失败,是边界

首轮抓完 77/267(28%)。按常规该「再爬一遍」。查下去发现不是抓取问题——看按科的命中分布:

各科「有值 / 总数」——海洋科几乎全中,纯淡水科全空 Scombridae 鲭科 10 / 10 Salmonidae 鲑科 15 / 28 Acipenseridae 鲟科 5 / 8 Cyprinidae 鲤科 0 / 26 Xenocyprididae 鲴科 0 / 18 Leuciscidae 雅罗鱼科 0 / 17 Centrarchidae 太阳鱼科 0 / 12 Cichlidae 丽鱼科 0 / 11 ⇒ 这是 AquaMaps 的覆盖边界,不是抓取失败
数据可复验:sources/scrape_out.csv

⇒ 引出两条补法:淡水源(Hasnain)与模板法。

04第二次转折:把「找数据」换成「查用法」

偏好列写进去后,发现 77 条里 55 条「偏好区」跑到「可接受区」外面——比如褐鳟偏好 14.5–17.6,而「可接受」写 18–24,完全不重叠。

按常规该去找一个更准的可接受源。但先查了一件事:「资料水温」到底怎么来的?

逐字核对 FishBase:白斑狗鱼 10–28、褐鳟 18–24、虹鳟 10–24 —— 对应的是 Subtropical; 10°C - 28°C 这种 气候带温度范围。

⇒ 那个「倒置」被读懂为「两列本就不同源」——倒置是证据,不是待修的病。

症状
偏好区跑到可接受区外面
看起来像「数据错了」
查
可接受区是怎么来的?
⇒ FishBase 气候带查表值,141 个取值被 267 行复用,含 −1.3℃
正解
换可接受区的数据源
而不是把偏好区夹进去(那会抹掉证据)

05第三次转折:整个任务被取消

顺着「可接受区要不要重做」往下,做了一件更省事的动作——去翻生产配置表,看这个字段到底被谁读。

生产水温模型=「单点 + 半宽 + 门限」,没有区间

temperature_fav(最喜欢的温度 ×10,单点)+ temp_affected_ratio(半宽)+ temp_threshold(门控)

⇒ 资料水温下限/上限 不进入生产模型,它是「资料」参考列。
⇒ 整个「找可接受水温源」的任务取消了——那个方向本来会白费一轮。

飞书四列水温 → 生产对应 最喜欢的温度(°C) → temperature_fav ÷10 ✓ 对温度变化的容忍系数 → temp_affected_ratio ✓ 水温接受阈值 → temp_threshold ✓ 资料水温下限/上限(°C) → 无对应物 · 不进模型

06FishBase 缺口的三种形态

「FishBase 里没有」这句话,其实含 三种完全不同的情况——把三者混为一谈,就会在最难的那一类上反复白找源。

267 行按 FishBase 命中情况分三类 ① 真的没有 —— 190 条 纯淡水科:鲤科 26 / 鲴科 18 / 雅罗鱼科 17 太阳鱼科 12 / 丽鱼科 11 / … 对照:鲭科 10/10 全中、海洋科几乎全中 ⇒ Hasnain 救回 28 + 模板 162 = 190 ✓ ② 有,但给的是海洋期 —— 13 条 值不是缺,是错阶段:AquaMaps 给的是海洋分布区适温 虹鳟 1.3–10 → 淡水实为 12.7–18.5 褐鳟 6.5–15.8 → 14.5–17.6 帝王鲑 0.7–8 → 12–16 ⇒ Hasnain 全部换掉(其中 11 条是鲑鳟类) ③ 洄游种,三个源都覆盖不到 —— 13 条 短吻鲟 · 高首鲟 · 白化高首鲟 · 白马切喉鳟 · 美洲条纹狼鲈 · 美洲鳗鲡 · 美洲西鲱 北极红点鲑 · 花羔红点鲑 · 大西洋小鳕 · 欧白鲑 · 闪光鲟 · 俄罗斯鲟 Hasnain 覆盖不到 · USGS FishTraits 语义错(是陆地气温) · 第三方已查遍 ⇒ 保留海洋值 + 标记「海洋模型·淡水期缺」
表内筛 水温数据口径 = 海洋模型·淡水期缺 即可取待办清单

一句判据:「FishBase 没有」→ 先问是「真没有」、还是「给了错阶段」。
前者要去别的源找;后者只要换一个更贴近游戏场景的源。 两者混在一起找,会在第三类上白耗。

07偏好列:怎么把 267 行填满

值来自多个源,所以加了一列 水温数据口径 逐行标出来源——把「推导值」与「观测值」分开。

267 行的口径构成 模板 162 海洋模型 51 野外实测 41 按水温带取中位数(推导) FishBase AquaMaps Hasnain CCRR-17 的 FTP 洄游种暂无淡水期源(标记待补) 暖水 21.2–28.3 ×83 · 温水偏冷 8.9–17.6 ×46 广温 0.5–31.9 ×18 · 冷水 1–11.4 ×15 蓝色:海洋模型·淡水期缺 13
模板 162 行里,有 83 条鱼共用同一个值

模板法有一个我自己埋的坑(后来才发现):

广温带当时特意用「全跨度」而不是中位数,理由是「避免宽度塌缩」。结果 广温模板宽 31.4℃,是其它带的 3–4 倍——当时没觉得有问题,直到它把接受区算成负数才暴露。

而「广温」的语义是「耐受广」,「广」应该体现在可接受上,不是偏好上。

08海水鱼专项:一次钓场核查改变了问题

22 条洄游种当时用的都是 AquaMaps 海洋值,而早先裁过「洄游种一律取淡水期值」——这批全部不合规。

原
22 条洄游种该换什么值?
预期成本:为 20+ 个物种找淡水期水温源
查
先查:这些鱼在游戏里投放到哪些钓场?
Noob_Sea「新手海」存在但 is_show=否(禁用)⇒ 没有独立海鱼池; 真正的海洋种集中在港口村;22 条里只有 7 条真被投放,其余 15 条没进任何水域
新
问题变成:港口村是什么水体?
成本从「找 20 个数据源」降到「问一句」 → 答案:淡海混合
A 组 15 条 · 取淡水口径
  • 鲟类 5(短吻鲟/高首鲟/闪光鲟/俄罗斯鲟…)
  • 鲑类 5(帝王鲑/粉鲑/北极红点鲑…)
  • 美洲鳗鲡、美洲西鲱、美洲条纹狼鲈
  • 欧白鲑、大西洋小鳕
B 组 7 条 · 维持海洋口径
  • 鲻鱼、公牛鲨、沙鮻
  • 海鲈、海鲈鱼
  • 毛鳞鱼、大西洋大海鲢

关键判据不是 FishBase 的 diadromous 标记,而是「这条鱼在游戏里主要在哪个水体被钓到」—— 公牛鲨、鲻鱼、沙鮻主要是海洋鱼,FishBase 挂标记就给它们填淡水值是荒唐的。

09接受水温:从「找源」到「设计值」

8.1 先确认:飞书现成的那列不能用

判据是嵌套(偏好区必须落在接受区里):成立率只有 30%(80/267),187 行越界,其中 57 行偏好区比它自己还宽。

8.2 方案演进:两次走错

三个方案,为什么前两个不行 ✗ 按水温带映射 冷水/温水偏冷/暖水/广温 各取一个区间 两个坑:① 带已被模板填满(带内 IQR=0,循环论证) ② 带太粗,暖水 2.9–31.9 几乎覆盖全温域 ✗ 在偏好区间上「按比例」放大 fav ± k×半宽 想法:偏好区间越宽,接受区间也按比例更宽 自败,而且方向是反的 —— fav 越宽 ⇒ 降段越宽;而暖水宽 fav 的鱼恰恰离致死线最近 ⇒ 它给「最该小心的鱼」最宽的外扩 实测:比例 r=0.3 越致死线 3 种;加法 F=2.0 只越 1 种 ✓ 改成加法:accept = fav ± F,每侧统一 2.0℃ 四带一致,斜坡可见;用致死线(UILT)卡出上限 F=2.0 时越致死线仅 1 种;F=2.5 时暴增到 16 种

8.3 F=2.0 是怎么定的

用 Hasnain 的 UILT(上临界致死温度)卡——可接受上限必须低于致死线,否则必错。

F越致死线的种数说明
1.01外扩几乎看不见
2.01降段可见、且仍在致死线内
2.516接近一半的种越过致死线

一处自我更正:中途说过「k ≤ 1.25 是硬上界」——表述不准。 精确上界是 k < 1.372,且由单个种(蓝鳃太阳鱼)决定、只在 15% 的种上可测、用的是致死线 ⇒ 必要条件,不是充分条件。F 的实际取值由「要留多宽的降段」这个设计意图定,边界只给上限。

8.4 两条必须随行的声明

① max(0, …) 是显式规则
  • 52 行的偏好下限低于 2℃ ⇒ 会算出负值
  • 截到 0 是域约束(水不结冰以下无意义)
  • 与「把接受区夹进偏好区」性质不同——那会抹掉证据
  • 但同样必须写进口径,不能静默处理
② 它是设计值,不是实测
  • 四路源皆空之后,它是设计出来的
  • 不得标成「实测/资料」
  • 与门控阈值 temp_threshold 同向但不同层:一个是形状外沿,一个是消费判据

8.5 唯一例外

蓝鳃太阳鱼:偏好 26.9–31.2 → 接受 24.9–33.2,而致死线 32.0 ⇒ 越过。

但这两个数同出 Hasnain,偏好上限离致死线只剩 0.8℃ —— 真实蓝鳃太阳鱼耐受远高于 32℃。 ⇒ 判为那个致死线读数偏低,不是 F 取大了。保留并标注,不为它把全表的 F 压到 0.8。

10最终数字

88
接受区的不同取值数
(按带法只有 4)
0
嵌套违反
fav ⊂ accept
52
接受下限被 0 截
(显式规则)
13
待补:洄游种
淡水期值

11落到哪儿

落点内容
飞书 V3鱼总表
rev 5047
新增 5 列:偏好 2 + 口径 1 + 接受 2;267 行全有值、0 空值
交付目录final/(权威,飞书回读生成)/sources/(源与脚本)/backup/(6 份写入前快照)
Notion 鱼种库加 5 个属性 + 267 条记录更新(含源表版本 4987→5047)
线上速览本页

12一路沉淀的判断原则

这些都不是推演出来的,是踩过之后才留下形状的。

原则来自哪个坑
先查用法,再找源三次转折都靠它,最后一次直接把任务取消
列名 ≠ 语义scientific_name 装俗名、Temperature Tolerances 装陆地气温…
看填充率,不只看字段在不在FishWiki.water_temp 单看名字以为有数据,49 行全空
断言「不存在」要给系统级证据「我没找到」是关于我的;「字段建了但全空」是关于系统的
过程成立 ≠ 产物可用「模板不必重推」推不出「模板值可写进生产」
交付物要回读生成中间产物混进交付目录,会被当成生效数据引用
自我更正也要留痕「k ≤ 1.25 是硬上界」被自己证伪,更正一并记

共同点:每一次正确的判断,都来自「先去看它实际是什么」,而不是「先假设它应该是什么」。

13原始数据来源

全程用到的来源(含采纳、校验、以及调研过但排除的)。完整清单含可达性实测见 SOURCES.md。

用途来源链接
采纳|海洋模型 51+淡水期缺 13 FishBase Preferred temperature(Ref. 123201) fishbase.se/country/CountrySpeciesSummary.php?GenusName=…&SpeciesName=…
采纳|野外实测 41 Hasnain, Minns & Shuter 2010, Key Ecological Temperature Metrics for Canadian Freshwater Fishes(CCRR-17) collections.ola.org/mon/25007/299113.pdf
校验|接受上限天花板 同上的 UILT(上临界致死温度,39 种可测) 同 CCRR-17
排除|气候带查表值 FishBase Environment: 行(常见 Ref. 12741,鲻鱼为 4944) fishbase.se/References/FBRefSummary.php?ID=12741
排除|语义错(是陆地气温) USGS FishTraits Database(10.5066/f7wd3zh8) api.datacite.org/dois/10.5066/f7wd3zh8
排除|USGS 无逐种温度数据 DataCite API 按 DOI 前缀 10.5066 全库枚举 api.datacite.org
备查未采用|覆盖更大的海洋候选 Morley et al. 2019(Dryad 10.5061/dryad.1m2vn52)
Dahlke et al. 2020 Science(PANGAEA 10.1594/PANGAEA.917796)
datadryad.org / doi.pangaea.de
本地|权威目标表 飞书多维表格「V3鱼总表」(267 行) base_token P9rTbUTARaE1GasZp3rctaoungb
本地|生产配置表 preCompute/configs/xlsx/(TempAffinity、fish_pond 等) —

可达性实测(2026-09-27):主用源与关键 PDF 全部 200 ✓。 sciencebase.gov / hydrosource.ornl.gov / zenodo.org 为 403(Cloudflare)—— 与采集当时实测一致,正因如此当时才改走 DataCite API 绕路。

完整过程与决策记录:PROCESS-RECORD.md · 原始来源清单:SOURCES.md
交付目录:fishbase-pref-temp-2026-09-18/ · 目标表:飞书「钓鱼元素周期表 → V3鱼总表」rev 5047