首页 / 资讯中心 / 文章详情

给模糊信息建模:未知生物目录的工程实践

给模糊信息建模:未知生物目录的工程实践 ★ FEATURED ARTICLE
前两天我把一个攒了大半年的项目丢到了 Hacker News 上标题写着 “Show HN: A Catalogue of Unknown Creatures”。本来只是顺手分享没想到一天里涌进来一堆人讨论。有人问数据从哪来有人建议加地图还有人直接在评论区帮我补了一条我听都没听过的生物。回头翻聊天记录这个项目最早的灵感其实特别简单我一直在收集各种奇奇怪怪的民间传说生物发现网上的资料散得离谱同一个生物在不同语言、不同年代的名字完全对不上可信度也不一样有的有照片有的只有一句口头描述。要不干脆自己做一个结构化目录把这些“未知生物”统一管理起来这篇文章不会讲什么大道理就是把我在做这个目录时踩过的坑、想清楚的设计逻辑、以及实际能跑起来的技术方案完整列出来。如果你也对民俗、神秘动物学、数据可视化或者冷门内容平台感兴趣这篇东西应该能给你不少能直接用的思路。哪怕你完全不是开发者光看数据模型和分类方式也会发现一件有趣的事给“模糊不清”的信息建模其实比给“明确”的信息建模更需要设计。1. 项目定位先搞清“未知生物目录”到底想做什么1.1 为什么选这个方向冷门但有黏性的选题说实话“未知生物”不是一个热门赛道。它不像 AI、插件、效率工具那样能快速让人理解价值甚至第一眼看上去会被人当成“怪谈收集器”。但我做这个项目的核心动机并不是猎奇。我做了一圈调研发现所有现存的民间传说生物资料库基本都落在两种极端里一种是纯粹百科式的什么都收但没有统一的信息结构读完一个条目的信息量完全取决于作者心情另一种是营销号式的专门挑耸人听闻的内容几乎没有原始出处。这两类对我来说都没法用。“目录”的价值在于它得像科学标本柜一样用同样的标签、同样的测量字段、同样的证据链去规范每一个条目。哪怕内容本身是“未知”的字段类型必须是已知的。这就是我决定做这个项目的根本出发点我想给一团模糊不清的人类集体想象造一个结构清晰到不能更清晰的容器。1.2 它到底解决什么问题不是猎奇而是记录“灰色地带”人类的民俗传统里存在大量“证据不足但反复出现”的描述。某个湖怪在不同年代被不同目击者描述成相似的形状某个神秘巨兽在村落口述中流传了上百年但这些都没法被生物学正式确认。这类信息有几个共同痛处名称不统一不同地区叫法完全不一样出现时间跨度大有的记录隔了上百年形态描述不可靠同一个生物有时候被说成兽类有时候被说成鱼类真假混杂有一部分最终被证明是恶作剧或误认。所以我这个目录要解决的核心问题不是“证明某个生物存在”而是“忠实记录人类曾经声称看到了什么”。这也决定了整个项目的价值观我们只整理描述不负责判决。让证据强度、目击次数、来源可靠性这些维度自己去说话读者自己判断。1.3 项目的目标用户和边界我在设计初期圈定了三类核心用户。第一类是民俗学和人类学方向的爱好者他们需要快速检索不同文化圈的相似母题比如“化蛇”和“水蛇传说”之间的共性第二类是神秘动物学爱好者他们更关注目击记录、照片证据和可信度数据第三类则是纯粹被好奇心驱动的游客他们可能只想翻一翻地球上还有哪些未被命名的东西。边界同样重要。我一开始就想清楚了几条红线不做纯虚构文学生物的收录比如小说、电影和游戏里凭空创造的生物必须和民间传说分开不做政治化解读所有文化区域用文化圈而不是政治实体来标记不进行“存在/不存在”的二元判断只保留证据条目。有了这些边界整个项目的调性才稳住了。它不是一个玩笑项目也不是一个神棍项目而是一个偏民俗学数据工程方向的内容项目。2. 从零搭一个“未知生物目录”的核心设计2.1 数据模型我如何给“模糊”建模项目最开始的版本特别简单就是一张大表把所有生物塞进一堆列里名字、地区、身高、体重、外貌、行为、来源链接。跑了一周就发现问题了。问题出在“身高”和“体重”这类字段上。一个生物如果在目击记录里有时候是三米、有时候是十几米我这个列到底填什么填“3到15米之间”那检索的时候怎么过滤更麻烦的是有些生物并没有明确的形态描述只有一种模糊的行为描述比如“会在雾天模仿人声”。这类信息根本塞不进传统的数值字段里。我推倒重来之后确定了几个核心原则。第一所有柔性信息用“标签数组 区间字段”来存第二所有硬性信息比如首次目击年份、最近目击年份、目击次数单独拆出来用数值存第三任何无法确认的信息用null而不是填空字符串这样统计时才能区分“未知”和“不存在”。最终的生物主表长这样字段类型说明idUUID全局唯一标识不暴露录入顺序common_namestring最常用的英文名比如 Fresno Nightcrawleroriginal_namestring原生语言名称aliasesjsonb别名列表比如同一种生物在不同文化圈的小名type_tagstext[]形态标签可多选类人、四足、蛇形、飞行、水生、兽类、变形cultural_regionstring文化圈名称用枚举约束first_seen_yearinteger最早目击年份last_seen_yearinteger最近目击年份sighting_countinteger有效目击次数size_rangeinterval例如 “3 至 15 米”用最小值最大值表示weight_rangeinterval同上允许为空appearance_summarytext外貌描述最长为500字behavior_summarytext行为描述diet_tagstext[]食性标签肉食、植食、杂食、未知evidence_typetext[]证据类型民俗口述、疑似照片、足印模型、录音、物证credibility_scoresmallint0 到 100statusenumdocumented / unverified / disputed / hoaxcitation_jsonjsonb来源引用列表created_attimestamptz创建时间“大小”这个区间字段尤其有用。它不用逼我非得给所有生物填一个精确数值只需要填目击记录中出现过的最小值和最大值既能算中间值也不至于丢失原始波动信息。这里我必须承认我参考了生物多样性格里“形态测量”的设计思路但做得更宽容因为我们要记录的对象并不存在可测量的标本。2.2 分类体系形态、文化区域、目击类型、可信度我一开始犯过一个经典错误试图做一个“完美分类”结果把每个生物都塞进一个且只能一个分类层级里。比如某个生物半个身子像兽、半个身子像鱼我之前会强行分到“两栖类”。后来想明白了我们记录的不是生物学物种而是人类描述里的形象所以标签系统比树状分类要合适得多。于是我把分类拆成四个独立维度互不干扰。第一个维度是形态标签完全扁平化一个生物可以同时拥有“水生”和“蛇形”两个标签。第二个维度是文化区域我用了枚举列表刻意避开主权国家划分改用文明圈概念比如“凯尔特口述传统圈”“亚马逊雨林民俗圈”“东亚山地信仰圈”。这样处理不只是为了政治安全更是因为传说传播的边界本来就不以国界为界。第三个维度是目击类型主要分成五类民俗口述、疑似影像、物理痕迹、官方档案、二手转述。这个维度决定了后续可信度评分的取值区间。第四个维度才是真正的核心我把它叫做“证明状态”不是简单的“真/假”而是四个互相递进的等级状态含义示例场景documented有明确民俗志或档案记录跨多个独立来源某地区口述传统中的湖怪历代地方志均有提及unverified有目击记录但证据不足或来源单一某旅行者日志里的陌生野兽disputed存在相互冲突的解释有争议疑似照片被怀疑是漂浮木或造假hoax已被证实为恶作剧或故意造假某电视节目自导自演的神秘生物事件这个设计帮我稳住了项目价值观我记录“曾经有人宣称它存在”这个事实而不是记录“它存在”这个结论。所以哪怕一条生物最终被打上 hoax 标签它依然值得留在目录里因为“一个谣言如何产生和传播”本身就是很有价值的民俗学材料。2.3 条目结构从“名字”到“证据链”一个完整的条目最理想的阅读流程是从“名字”出发逐步深入到“证据链”的底层。我把条目拆成了哪些模块直接决定了代码实现难度。首先是名称和别名模块。这里我踩了一个不小的坑一开始 common_name 字段我用的是英文名后来一条来自夏威夷民间传说的条目原生名字长达二十几个字母我才意识到必须单独立一个original_name字段并且用aliases表来存多语言别名不然检索框一搜完全搜不到原生语言查法。其次是“目击时间轴”模块。每个生物条目都有几个关键时间点我会做成一个单独的子表sightings存每条目击记录的时间和地点地点只精确到文化圈层甚至模糊地理坐标。这样在主列表页可以渲染出一条“目击时间线”一眼就能看出某生物的记录在哪些年份扎堆出现哪些年份完全断档。然后是“证据链”模块。每条生物都携带一个证据数组比如一份1962年的报纸剪报、一枚野营者拍到的胶片、一段村民口述录音。这些证据会被分别打上“已考证”“待考证”“证据驳回”的标签。这个模块的最大价值是让读者和审校者都能看到判断的依据而不是只看到结论。最后是“交叉关联”模块。A 生物和 B 生物在形态描述上有80%相似度我会在后台生成一条关联记录方便民俗学爱好者做母题溯源。这个模块开发量不大但却是社区最喜欢用的功能之一它意外地帮整个目录建立了一种“知识网络”的感觉。2.4 技术选型为什么我用这套组合说实话这类内容型项目技术上没有任何“必须用XX”的理由但我还是仔细比较过最后敲定的技术栈是这个组合Python FastAPI 做后端PostgreSQL 存数据React Next.js 做前端地图用 Leaflet动画图表用 Recharts。选 FastAPI 纯粹是因为它天生自带 OpenAPI 文档我可以把数据模型直接暴露成可探索的 API 页面这样社区用户不需要学 SQL 也能查数据。而且它的异步支持对将来挂爬虫和图片处理任务有帮助。PostgreSQL 是必然选择因为jsonb和text[]这类半结构化字段太好用了MySQL 在数组和 JSON 检索上要费很多劲。我尤其依赖 Postgres 的 GIN 索引来加速全文搜索后面会详细说性能问题。前端用 Next.js 是因为它支持服务端渲染对搜索引擎更友好。其他目录型项目会纠结要不要做 SSR但我想通了如果一个民俗爱好者在谷歌搜某种生物的名字却不能直接搜到我目录里的条目那这项目就失去了传播的意义。3. 实操环节录入第一批生物条目会遇到的事3.1 我录入的二十个初始条目的分类过程第一批数据我选了二十个来源清晰、资料密度适中的生物大约花了一个周末来录入。这个阶段不追求数量而是为了验证数据模型是否扛得住真实世界的复杂情况。二十个条目里有几个非常有代表性。一个是来自北欧口传传统的“水灵”在不同地方被描述成半人半鱼、有时又像水獭。我一开始想把它归为水生类但它的形态标签经常跳动最后我给它加了“水生”“变形”“类人”三个标签。另一个条目来自北美某地有照片、有脚印模型、甚至有一小段录音但后来的调查证明照片里只是枯萎树干我依然保存了全部证据只是打上了disputed状态。录入过程给了我一个很重要的启发数据模型一定要设计成“允许冲突”的。一个生物同时被两个互斥的形态标签描述这不是数据错误而是现象本身如此。我要是强行把这个冲突抹掉反而丢了民俗学上最有价值的信息。3.2 数据清洗统一名称、别名与跨语言处理数据清洗是最枯燥但最重要的一环。我花在清洗上的时间比写代码的时间还多。先说名称统一。同一个生物可能有英文名、原生名、学术译名、营销号编造名。我做的处理是建立aliases表把名称分成“主要名称”和“别名”两类别名可以标记语言来源和来源类型。比如一条“卓柏卡布拉”的条目别名表里存了西语原名、英文音译、还有一个《纽约时报》用过的历史用名。检索系统会同时索引主表和别名表。跨语言处理上我一开始天真地以为只要把数据都翻成英文就行后来发现不行。民俗学概念在翻译过程会变形比如某个词在当地语言里是“灵魂守护者”直译成英文却变成“幽灵”意思差很远。我的解决办法是original_name保留原生拼写translation_gloss存国内通用的翻译解释并在其中加一条“语义不可完全对译”的提示。听起来有点矫情但民俗内容恰恰需要这种边界感。清洗阶段还有一个小技巧跑了一组模糊去重脚本把名称相似度过高的条目自动标出来人工再决定是合并还是分开。效果还不错省了几小时手工比对时间。3.3 可信度评分的设计逻辑专家分、来源分、目击分整个页面里最显眼的元素就是每个生物卡片上的一个数值分数0到100从灰到绿到红。这个分数不是随便算的我一开始试过纯人工打分很快发现不同人可以给同一个条目打出天差地别的分数。于是我把评分拆成了四个可解释的维度。第一个维度是“来源分”满分40。一级民族志、官方档案、正规出版社的著作来源分30到40地方报纸、个人出版物来源分15到30匿名网络帖子、无出处的营销文章来源分0到15。第二个维度是“目击分”满分30。目击次数超过十次且分布于不同年代给20到30三次以下给5到10只有单次孤证直接给0到5。第三个维度是“证据分”满分20。有疑似物理痕迹或影像加10到20只有口头描述加0到10。第四个维度是“一致性分”满分10。不同目击者对生物的描述彼此吻合加7到10互相矛盾明显减分。这四个维度加权求和就得到了最终分数。但我要特意说明一个细节哪怕分数低于20我也不会把条目直接删除因为低分和高分一样都有信息价值。这个评分模型公布之后社区参与度反而上来了因为每个人都能看到自己的补充会如何影响最终分数。3.4 图片与来源的版权处理容易翻车的环节做这种内容项目版权问题真的是最容易翻车的环节。我先给自己定死一条规则任何一条没有明确授权或者公有领域声明的图片一律不上传只保留图片链接让来源网站承担展示责任。实际操作里我用了一个折中方案图片统一抓取缩略图存本地但原图始终保留外链并且在citation_json里记录图片的 URL、作者、授权类型。如果某张图片是用户上传的必须勾选“我拥有这张图的版权或已获得授权”才能提交。还会遇到一些极其模糊的情况比如某条目配了一张手绘插图来自一本1985年的田野笔记作者已经过世无法确认授权。我的处理办法是把图片标记为“历史文献资料”并且只展示缩略图不做商业用途同时尽量寻找出版方的公开版权声明。这种“灰色处理”不是法务级标准但对个人项目来说已经是比较稳妥的做法了。4. 展示层与社区化怎么让目录“活”起来4.1 时间线视图与地理地图数据录进去只是第一步让读者能直观地看出一条条“传说脉络”才是真正有价值的部分。我想做两个展示视角一个是时间线一个是地图。时间线视图做起来比想象中简单因为first_seen_year和last_seen_year两个字段天然适合生成区间条。Recharts 的区间条组件直接把两个年份连成一线鼠标悬停时显示生物名称和状态。观众可以一眼看到地球上某个区域的传说记录在特定年代突然密集又在某段时间销声匿迹。这个视角特别适合讲民俗传播的故事。地图视图麻烦不少。Leaflet 本身很成熟但问题在于坐标精度。很多传说根本没有精确坐标我只能根据文化区域质心生成一个模糊位置并且在地图上用半径大小来表示不确定程度。我不会把某个传说精确标到某个村庄上因为那样既误导人也可能干扰现实居民。模糊坐标确实损失了一部分“身临其境”感但换来了安全性和规范性。这个模糊坐标设计在 Hacker News 上被不少人夸奖过他们说一般项目只会无脑把所有点打在地图上很少考虑数据不确定性的可视化。这让我觉得当初多花的那一天时间非常值。4.2 开放API的设计把目录变成生态底座项目做到中期我开始意识到光有网页界面还不够。如果别人想基于我的目录做自己的研究或者写个机器人做每日“神秘生物推荐”他们需要一个接口。我利用 FastAPI 的自动文档特性直接暴露了三层 API。第一层是列表页/api/v1/creatures支持分页、标签过滤、评分排序第二层是详情页/api/v1/creatures/{id}返回完整数据包括证据链和来源引用第三层是搜索接口/api/v1/search?q会对主名、别名、标签做加权检索。授权不用复杂的 OAuth直接给 API Key每位注册用户能拿一个令牌每天限一千次调用。这个限流策略让免费版足够个人使用又不至于被爬虫拉爆。API 真正上线两周后已经有两个小项目挂在上面做了第三方客户端一个是预测“哪些生物条目将要升级状态”的社区实验另一个是给条目生成本地化翻译的众包插件。当时我非常有成就感因为这代表目录不再是一个孤立网站而变成了一个小生态里的基础设施。4.3 社区提交与审校流程避免猎奇内容淹没数据开放提交功能之后我很快发现了一个问题大量用户想上传的其实是虚构游戏生物和自创角色。这些内容质量并不低但不符合目录“民间传说目击记录”的定位。为此我设计了一套提交与审校流程。用户提交新条目时必须先选择“来源类型”按自定义规则划分成四类民间口述传统、历史档案、目击记录、网络传说。选“网络传说”需要额外填写原始链接和传播时间线如果不填就无法进入审校队列。后台审校界面我把所有待审条目按“可信度评分从低到高”排列优先处理低分条目因为低分条目往往有版权或真实性风险。审校者只做三件事确认来源类型、检查是否有明显伪造痕迹、补充缺失字段。不做“是否可信”的主观判断。整个过程公开透明审校记录会在条目详情页展示谁在什么时候改了什么全部有迹可循。5. 常见问题与排查技巧实录5.1 数据录入乱序为什么越录越乱项目上线一周后我发现自己录入新条目时经常搜到重复内容。起因是我在录入界面直接用了浏览器自带搜索后知后觉地发现搜索接口搜的是common_name根本不会匹配别名。比如我花半小时录完一条新条目结果发现“另一个名字”的同款生物早在数据库里躺着了。教训很简单搜索必须同时覆盖主名、别名、原始名。我用 Postgres 的pg_trgm扩展做了模糊匹配并为aliases表建了 GIN 索引之后重复录入率才降下来。如果你也在做内容型数据库这个坑千万要避开一开始就把别名索引建好别走我的弯路。5.2 跨界生物的标签冲突问题有一类生物会同时命中多个分类比如水系生物又有飞行描述导致列表页按单一标签过滤时出现缺失或重复。最初有人建议我改成树状分类说只能选一个标签这样统计更“干净”。我想了想还是拒绝了。标签冲突不是数据错误而是这类描述的固有特征。我的解决办法是前端加了一个“标签交集”筛选模式用户可以同时选中“水生”和“飞行”系统返回同时满足两个条件的条目。这算是在分类和真实世界之间找到了一个折中方案。5.3 图片加载失败与缩略图处理图片是目录里最占用资源的部分。我最初直接把原图缩略图挂在列表页结果某些图片来自古董网站加载极慢有时候还会因为防盗链直接挂掉。我做了两层处理。第一层是把所有图片统一抓取到本地 OSS后台跑一个resize jpg转Avif的压缩流程限制最大宽度800像素第二层是在数据库里加了一个image_ready布尔字段只有本地的缩略图生成成功列表页才显示图片卡片否则只显示一个占位图标并外链原站点。这个改造让列表页首屏加载时间从四秒多降到了不到一秒收益非常明显。5.4 性能问题条目明明不多查询却越来越慢刚开始我觉得几千条数据而已怎么可能会慢。结果当我在搜索接口里加了“按文化区域过滤、按评分排序、再全文匹配”三层条件后响应时间飙升到了两秒多。原因是jsonb里的来源数组成了搜索和排序的拖累。排查下来问题不在引擎而在表结构。我把大的文本字段拆到了long_reads字表列表接口只查主表摘要字段全文搜索用tsvector的索引字段。改完之后响应时间回到两百毫秒左右。对于内容型小项目这个优化思路足够支撑到百万级条目了。最后分享一个小技巧做这个项目时我养成了一个习惯每录入一条新生物都会顺手写一段“这条为什么值得收录”的草稿存在notes字段里。这些草稿后来意外成了审校和社区讨论最好的起点。有人说我的目录有一种难得的“温度”大概就来自这些看似多余但真实存在的信息。如果你也想做一个类似的内容型项目少钻技术牛角尖多想想你的数据凭什么让用户信任这比任何架构决定都重要。
阅读完成 · 觉得有帮助?
咨询建站