一次搜索为什么会变成一个系统问题
这次的起点很简单:我希望自动从小红书上找到更多值得看的开源项目。最初看起来,这像是一个搜索问题,输入几个关键词,拿回一些笔记,再从里面找 GitHub 链接就够了。真正动手以后,我很快发现,搜索只是把门打开,后面还有一整套判断没有被处理。
有的项目写在正文里,链接很清楚;有的只出现在配图的一行小字里;还有的项目藏在评论中,正文只说“这个工具很好用”。更麻烦的是,作者可能只写“某某作者的某某项目”,没有直接贴仓库地址。一张图片也不一定只介绍一个项目,做成清单的图片经常一次放进好几个名字。如果仍然把一篇笔记当成一个项目,结果从第一步就已经丢失了结构。
我一开始以为这是把搜索范围扩大一点就能解决的事,后来发现,真正要解决的是“线索如何变成可以继续判断的对象”。发现开源项目不是一次命中,而是把分散在不同载体里的证据,整理成可以追踪、去重和复查的候选记录。
先盘点已有能力,而不是重新造一套
当时我先看现有的小红书读取能力能做到哪里。原来的搜索命令虽然能工作,但每次启动都会下载很大的浏览器环境,实际使用成本太高。另一个已经存在的平台读取后端可以返回搜索结果,实测一次能拿到约 30 条笔记,这说明问题不在于“完全没有入口”,而在于怎样把入口用对。
这里最容易犯的错误,是看到旧入口不够顺手,就从登录、搜索、详情解析到图片下载全部重写一遍。这样做表面上掌控力更强,实际会把已经处理过的登录态、页面结构和异常情况重新踩一遍。我选择优先更新已有的平台读取能力,是因为它已经知道怎样和小红书对话,新的工作应该集中在项目线索的抽取、校验和记录上。
这件事让我重新意识到,自动化的第一步不是写新代码,而是确认系统里已经有什么。旧能力可能不完整,却常常保存着最贵的经验:怎样避开登录墙,怎样取得笔记详情,怎样处理页面变化。复用成熟入口的价值,不只是少写代码,更是把已经验证过的风险一起继承下来。
一篇笔记要拆成三种证据
我把笔记看成一个容器,而不是一个项目。每篇笔记至少要经过正文、图片和评论 3 条路线。正文路线负责提取明确的 GitHub 地址,也要识别“作者名加项目名”这类没有链接的表达。图片路线负责逐张取图,再做 OCR;不能只看首图,因为后面的图可能才是项目清单。评论路线则要单独读取,不能把评论当成正文的附属文本,因为真正有价值的补充链接经常在讨论里出现。
三条路线的结果不能直接混在一起。每个候选至少要保留它来自哪篇笔记、哪位作者、哪条内容、哪张图片或哪条评论,以及发现时间。这样做不是为了让记录显得完整,而是为了让后续判断有依据。一个项目后来被判定为重复时,我需要知道两个名字为什么被认为是同一个;一个仓库失效时,我也需要回到原始线索,判断是链接过期,还是当初识别错了。
图片是这条链路里最容易被低估的地方。OCR 不是“把图片变成文字”这么简单,它还要面对多项目并列、换行打断、中文说明和英文仓库名混排等情况。所以抽取时不能只返回一段文本,而要把一张图拆成多个可能的项目片段,再分别进入后面的校验。若不这样做,会导致一张项目合集图最后只留下第一个名字,其余项目连进入候选池的机会都没有。
从作者加项目名到准确仓库
没有直接链接的线索,不能凭相似名字直接入池。比如正文写了作者和项目名,我会先把它作为待校验线索,再去做精确的仓库匹配。匹配时至少要看作者是否对应、项目名是否一致、仓库描述是否符合笔记语境。只命中一个相似字符串,不足以证明是同一个项目。
我判断这一步必须保守,是因为候选池的错误会产生复利。一个错误仓库一旦进入后续流程,可能被继续做介绍、生成视频,甚至被推荐给别人。到那时再追溯,已经不只是删掉一行记录,而是要重新检查所有下游产物。宁可把不确定的线索留在待校验区,也不要用一个看起来相似的仓库替它完成判断。
这里也要区分“发现”和“合格”。发现层可以容纳作者名、项目名、截图文字和评论链接,只要来源清楚就有保留价值;合格项目池则要经过仓库确认、项目去重和基本可用性判断。把两者混成一张表,会让人误以为所有被发现的项目都已经值得使用,也会让后续无法解释为什么某些线索被暂时搁置。
专用候选池解决的是记忆问题
这次我没有把小红书发现的结果直接塞进总候选池,而是单独维护一个来源候选池。它保存的是“从小红书发现了什么”,而总候选池保存的是“哪些项目已经达到下一步处理条件”。两者之间通过去重和校验连接起来,而不是互相替代。
这样设计还有一个实际好处:可以快速拆解一个博主,从主页进入全部笔记,再把每篇笔记的正文、图片和评论统一处理。博主只是一个入口,不应成为记录的唯一层级。若只记“这个博主推荐过某项目”,以后很难知道它来自哪篇笔记、哪张图,也无法判断同一项目是否在别的笔记里被重复提到。
去重也不能只靠项目名称。优先使用仓库的稳定标识,再结合作者、项目名和来源做交叉判断。相同仓库可能被多个博主提到,这不是多余记录,而是多个来源证据;真正应该合并的是项目实体,保留的却应当是各自的发现来源。去重不是把历史擦掉,而是在合并项目的同时保留它被发现和验证的路径。
如果只搜正文,后面会失去什么
错误捷径很明确:只搜索正文里的 GitHub 链接,再把一篇笔记当成一个项目。它可能很快产生一份看起来干净的列表,却会系统性漏掉图片项目、评论补充和作者加项目名的表达。一旦拿这份列表去做下一步推荐,就会把“没有被正文写出来”误判成“不存在”。
如果不处理来源和时间,后面还会出现另一种问题:同一个项目多次出现时,没人知道哪次是最早发现,哪个来源提供了更完整的说明;如果不保留原始笔记和作者,遇到仓库名变化或识别争议时,就只能靠记忆补证据。一个候选池是否可靠,不看它收集了多少名字,而看每个名字能不能回到原始线索。
这件事表面上是在找项目,其实是在设计一条证据链。搜索结果只是入口,OCR 只是转换,评论读取只是补充;真正的工作,是让每一步都留下下一步可以使用的依据。一个流程成熟的标志,不是它能跑通一次,而是下次遇到相似内容时,不需要重新争论“这个项目到底从哪里来的”。
下次我会怎么做
下次再做类似发现,我会先确认已有平台读取入口和当前登录状态,再建立本次任务的来源候选池。随后按正文、逐图 OCR、评论 3 路并行抽取,遇到作者加项目名的写法就先记录原文,不急着猜仓库。每一条候选都带上笔记、作者、内容位置和发现时间,图片中出现多个项目时逐个拆开。
抽取完成后,我会先做来源层去重,再做仓库层校验,最后把结果分到待校验线索和合格项目池。发现层允许不完整,合格层必须有足够证据;同一项目可以合并实体,但不能抹掉不同笔记和评论提供的来源。
我更愿意把这套方法看成一种工作习惯,而不是一个搜索技巧。面对任何内容平台,只要信息可能分散在正文、图片和讨论区,就先问 3 个问题:线索藏在哪些载体里?每条线索能否回到原处?不确定的判断有没有被单独留下?这样,下一次扩大搜索范围时,增加的不是一堆无法解释的名字,而是一批可以继续核验的项目。