2026/10/29 17:16:09

自托管信息聚合工具Iris:从解决问题到理性停更的复盘

自托管信息聚合工具Iris:从解决问题到理性停更的复盘 算起来Iris 是我两年前那个冬天突然想做的一个小项目。当时我的最大痛点不是时间不够用而是每天打开 RSS 阅读器看到数百条未读之后会产生一种很奇怪的情绪——不是想看是焦虑。我试过各种效率工具、待办清单、GTD 方法论发现全都是在教我怎么挤出时间没有一个是帮我把真正重要的信息从噪音里捞出来的。Iris 就是这么来的一个我自己开发、自己维护了快两年的自托管信息聚合工具。这篇内容不是要炫耀它多好用恰恰相反我想老老实实复盘一遍为什么我会做它、踩过哪些坑、以及最后为什么选择把它停下来。如果你是独立开发者、产品爱好者或者也在折腾自托管工具这篇应该能帮你省下几个月的弯路。1. 我为什么做 Iris先解决自己的问题再想别人的问题1.1 痛点不是读不完而是不知道错过了什么先说清楚我当时的处境。我做技术内容相关的工作平时必须盯着几十个信息源技术社区、行业博客、产品更新、研究报告还有一堆我不舍得取消订阅的冷门站点。日积月累下来我的订阅源大概有四五百个每天产出几百篇文章。我原来的处理方式是每天抽一小时刷一遍结果一周以后就放弃了。因为刷完之后我既没有获得知识增量反而更焦虑了。焦虑的来源很微妙未读数字太多我总觉得有重要内容藏在里面等我真正打开去读又发现 90% 是水文和重复资讯。这时候我试过几个现成的方案。第一用在线稍后读工具把文章统统收进待读列表结果收藏夹越来越长再也没打开过。第二用笔记软件配合标签体系但整理标签本身变成了一项新的全职工作。第三干脆靠人肉筛选上班看一遍、下班看一遍手动把好内容转给同事朋友坚持了不到两周就崩溃了因为人工筛选的稳定性和可持续性都太差。于是我意识到真正的问题不是读得不够快而是缺乏一道自动过滤的闸门。我需要一个机制在文章进入我视线之前就替我做一轮粗筛哪些和我的兴趣方向相关哪些质量够高哪些值得在今天有限的时间里优先看。这不是对现成阅读器的功能改进而是一套完全不同的产品逻辑——不是管理未读队列而是管理注意力预算。1.2 一句话定义 Iris给自己的信息流装一道过滤网既然市面上的工具没有解决我的核心问题我就决定自己做。Iris 在最开始的定义非常朴素每天定时从上百个订阅源抓取文章经过清洗、去重、打标签、打分之后只保留 20 篇左右的高价值内容展示在一个极简网页里。我不需要它像传统阅读器那样把我所有的订阅都塞到一个列表里我只需要它告诉我今天有哪 20 篇内容值得你看。这个名字起得也挺直接。Iris 是眼睛的虹膜也是希腊神话里的彩虹女神我用它来隐喻帮你看清信息世界的能力。项目早期有人建议我叫它XX Reader或者XX News我都拒绝了。因为我不希望它给人又一个阅读器的预期它更像一只帮你盯着整个信息海洋的过滤器。读者看到 Iris 这个名字第一反应是好奇它到底是什么后面的沟通成本会低很多。可能有人会问做一个这样的东西是不是很难实话实说如果只是做出来自己用真的不难。抓取有现成的库存储用 SQLite前端就是一张网页。真正难的是后续那两年里不断克制自己再加一个功能的欲望和最终承认它该停下来了的勇气。1.3 我给自己定的三条设计原则产品开工之前我给自己立了三条非常有针对性的原则后来的所有功能取舍都围绕这三条。原则一永远不做社交。不加评论、不加点赞、不加分享。原因很直接社交功能一旦加入产品的重心就会从信息质量滑向用户互动随之而来的是审核、举报、通知、关系链这些无底洞。而我做 Iris 的初衷就是对抗噪音社交恰恰是噪音最大的来源。原则二默认只看 Top 20。每天首页只展示评分最高的 20 篇文章往下翻需要显式点击查看全部。这条原则是反直觉的很多人都觉得内容越多越好但我的观察是人每天的深度阅读时间上限大概就 30-60 分钟能看进去的文章通常不超过 5-10 篇。Top 20 已经是冗余度极高的数字了。这个限制后来真正挽救了 Iris 的使用率。原则三所有数据归我所有。抓取的文章正文、打分记录、标签数据全部存在本地 SQLite 文件里支持一键导出为标准格式。我不接受任何把数据绑定在某家云服务的方案。理由很简单信息聚合工具的核心资产就是数据如果数据放在别人手里哪天服务关停或者调整政策我积累的筛选逻辑就全没了。2. Iris 的设计与核心思路从我全都要到我只要有用的2.1 产品边界三个不做和三个坚持给自己做工具最大的好处是你不需要讨好所有人可以非常自信地划掉一堆功能。我在需求文档里写了三组明确的对比后来每一次要不加个 XX 吧的冲动出现时我都会翻出来看一遍。三个不做三个坚持不做移动端原生 App坚持数据 100% 本地存储不做云端账号和同步坚持界面无广告、无未读红点不做社交和评论坚持所有打分规则透明可调移动端 App 为什么不做因为我当时的使用场景已经很明确早晚各打开一次桌面浏览器后来加了一个非常简单的 PWA 入口。做一个原生 App 意味着要维护 iOS 和 Android 两套构建、上架、证书更新这些成本对单人开发者来说是巨大的黑洞。云端同步也是同理个人项目最怕的就是引入分布式状态一旦有账号系统就需要处理注册、登录、密码找回、Token 过期这些和帮助用户读好文章没有半毛钱关系。我把所有同步需求用一条数据备份命令解决了每天早上自动把 SQLite 文件复制一份到我的 NAS 上。这些选择后来被证明无比正确。正因为产品边界极度克制Iris 的全部代码库在两年内只膨胀到不到 1 万行我一个人即使工作繁忙也能勉强维护住它。很多个人项目之所以烂尾不是因为能力不够而是因为在产品早期就把自己忙死在了无关紧要的支线上。2.2 优先级评分用 0 到 100 的分值替代未读红点传统阅读器赖以生存的交互是未读数字——用红色角标把你拉回 App。但在我看来未读数字是焦虑的放大器而不是信息的过滤器。Iris 的核心机制是用一个分数代替未读数我把它叫做 InfoScore信息价值分。这个分数由四个因子加权计算而来公式大致长这样score 0.4 * text_match 0.3 * recency 0.2 * source_quality 0.1 * length_penaltytext_match 衡量文章和我的兴趣主题的匹配度。这里我没有一开始就上复杂的机器学习模型而是用了一个关键词权重表我把自己关注的十几个主题整理成词汇集群例如AI、大模型、深度学习是一个集群开源、许可证、社区治理是另一个集群每个集群配置一个权重。一篇文章命中哪个集群、覆盖多少个词、关键词出现在标题还是正文都会影响得分。这套方案看起来简陋但非常可解释我随时知道某篇文章为什么得了高分。recency 是新鲜度因子我用了一个指数衰减函数recency 0.5 ^ (hours_since_publish / 72)。也就是说文章发布 3 小时后新鲜度还剩一半24 小时后衰减到约 14%三天后基本归零。这个衰减速度对技术资讯类内容很合适如果是学术论文类的源我会单独把这个参数调慢。source_quality 是来源的历史表现分。如果某个网站过去 30 天里被标记为有用的文章比例很高那它的基础信用分就会上升反之则下降。每次我点击标记有用都会反过来影响这个网站未来的评分。这样系统会慢慢学会某个科技博客的文章对我价值很高而某个标题党网站的发文会被自动压到最底部。length_penalty 则是一条简单的经验规则少于 300 字的文章通常信息量不足多于 5000 字的文章大概率是冗长报告。理想区间是 800 到 3000 字在这个区间外会有轻微扣分。这个因子只影响整体排名的上下滑动不会一票否决因为偶尔也会有高质量的长文。最终页面上不展示分数本身只按分数降序排列。Top 20 以外的文章默认折叠想看就点更多。这套机制的意义在于它把筛选从用户任务变成了系统任务。用户不再需要逐条判断这条要不要点开系统已经替你做掉了 80% 的决策。2.3 技术选型为什么我没有上那些花架子聊到底层实现可能是很多技术朋友最关心的部分。Iris 的技术栈非常朴素甚至在今天看来有点复古。但每一层选型都是我踩过坑之后作出的决策。环节选型理由抓取Python lxml requests生态最全遇到反爬和编码问题时的样本最多存储SQLiteWAL 模式单文件即可备份迁移并发读写对个人使用绰绰有余文本处理TextRank 规则关键词不需要 GPU不依赖外部 API效果对中文可接受前端一个原生 HTML 少量 JavaScript无构建工具、无 node_modules打开就是可用页面部署systemd cron Nginx 反代一台小机器能跑两年日志干净、维护直观为什么不上向量数据库、不上消息队列、不上微服务因为我对个人项目的维护成本极其敏感。一个自用工具如果每次跑起来要拉起六个容器那它只会在新鲜感消失后成为我的负担。SQLite 单文件的好处是我想备份就拷贝想迁移就换个机器跑一下一点心理负担都没有。事实上Iris 在两年里经历过一次服务器迁移整个过程就是停 cron、scp 数据库文件、改一下 systemd 路径、重启服务五分钟完成。这在任何重度依赖外部服务的架构里都是不可想象的。3. 实操过程从零搭一套能自我进化的信息管道3.1 数据层设计为什么三张表就够了很多人第一次做爬虫项目时喜欢把表设计得很复杂好像不做个三四张关联表就对不起这个项目。Iris 的最简数据模型只有三张核心表却支撑了所有功能需求。sources( id, name, url, feed_url, enabled, avg_score, error_count ) articles( id, source_id, url, title, content, content_hash, sim_hash, published_at, fetched_at, score, status ) tags( article_id, name )sources 表记录订阅源的基础信息和健康度每次抓取失败 error_count 就会增加连续失败多次会自动禁用该源避免反复请求浪费时间。articles 表是主表content_hash 用来做精确去重sim_hash 用来做模糊去重score 是前文提到的 InfoScorestatus 标记这篇文章是未读已读还是有用。tags 表就是简单的文章标签多对多映射支持筛选。这个模型里我最满意的一条是文章正文直接存在 content 字段里不做外部文件存储。很多同类工具喜欢把正文分开存成 HTML 文件或者 Markdown 文件看上去更规范实际上平白增加了文件管理复杂度。SQLite 对单条大文本的处理性能足够好直接存正文、再建一个普通索引查询速度完全没压力。每天凌晨 3 点cron 会触发一次抓取任务。调度逻辑很简单先遍历所有启用状态的 sources根据每个源的抓取间隔我统一设成 4 小时个别高频源设成 1 小时决定是否抓取然后逐条解析、清洗、入库。整个任务通常跑 20 分钟到 1 小时视当天内容量而定。3.2 抓取与清洗别让脏数据毁掉下游所有逻辑如果只用一个词形容抓取阶段的教训那就是脏。不同站点的编码各不相同有的用 UTF-8有的还在用 GBK直接解析字段分分钟出现乱码不同站点的 HTML 结构千奇百怪正文里夹杂着导航、推荐位、脚本标签和各种广告。我在第一版里用正则表达式去匹配正文 HTML结果一塌糊涂——规则定义了几十条却总被没见过的页面结构击穿。后来我把正文抽取改成文本密度法。核心思路非常简单一个页面里的内容区域往往是文本密度最高的区域。算法先解析出 DOM 树去掉 script、style、nav、aside 这些明显不可能是正文的节点然后逐个节点计算字符数 / 标签数这个密度指标密度最高的节点就是候选正文。最后再运行几个简单的清洗规则去掉空行、去掉无意义的本文转自某某、统一段落标签。这套方法对大多数文章类站点准确率在 85% 以上个别特殊站点我再手工写一条 XPath 覆盖规则。我特别想提醒的是请求环节的细节。爬虫请求必须带上合理的 User-Agent 和一个可联系的标识频率要控制在友好范围比如同一个域名的请求间隔不少于 30 秒。我早期因为抓取频率太高被好几个站点临时封禁过 IP后来在代码里加了一组带指数退避的重试逻辑第一次失败等 10 秒第二次 30 秒第三次 120 秒超过三次就直接标记该源异常。这样既不会把一个站点打爆也不会因为一次小失败就丢失全部内容。3.3 打标签、去重、摘要三个信息减法的灵魂如果说抓取和清洗是信息的净化那打标签、去重、摘要就是信息真正的减脂。这三个环节直接决定了用户看到的 Top 20 质量。先说去重。新闻网站最喜欢互相转载同一个事件经常出现几十个版本。我在第一版里只对标题做了一次规范化哈希结果荷兰式标题、含【】符号的标题、添加了副标题的标题全都漏网了。后来引入 SimHash 算法才解决先把正文分词并计算特征哈希映射成一个 64 位指纹再计算两篇文章指纹之间的汉明距离。距离小于 3 就判定为近似重复只保留来源质量更高那篇。SimHash 的优点是计算快、内存占用小而且对同义改写和段落顺序变化有一定容忍度非常适合做全文近似去重。打标签我用了规则词典 TextRank关键词两条腿走路。规则词典覆盖常见主题词但词典永远有覆盖不到的长尾词所以我会用 TextRank 从正文中提取 Top 5 关键词然后和主题词表做一次最大匹配。之所以不用更重的预训练模型是因为那个阶段我对实时性和可解释性的需求远高于单纯准确率。规则出错的场景我瞬间能明白为什么错模型出错时我完全没法调试。摘要生成也走了实用主义路线。我用 TextRank 的句子排序版本把文章切分成若干句子构建句子之间的相似度图然后迭代计算每个句子的权重。最终选出权重最高的三句话拼成摘要。这个方案对新闻类、资讯类文本效果非常好输出来自原文原句不会有生成式摘要的幻觉问题。它的缺点是摘要缺乏总结感更像是从原文里抽出的三句话但对我当时快速判断要不要读原文的需求来说已经绰绰有余。3.4 前端与交互把信息消费做成每天 5 分钟的仪式Iris 的界面是我特意做的极简甚至可以说简陋。整页只有一张卡片列表每张卡片显示四样东西标题、来源名、发布时间、预估阅读时间。没有封面大图没有侧边栏没有标签云。因为我的使用习惯是清晨打开一次看一遍列表把最感兴趣的两三篇点开读掉整个过程不超过 5 分钟。这个路径如果被任何多余元素打断都会变成又一次无尽的信息漫游。每条卡片右侧只有两个操作按钮已读和有用。已读就是标记丢弃有用是标记为高价值并反馈到来源评分。这里的交互设计借鉴了一个很朴素的心理机制按钮越少用户越愿意给出反馈。如果给五个按钮太棒了/不错/一般/差/举报用户大概率一个都不会点。两个按钮看似粗暴却让大部分用户愿意每天顺手标三五个有用这些反馈又反过来优化了 source_quality 因子。PWA 的入口我是在项目上线一年多以后才补上的。用户从手机浏览器打开网页可以添加到主屏幕之后就获得一个全屏、无地址栏的 App 式体验。这个方案完全绕开了应用商店的审核和证书管理却满足了移动端阅读的需求。我强烈推荐自托管轻工具的作者认真考虑 PWA它是个人开发者用得起的成本最低的移动端方案。3.5 部署与运维一台小机器跑两年的方案部署这块我选择了 systemd 加 Nginx没有走 Docker。为什么因为我只有两个长期进程一个 cron 定时任务一个提供网页服务的 Python 小型 Web 服务。用 Docker 至少要多维护一个镜像构建流程却带不来任何实际收益。systemd 的一个 service 文件就能搞定开机自启和崩溃重启日志也统一交给 journald 管理。给大家看下我当时的 service 配置片段[Unit] DescriptionIris Web Service Afternetwork.target [Service] Useriris WorkingDirectory/opt/iris ExecStart/opt/iris/venv/bin/python serve.py Restarton-failure RestartSec10 [Install] WantedBymulti-user.target数据备份则用一段极简的 shell 脚本每天凌晨把 SQLite 文件压缩后同步到 NAS。我在脚本里写了保留最近 30 天快照的逻辑超过 30 天的自动删除避免磁盘被滚雪球一样的数据占满。这套运维方案让我在两年内恢复过一次意外删库事故从发现到恢复只花了 15 分钟靠的就是当天凌晨的备份文件。4. 开发中踩过的坑如果重来一次我会省下整整三个月4.1 我做过最多的无用功把功能堆叠当成产品迭代Iris 上线三个月后用户量虽然不多但开始有陌生人写邮件来提建议。我犯了个人开发者最经典的错误把每一条建议都当成需求疯狂加功能。两个月内我陆续加上了主题热度周报、邮件推送、浏览器插件、多用户支持、自定义主题界面……结果在后续的埋点里看得清清楚楚除了邮件推送有 27% 的周活占比其余新功能的使用率长期在 5% 以下。多用户支持更是讽刺我上线半年只有三个人用过。这个教训让我彻底理解了什么叫开发 3 个月维护 2 年。功能堆叠不只是浪费开发时间还意味着每个新功能都变成了未来的兼容性负担。我给自己的补救方案是强制引入删除清单每次想加一个功能先写下这个功能如果三个月后没人用我是否愿意承担删除它的成本。这样一来我砍掉了至少 80% 的伪需求。4.2 文本去重与编码两个看似简单却翻车最多的技术点中文内容去重比我想象中要难得多。一次偶然的巡检里我发现同一条新闻在 Top 20 里出现了两次而我的 title 哈希完全没识别出来。原因是一个版本标题是OpenAI 发布最新模型性能大幅提升另一个版本是OpenAI 昨日重磅更新模型能力迎来全面升级。从标题层面看这就是两句话但如果做全文相似度计算它们的正文 90% 重叠。我只能把去重层级从标题精确匹配升级为正文 SimHash 近似匹配。升级之后同类重复内容的识别率从不到六成提升到了九成以上。编码问题则是典型的阴沟翻船。我用了 Python 的 requests 库抓取页面时自以为设置了utf-8解码结果遇到几个老站点依然乱码一片。后来才知道很多站点没有正确声明 charset甚至声明和实际内容不一致。我改成先用requests拿到原始字节流再用chardet自动探测编码最后在解析器里强制转成 UTF-8。这个改动非常不起眼却是我做项目一年多里最值钱的一次修 bug 经历它直接消除了大量看起来像乱码又不知道哪里来的脏数据。4.3 值得抄作业的项目终止决策清单Iris 能体面地停下来很大程度上靠的是一张清单。这组标准是我在一次又一次加班维护中总结出来的如果自查时满足至少三条就该考虑停更而不是继续自我消耗核心功能连续 8 周没有实质性的使用数据增长且留存曲线平滑向下。issue 列表里 BUG 报告的数量开始多于新功能请求意味着你正在失控维护旧债。市场上出现了 3 个以上同类替代品且其中一个明显做得更好、更新更快。你打开自己产品时的第一感受从期待变成了又该处理 pending 任务了。每次迭代发布前你不再想验证新功能是否好用只想确认它没有引入新 BUG。Iris 在停更前同时满足了至少四条。我的第一反应是要不要再撑一撑但清单上的事实告诉我撑下去的投入产出比只会越来越差。这张清单后来被我分享给几个朋友他们都觉得比很多技术教程更有价值因为它本质上是教人如何用理性决策对抗沉没成本谬误。5. 为什么我最终停下了 Iris三个诚实的原因5.1 外部原因通用 AI 把摘要能力变成了免费标配Iris 最引以为傲的三个卖点是自动摘要、自动打标签和优先级打分。但这些功能的核心竞争力正在被大模型工具直接抹平。在我开发 Iris 的那两年里把一篇文章丢进主流 AI 对话工具几秒钟就能收获比 TextRank 规则版好得多的总结丢十个链接进去AI 也能快速找出重复内容并归纳主题。这意味着 Iris 的差异化价值已经名存实亡。我要做的帮用户省时间的事情用户用一个通用 AI 对话界面就能做到而且做得更好还能自由追问这篇文章和我上周看的另一篇有什么观点差异。我很难要求用户为了体验一个自托管工具而放弃了这种便利。生态位一旦消失仅靠情怀维护一个项目是难以为继的。5.2 内部原因维护成本永远被低估它会一点点吸干你的精力统计下来Iris 的代码我总共可能写了 1200 小时但后续两年的维护时间加起来远比这个数字更可观。主要是这些隐形成本网站改版导致抽取规则失效、上游依赖升级带来的兼容性问题、抓取源反爬策略调整、用户报 bug 后的排查沟通、服务器安全补丁与证书续期。每一项听起来都不大但它们全都散落在你的非工作时间里像是一堆永远关不上的后台进程。最真实的时刻发生在停更前一个月。我晚上十点打开项目仓库看到三个待处理 issue一个是某个站点改版导致正文抽取为空一个是 SQLite 锁在特殊情况下没有自动释放还有一个是一个用户想要更快的全文搜索。那个瞬间我突然意识到我对这些 tasks 的心态已经从今天的我状态不错修一个 bug 吧变成了怎么又来了。当维护变成麻木的例行公事产品就失去了灵魂。继续下去我不仅不会把它做得更好反而会开始恨它。5.3 用户视角确实有人用但没有人是非它不可Iris 在最热闹的时候大约有几百个部署实例这个数字说不上失败但也绝对算不上成功。我更关心的是用户使用它的真实方式打开率前 20 天最高随后断崖式下跌。这说明大多数人并不把这个工具当成每天必不可少的信息入口而只是新鲜感驱动的试用项目。私聊里问得最多的一句话是这个和某某阅读器/某某 AI 工具比有什么区别我很难给出一个足够硬气的回答。结论很残酷Iris 是一个做得还不错的个人工具但它不是一个能持续创造独特价值的产品。用户想要的可能从来不是另一个信息聚合器而是一个真正理解他们兴趣的智能筛选系统。那个需求还在只是已经应该有更好的实现方式了。我与其继续给 Iris 打补丁不如尽早收手让用户去拥抱那些更强大的现代方案。6. 体面收官的实践参考停更不是终结是把经验归档6.1 关停前我做了四件不容遗漏的事很多人决定停止一个项目后就直接删库跑路。这是我最不推荐的做法尤其是开源项目草率关闭会消耗你的信誉。我花了一周时间做收尾做了四件事供你参考。第一导出所有数据。包括 SQLite 数据库、全部订阅源清单我导出了 OPML 格式、打分统计和标签分布。导出后的数据我打包存到了个人 NAS万一未来想继续这个方向这些积累不会白费。第二编写一份详细的 README 归档文档。我在项目仓库顶部写明了停更原因给出了数据导出教程和迁移建议并说明不会再接受新问题。这份文档既是给用户的交代也是给自己两年维护生涯的总结。第三把核心算法抽成可复用的独立小库。比如文本密度抽取算法、SimHash 去重、TextRank 摘要这些代码原本和 Iris 的主流程耦合很深。我用几天时间把它们拆成了三个独立函数库开源出来让后来者可以直接复用。这样一来即使 Iris 本身不再运行其中沉淀的部分智慧仍然在 GitHub 上供人使用。第四发布正式停更公告。我写了一段坦诚而中性的说明感谢所有使用者解释为什么停更指出替代工具方向并明确不再接受捐赠。这条公告发出后没有出现我担心的负面声音反而收到了好几封感谢私信说从这个项目里学到了不少东西。6.2 我从 Iris 身上真正学到的东西如果只让我说一条最深的收获不是技术能力而是判断什么不该做的能力。做 Iris 之前我是一个典型的技术乐观主义者觉得任何问题都可以通过写代码解决。做完之后我才明白代码能解决的只是效率问题解决不了定位问题。你需要先弄清楚这个工具在用户生活中扮演什么角色再开始写逻辑。很多个人项目失败不是因为程序员写不出功能而是因为没有人认真想过为什么用户需要这个功能。另一个收获是对习惯价值的重新认知。开发者很容易高估功能价值觉得只要我把摘要做成完美的五句话用户就会留下来。但真正让用户留下来的是这个工具融入日常的方式是否顺滑、打开它是不是一种享受、是否能清晰地感受到时间被节省。功能可以复制习惯很难替代。这也是为什么后来 Iri 停更之后我依然保持每天清晨阅读固定来源的习惯——工具停产了但好习惯留了下来。6.3 如果你也想做类似工具我的三条建议第一先用三周手动流程验证需求再写第一行代码。我在动工之前其实只靠想象确认了需求根本没有模拟每天筛选 20 篇这件任务。如果当时我先用笔记软件手动记录一周的值得读列表我会更快看清这个需求的真正强度可能就不会投入那么大精力了。第二默认拥抱现成服务除非数据主权真的重要。万事皆可自托管的理想主义只适合当成业余爱好不适合当作产品基座。如果你对数据究竟存在哪没有强烈的信仰直接用成熟的托管服务组合起来会更高效。第三开工之前就写好终止条件。哪怕只有一句话当每周维护超过 4 小时且新用户连续两个月为 0 时我允许自己关闭它。把这句话写在 README 最上面。它会变成一道保护你的闸门避免你在沉没成本里越陷越深。最后再多说一句个人的体会吧。Iris 关停那天我把编译好的文件压缩归档删掉服务器上的反向代理配置浏览器里那个熟悉的页面终于变成了 404。我心里没有难过反而觉得很踏实。不是每个项目都该熬到地老天荒一个开源项目的终点往往意味着另一个思路的起点。那两年我最大的收获不是某个算法跑出了 95% 的准确率而是终于学会了一件事敢于开始是热爱敢于停下是清醒。