Contents
1 前言
最近我一直在思考博客结构方面有哪些可以新增的功能,比如文章页下方新增的内容结构提示(参见文章:从内容到结构:一种低成本的博客脉络演化思路)。
不过,还有不少更进一步的能力,比如:自动推荐内容相关的文章、按主题聚合内容,甚至让博客逐渐形成一种“知识网络”式的结构,却不是仅仅依靠简单的“标签”、“分类”,甚至前面提到的“结构提示”就能实现的。
因为这些方案本质上都有一个共同的问题:它们依赖的是人为定义的结构,而不是内容之间基于文本本身形成的关系——标签可以标,但粒度粗;分类可以分,但维度有限;哪怕是我现在在尝试的“结构提示”,本质上也还是一种“人为抽象后的映射”。
这里并不是说标签或分类没有价值。相反,它们本身就是一种已经存在的“结构信号”,在很多场景下依然非常有效。但问题在于:当博客规模逐渐扩大时,如果内容之间的关系完全依赖这些人为结构来维系,就会逐渐暴露出一个很现实的问题——它们无法自动演化。
举个很具体的例子:当我写了一篇新的文章,它在某些方面可能和过去的几篇内容存在关联,但如果我没有刻意打上相同的标签,或者没有把它纳入某个既有结构中,那么这篇文章在博客整体结构里其实就是“孤立”的。
换句话说,博客现在虽然已经“有内容”,但它本身仍然缺少一种能够自动组织内容关系的能力。
而我真正想要的,其实是另一种状态:当一篇文章被写出来之后,它不仅仅只是被展示出来,还能够被博客“自己”组织进整体结构中——知道它和哪些内容接近、属于哪个主题方向、在整个内容体系中大概处于什么位置。
一旦具备了这种能力,很多原本需要手工维护的功能,其实都会自然出现,比如:自动推荐内容相关的文章、基于内容而不是标签进行聚合,甚至后续进一步扩展出的知识图谱、AI 问答等能力。
在很多技术语境中,一旦提到“内容理解”或“语义关系”,往往会直接联想到向量化表示(embedding)——一种表达能力更强、也更加通用的语义建模方式。
但这类方案通常意味着需要引入额外的模型、计算过程以及新的数据存储方式。对于大规模系统而言,这些成本是合理的,但对于一个以内容为核心、规模相对有限的个人博客来说,这并不一定是最合适的起点。
换个角度来看,如果只是为了回答一个更具体的问题——“哪些文章之间更接近”,那么是否一定需要完整的向量语义表示,其实是有待斟酌的。
在本文中,我并没有直接引入 embedding,而是选择了一种更加工程化、也更加轻量的方式:通过对内容进行压缩表达,并在此基础上构建文章之间的关联关系。
需要强调的是,这种方式并不是完全抛弃已有的标签体系,而是在标签之外,引入基于内容本身的表达,使关系的建立不再完全依赖人为结构。
从这个角度看,这套方法更像是在博客现有结构之外,再增加一层基于内容关系的“轻量级知识索引”:它不追求完整的语义理解能力,而是在较低成本下,让内容之间形成一种可以被计算、组织和引用的关联结构。
换句话说,这里并不是在追求“最强的语义表达能力”,而是在尝试构建一套在成本、复杂度与效果之间更平衡的内容关联方案:标签仍然可以作为一种结构信号存在,但不再是唯一依据,内容本身也开始参与到关系计算之中。
那问题就变得很明确了:如何为博客构建一层能够描述内容关系的结构化索引?而这,也正是我这段时间一直在折腾的一个方向——为博客内容增加一层轻量级知识索引(Lightweight Knowledge Index)。
接下来要做的事情,其实可以用一句话概括:
把原本只对人类友好的文章内容,转化成一份对机器也友好的结构化表示。
但问题在于——这种“结构化表示”到底应该如何设计?是继续在现有的标签和分类体系上做增强?还是引入一套全新的数据结构?又或者,干脆借助现成的搜索或向量化方案?
这些看起来都像是可行路径,但如果从“可维护性”和“实际落地成本”的角度去看,它们之间的差异,其实非常大。
2 从“可行路径”到“可落地方案”
如果要为博客引入一层“结构化表示”,到底有哪些现实可行的路径?
最直觉的一种方式,其实是继续在现有的标签和分类体系上做增强:比如细化标签、增加维度,甚至人为地去统一一些命名规则,让内容之间的“关联”更明显一些。
但我很快就发现,这条路本质上只是把问题往后推了一步——它依然依赖人工维护,而且这种“关联”是离散的、粗粒度的:要么有,要么没有,却很难表达“有点相关”“高度相关”这样的细微差别。一旦文章数量上来,这种方式的维护成本和不一致性都会迅速放大。
那如果不继续依赖标签,而是引入一套更完整的内容结构呢?比如,为文章建立更清晰的层级关系,或者手动维护“相关文章”“系列文章”,甚至像我之前尝试的那样,在文章中加入结构提示,让内容在整体上呈现出一种更明确的脉络。
这种方式在局部是有效的——尤其是在写作当下,它确实能帮助理清思路。但问题在于,它依然高度依赖人为设计:哪些内容相关、如何关联、放在哪个层级,本质上还是作者在做判断。一旦规模扩大,或者旧文章需要回溯调整,这种结构就会逐渐演变成一项持续性的负担。换句话说,这条路径只是把“结构能力”建立在人的持续投入之上,而不是让系统本身具备这种能力。
再往前走一步,其实也可以完全换一个思路——不再试图自己定义结构,而是直接借助现成的搜索或向量化能力,让机器去“理解”内容之间的关系。例如,将文章内容通过向量化的方式,将文本映射到语义空间中,再基于距离来判断内容之间的相似程度。从能力上来说,这类方案几乎是完美的:无论是全文检索,还是基于向量的语义相似度计算,都可以很好地解决“内容关联”这个问题,甚至还能带来更自然的搜索体验。
但问题也同样明显:这意味着引入额外的服务、存储和计算过程,甚至需要对现有博客架构做一定程度的改造。对于一个以内容为主、结构相对简单的博客来说,这样的方案虽然强大,但多少有些“过重”了。
走到这里,其实会发现一个很现实的矛盾:一方面,轻量的方案(标签、手工结构)难以表达真正的语义关系;另一方面,能力强的方案(搜索、向量化)又引入了过高的复杂度。而我需要的,其实是一个刚好落在两者之间的解法——它不依赖大量人工维护,可以随着内容自然演化;不需要引入复杂的后端系统,能够在现有博客结构上直接落地; 同时,又能够在一定程度上表达“内容之间的关系”,为后续功能提供基础。
换个角度来看,这个问题其实可以被重新表述为:是否存在一种方式,可以在不改变博客整体架构的前提下,为内容增加一层“可被机器理解”的表示?如果答案是肯定的,那么这层表示本身,就应该是轻量、独立,并且可以被离线生成和消费的。
而在这个思路下,问题其实已经从“选哪种方案”,转变成了另一个更具体的问题:如果把“语义表示”当作一种独立的数据产物,它应该以什么形式存在? 换句话说,我们不再关心“用哪一套系统去实现”,而是需要先确定:是否存在一种足够简单、通用,同时又能被前端直接消费的数据表达方式。
3 结构化表示的形态选择
如果把“语义表示”当作一种独立的数据产物,那么问题就变得非常具体了:这份数据,应该以什么形式存在? 从直觉上看,这似乎只是一个“格式选择”的问题,但如果结合第2章中提到的那些约束条件来看,它其实已经被限定在一个相对狭窄的范围内。
首先,这份数据需要能够被前端直接消费。这意味着,它的表达方式不能依赖复杂的解析过程,也不应该绑定某种特定的运行环境。更理想的情况是:浏览器在需要使用这些数据时,可以像加载普通资源一样获取它,并直接参与到页面逻辑中,例如用于生成推荐内容、增强结构提示,或者作为搜索的数据来源。需要注意的是,这里所说的“直接使用”,并不意味着前端必须一次性加载完整数据,而是指这种数据结构本身应当对前端是天然友好的,可以根据实际场景,以合适的方式被加载和使用。
其次,这份数据应该在构建阶段生成,而不是在用户访问时动态计算。这一点看起来只是一个实现细节,但实际上影响很大。如果选择在请求时计算,那么无论是性能、缓存,还是整体架构复杂度,都会迅速上升;而一旦改为离线生成,这份数据就更像是一种“构建产物”,可以和页面一起被部署、缓存和分发。
再进一步,这份数据需要具备一定的独立性。它不应该依附在现有的数据库结构中,也不应该隐藏在某个后端接口之后,而是应该作为一种“显式存在”的内容——可以被单独访问、调试、替换,甚至在不同环境之间迁移。这一点对于后续的演化其实非常关键。
把这些条件合在一起看,其实已经隐含了一个相当清晰的形态:它应该是一份可以被浏览器直接加载的、在构建阶段生成的、独立存在的数据文件。
到这里为止,我们还没有讨论任何具体的技术选型,但“可选空间”其实已经被压缩得很小了。因为一旦它被定义为“文件”,就意味着需要一种结构化的表达方式;而一旦它需要被前端直接使用,这种表达方式就必须是浏览器天然支持的;如果还要兼顾可读性与调试便利性,那么格式本身也不能过于晦涩。
在这些约束之下,一些选项其实可以很自然地被排除掉:例如自定义的序列化格式,虽然灵活,但缺乏通用性;一些更紧凑的编码方式,虽然在体积上有优势,但会引入额外的解析成本;还有一些依赖特定运行环境的方案,则很难满足“前端直接消费”的前提。
换句话说,当这些限制被逐层叠加之后,问题已经不再是“从很多方案中选择一个”,而更像是:只剩下少数几种不会让人感到别扭的表达方式。
顺着这个思路往下走,一个非常自然的候选就会浮现出来——JSON。它的优势并不在于某一项能力特别突出,而在于与这些约束条件的高度契合:它可以被浏览器直接解析和使用,本身就是为结构化数据设计的表达方式,能够自然地承载嵌套关系,同时在当前前端生态中也几乎是“默认语言”,无论是工具链还是使用习惯,都已经非常成熟。
当然,这并不是说没有其他选择。也可以使用一些同样具备结构表达能力的格式(例如 XML、YAML、CSV,甚至二进制结构等),但要么在浏览器端缺乏直接支持,需要额外的解析过程;要么在可读性或使用习惯上,与当前前端生态存在一定距离;还有一些方案虽然在性能或体积上更有优势,但对于当前这个以“可维护性”和“实现简单”为优先的场景来说,反而显得有些过度。
在这些权衡之下,JSON 并不是唯一的可能,但却是那个几乎不需要额外解释、可以被直接接受的选择——它没有明显短板,也很少引入额外复杂度。换句话说,这里的关键并不在于“我选择了 JSON”,而在于:当“语义表示”被定义为一种离线生成、前端可直接消费的数据文件时,JSON 几乎是一种顺理成章的落点。
而一旦这一点确定下来,问题的重心也就随之发生了转移——接下来真正需要思考的,并不是“用不用 JSON”,而是:在这份 JSON 中,我们应该如何表达一篇文章的语义信息?
也就是说,从这一刻开始,问题已经从“形式选择”,转变为了“数据建模”。而这,才是整个语义表示层设计中,真正需要花力气的部分
4 语义表示的结构设计
在确定使用 JSON 作为“语义表示”的载体之后,问题很快变得具体起来:这份 JSON,到底应该包含哪些信息?
从最直观的角度来看,最容易想到的往往是一些基础字段,比如标题、链接、标签之类。但如果只是这些内容,其实并不需要额外引入一层结构化表示,因为这些信息在现有的博客系统中本身就已经存在。
这也意味着,这一层 JSON 的意义,并不在于重复已有数据,而在于把那些原本存在于内容之中、但系统无法直接利用的信息抽取出来——也就是所谓的“语义”。
如果从最基础的形态去看,一篇文章首先需要能够被唯一识别,并且可以被访问,因此最初的结构往往会非常简单,只包含标题和链接:
{
"title": "...",
"url": "..."
}
但这种结构仍然停留在“描述层面”,它只是告诉系统这是什么,而没有提供任何“理解”的能力。
如果希望往前走一步,通常会先引入的是摘要。因为正文本身过长,不适合直接用于处理,而摘要可以在保留核心信息的同时大幅压缩表达:
{
"title": "...",
"url": "...",
"summary": "..."
}
这里的 summary 并不是正文的替代,而是一个更适合计算与比较的语义入口,它为后续处理提供了一个统一且可控的输入。
在此基础上,如果希望进一步抽象文章的主题,就会自然引入关键词(keywords)。关键词本质上是对内容的一种离散化表达,它比摘要更抽象,但也更容易用于分类与筛选:
{
"title": "...",
"url": "...",
"summary": "...",
"keywords": ["...", "..."]
}
到这里为止,这份结构已经能够比较完整地描述一篇文章本身。但需要注意的是,这一切仍然围绕“单篇文章”展开,它回答的是“这篇文章是什么”,而不是“它与其他内容之间是什么关系”。
一旦开始考虑“文章之间的关系”,一个更基础的问题就不可避免地出现了:在结构化数据中,应该如何稳定地指向一篇文章?
最直接的方式是使用 URL,但 URL 本质上是访问路径,它将“身份”和“位置”绑定在了一起,一旦路径发生变化,整个关系体系就会受到影响。另一种方式是使用系统内部的 id,比如数据库中的主键,但这种方式虽然稳定,却依赖具体实现,而且缺乏语义可读性。
在这种情况下,引入一个独立的标识字段 id 就变得合理起来。它不依赖访问路径,也不依赖数据库结构,而只是作为一个稳定的引用单位存在:
{
"id": "...",
"title": "...",
"url": "...",
"summary": "...",
"keywords": ["...", "..."]
}
当这个 id 出现之后,结构的性质开始发生变化。文章不再只是一个被描述的对象,而是一个可以被引用的节点——也正是从这一刻开始,“关系”才真正具备了被表达的可能。
在这个基础上,如果进一步引入一种用于描述“关联”的机制,结构就会自然演化为:
{
"id": "current-article",
"title": "...",
"url": "...",
"summary": "...",
"keywords": ["...", "..."],
"related": [
{ "id": "article-1", "score": 0.91 },
{ "id": "article-2", "score": 0.84 },
{ "id": "article-3", "score": 0.76 }
]
}
这里的 related 并不是简单的“相关文章集合”,而是一组从当前文章出发的连接。每一项都指向另一篇文章,并通过 score 表达关联的强弱。
当这种结构出现之后,视角实际上已经发生了一次跃迁:文章不再是孤立的内容单元,而是网络中的节点,而 related 则定义了节点之间的连接关系。
从整体来看,这样的结构隐含了一张内容之间的关系网络。只不过,这张网络并不是在运行时动态计算出来的,而是在构建阶段就已经被确定下来。在实际使用时,每一篇文章只需要读取与自身相关的那一部分连接,而不需要重新计算整个关系结构。
换句话说,这里并不是在访问时“计算关联”,而是在生成阶段“固化关联”。
至于这些连接的权重是如何得到的,其实并不属于这一层结构需要解决的问题。对于当前这一层来说,更重要的并不是“分数如何计算”,而是这种表示方式本身,已经提供了一种可以直接用于排序和推荐的基础。
从更整体的角度来看,这一层结构中的字段可以自然分为两类:一类来自博客系统本身,是已有的结构化数据,例如标题和链接;另一类则来自对内容的进一步提取或计算,例如摘要、关键词,以及文章之间的关联关系。前者提供基础的信息骨架,后者补充语义能力,两者共同构成了这一层语义表示的完整形态。
5 语义关系的生成过程
当这种关系网络被明确下来之后,下一个问题就变得不可避免:这些关系,是如何被构建出来的?
如果回到最初的输入,本质上其实只有两样东西:文章的结构化信息,以及文章本身的内容。前者本身已经是结构化的,可以直接使用;而后者,则是整个语义层真正的来源。这也意味着,第4章中那份 JSON,并不是“直接生成”的,而是经历了一次从内容到结构的转换过程。
从数据来源的角度来看,这个过程并不复杂。像标题、链接这类字段,本身就可以直接从博客系统中获取;而真正需要被处理的,只有一部分——文章的正文内容。问题也正是从这里开始的。正文是一段天然为人类阅读设计的文本,它的信息是连续的、非结构化的,如果直接用于计算,无论是效率还是效果,都很难令人满意。
因此,在进入关系构建之前,首先需要做的一步,是将这段内容转化为一种更适合处理的表达形式。最直接的方式,是对正文进行压缩,提取出一段能够代表核心内容的摘要。这个过程并不追求完整复现,而是尽可能保留“语义密度”,让文本变得更短、更集中,从而为后续处理提供一个稳定的输入。在摘要的基础上,可以进一步抽象出关键词。相比摘要这种连续表达,关键词是一种离散的语义表示,它将内容拆解为若干可标记的主题单元,更适合用于筛选和初步判断。
当有了这两种表达之后,关系的计算才真正有了基础。如果从最直观的方式来看,两篇文章之间是否相关,可以通过关键词的重叠来判断:共享的关键词越多,通常意味着主题越接近。这种方式实现简单,也具有较强的可解释性,但它的局限同样明显——关键词是离散的,它能够表达“是否相关”,却很难表达“相关程度”。
因此,如果希望关系更加细致,就需要引入更连续的表达方式。一个常见的做法,是直接基于文本计算相似度,例如通过词频或权重,将文本转化为可比较的数值表示。这种方式相比关键词,会更稳定一些,也更容易体现出“相似度”的差异。
再往前一步,则可以把文本映射到一个统一的语义空间中。在这个空间里,文本之间的距离本身就可以用来表示它们之间的关联程度。相比前面的方式,这种方法不再依赖具体词语是否一致,而是更关注整体语义的接近程度。但无论具体采用哪种方式,本质上都在做同一件事情:把“内容之间是否相关”这个原本依赖人类判断的问题,转化为一个可以被计算的过程。
当这个过程完成之后,每一篇文章都可以得到一组“与自己最接近的其他文章”,以及对应的关联强度。这些结果,正是第4章中 related 字段的来源——那些带有权重的引用关系,并不是人为定义的,而是通过计算得到的。
需要注意的是,这一过程通常发生在离线阶段。也就是说,它并不会在用户访问页面时实时执行,而是在构建阶段一次性完成。当新的文章加入,或者旧的内容发生变化时,再重新生成一份新的结果。
这种方式带来的一个直接结果是,运行时的系统可以被大幅简化:前端不需要参与任何计算,只需要读取已经准备好的数据,就可以完成排序、推荐等操作。从这个角度回看,第4章中的那份 JSON,其实并不是一个“中间结构”,而是整个流程的最终产物。它既承载了单篇文章的语义信息,也固化了文章之间的关系。
也正因为如此,这里的“语义表示层”,本质上并不是引入一个复杂的在线系统,而是增加了一次离线处理过程:它把原本分散在内容中的信息提取出来,转化为结构化的数据,并在构建阶段完成所有需要的计算。从这个角度来看,这个过程其实是在为博客引入一层“离线计算能力”。
当这一过程完成之后,博客本身的形态也随之发生了一点变化:它不再只是一个按时间或分类组织的内容集合,而是多出了一层基于语义关系的结构。而这层结构,正是后续各种能力得以实现的基础。
但需要注意的是,这里所描述的仍然只是“计算结果本身”,而不是这一过程在系统中的实际运行方式。也就是说,上面这些语义信息是如何被一步步构建出来、如何被组织为一条完整的处理链路,本身就构成了接下来需要解决的问题。
6 语义索引的构建流程
6.1 整体流程概览
在前面的章节中,我们已经确定了语义表示的最终形态:一份基于 JSON 的离线索引文件。但从设计到实现之间,还需要一个明确的执行过程来将其真正构建出来。在我的实现中,这一过程被收敛为一个独立运行的脚本,这个脚本的作用非常直接:从 WordPress 中获取文章数据,经过处理之后,生成最终的 JSON 语义索引文件。
从整体来看,这一流程可以抽象为四个连续的阶段:
- 第一步,是从 WordPress 获取文章数据;
- 第二步,是对获取的文章数据进行处理与语义转换;
- 第三步,是基于语义信息构建文章之间的关联关系;
- 第四步,是将最终结果输出为统一的 JSON 结构。
这几个阶段看起来非常线性,但它的关键点在于:每一步都不依赖 WordPress 本身的运行机制,而是完全在脚本内部完成数据的接管与重建。换句话说,WordPress 在这里仅仅扮演“数据源”的角色,而不再参与任何逻辑计算。
整个过程并不依赖于博客的运行时环境,而是在离线状态下完成一次性计算,因此,它更像是一条独立的数据处理流水线,而不是系统运行的一部分。在这个流水线中,不同阶段承担不同职责,并共同完成从“内容”到“结构化语义”的转换。
如果从工程视角再往下拆一步,可以发现这条流水线的本质,其实是在做一件事:把分散在 CMS 中的内容数据,重新组织成一个面向“关系计算”的数据结构。原本用于展示的字段(标题、正文、标签),在这里被重新组合成可以参与计算的输入;而原本不存在的字段(摘要、关键词、关联关系),则是在这一过程中被逐步“生成出来”的。
也正因为如此,这个脚本本身并不是一个简单的数据搬运工具,而更像是一个“构建器”:它负责把 WordPress 中相对松散的内容结构,转换为一份具有稳定结构和明确语义边界的数据产物。
从执行方式上看,这个构建过程通常是一次性完成的批处理任务。当文章发生新增或更新时,只需要重新运行脚本,就可以重新生成一份完整的索引文件。因此,在运行模型上,它更接近“构建阶段”而不是“服务阶段”,这一点也决定了整个系统的复杂度可以被控制在一个非常低的水平。
6.2 从 WordPress 获取文章内容
在整个流程中,第一步是从 WordPress 中获取完整的文章数据。这一步看似简单,但实际上决定了后续所有处理的输入边界,因此需要优先确定一个稳定且可控的数据获取方式。
在可选方案中,最直观的选择其实是 RSS,但在这里并不适合用于这一层的实现。原因很简单:RSS 的设计目标是内容分发,而不是数据处理。它通常只包含摘要信息,字段也相对固定,很难获取完整正文,更无法灵活控制返回的数据结构。
相比之下,WordPress 提供的 REST API 则更接近“数据接口”的定位。通过它可以直接获取文章的完整信息,包括标题、链接、正文以及标签等字段,并且支持分页、筛选等能力,更适合用作离线处理流程的输入来源。
在实际实现中,使用的接口是:
/wp-json/wp/v2/posts
这个接口默认会返回文章列表,每一条记录中都包含了一篇文章的主要字段。其中比较关键的包括:
id:文章在系统中的唯一标识title.rendered:文章标题link:文章访问地址content.rendered:文章正文(HTML 格式)tags:标签 ID 列表
需要注意的是,这个接口默认是分页返回的。也就是说,单次请求并不能获取全部文章,而是需要通过分页参数逐步遍历。
在请求中可以通过 per_page 参数控制每页数量(最大为 100),并通过 page 参数指定页码。因此,一个完整的数据获取过程,通常表现为一个分页循环:从第一页开始,依次请求后续页面,直到所有文章被获取完毕。
在这个过程中,有两个细节需要注意:首先是如何判断“是否已经获取完全部数据”。一种直接的方式,是根据返回结果是否为空来终止循环;而更稳妥的做法,是利用响应头中的 X-WP-TotalPages 字段,提前获取总页数,从而明确遍历范围;其次是获取到的正文内容通常是 HTML 格式,这一点在后续处理中需要额外关注。在当前阶段,可以先将其作为原始数据保留,在后续步骤中再进行清洗和提取。
通过这样的方式,脚本可以一次性获取博客中的全部文章数据,并将其作为后续语义处理的输入。到这里为止,整个流程仍然只是“数据获取”,尚未涉及任何语义层面的计算,但它已经为后续所有步骤提供了完整且稳定的数据基础。
6.3 对获取的文章数据进行处理与语义转换
在获取到 WordPress 中的原始文章数据之后,接下来的步骤,是将这些内容整理为一套统一的结构,并在此基础上生成能够参与后续计算的语义信息。
这一阶段的核心,并不是“增加数据”,而是对已有内容进行重新组织,使其从“用于展示的内容”,转化为“可用于计算的输入”。
从结构上看,首先需要完成的是字段的对齐。通过 REST API 获取到的数据中,标题、链接、正文以及标签等信息已经存在,但它们的组织方式仍然是面向页面展示的。因此,需要将这些字段映射为一套更加稳定的内部结构。例如,将 title.rendered 转换为统一的 title 字段,将 link 作为访问地址,将 content.rendered 作为原始内容保留。
在此基础上,正文内容需要进行一定程度的处理。由于 REST API 返回的正文通常是 HTML 格式,因此在后续计算之前,需要将其转换为更适合处理的纯文本形式。这个过程本身并不复杂,其目标也不是“完美还原内容”,而是去除结构标签,使文本能够作为统一的输入参与后续步骤。
完成基础清洗之后,下一步是生成文章的简化表达。最直接的方式,是从正文中提取一段能够代表主要内容的摘要。这一过程可以采用非常简单的策略,例如截取前一部分文本,或者进行轻量的压缩处理。关键不在于精确,而在于提供一个信息密度更高、长度更可控的语义载体。
与摘要相比,关键词则提供了另一种形式的表达。在这一实现中,标签本身就是一个天然的起点,可以直接作为关键词使用。同时,也可以根据需要,从正文或摘要中补充提取部分词语,用于增强表达能力。
经过这些处理之后,每一篇文章都不再只是原始内容的集合,而是被转化为一条具有统一结构的数据记录。它既保留了基本的访问信息,又具备了可以参与计算的语义字段,从而为下一步的关联关系构建提供了基础。
6.4 基于语义信息构建文章之间的关联关系
在上一节中,每一篇文章已经被整理为一条具有统一结构的数据记录,并具备了可以参与计算的语义信息。在这个基础上,下一步要解决的问题就变得非常明确:这些文章之间,究竟是如何相互关联的?
如果说前面的处理是在回答“每一篇文章是什么”,那么这一阶段要做的,则是回答“它与其他内容之间的关系是什么”。
从实现角度来看,这一步的核心思路其实并不复杂:将每一篇文章与其他所有文章进行对比,并根据语义信息计算它们之间的相似程度。这里的“语义信息”,主要来自前一阶段生成的摘要与关键词,它们分别提供了连续文本和离散特征两种不同形式的表达。
在具体处理时,可以将整个过程理解为一个遍历与比较的组合:以某一篇文章作为当前对象,将其与集合中的其他文章逐一进行比对,计算出一个表示“相关程度”的数值。这个数值并不需要具备绝对精度,它的意义更多在于提供一种可排序的依据,使得“更相关”的内容能够被排在前面。
当所有比较完成之后,就可以为当前文章生成一组候选结果。接下来,只需要按照相关程度对这些结果进行排序,并选取其中最靠前的一部分,即可得到一组稳定的关联内容。
也正是在这一过程中,前面结构中定义的 related 字段被填充起来。对于每一篇文章而言,它不再只是一个独立的节点,而是通过这些关联关系,与其他内容形成了一张隐含的连接网络。
需要注意的是,这里并没有追求“完美的语义理解”。在当前阶段,相关性的计算更偏向于一种工程化的近似:通过可获得的信息,构建出一组足够合理的关联结果。相比复杂的模型或算法,这种方式更简单,也更容易控制,同时已经能够满足绝大多数内容推荐的需求。
从整体上看,这一阶段完成的是一次从“单点描述”到“关系网络”的跃迁。前一节中构建的是一组结构化的数据,而在这里,这些数据被进一步组织为一个具有关联性的整体。
而在完成这一层构建之后,剩下的工作就变得相对直接了:将这些已经包含完整关系信息的数据,输出为最终统一的 JSON 结构。
6.5 将最终结果输出为统一的 JSON 结构
在上一节中,文章之间的关联关系已经被计算出来,并被组织为结构化的数据记录。到这一阶段为止,所有与语义相关的处理都已经完成,剩下的问题则变得相对直接:如何将这些结果以一种可被实际使用的形式保存下来。
从处理过程来看,前面的所有计算都是围绕“单篇文章”展开的。每一篇文章在处理完成之后,都会形成一条包含基础信息与关联关系的数据记录。但对于实际使用而言,这些分散的结果需要被进一步汇总,形成一个统一的数据集合。
在当前的实现中,这一步的处理方式非常直接:将所有文章的结构化结果整理为一个数组,并统一输出为一份 JSON 文件。
之所以选择这种形式,而不是继续依赖接口或引入额外的存储系统,原因在于这一层数据本身具有非常明确的使用场景。它并不是一个需要频繁更新或动态查询的数据源,而是在构建阶段一次性生成,并在运行时被直接读取。因此,将其固化为一个静态文件,反而是最简单且稳定的方案。
从系统角度来看,这份 JSON 文件可以被理解为整个语义表示层的“最终产物”。它既包含了每一篇文章的基础信息,也包含了文章之间的关联关系,从而形成了一份完整的内容索引。
在实际使用中,前端只需要加载这份文件,并根据当前文章的标识查找对应的数据记录,即可获取相关推荐等信息,而无需参与任何额外的计算过程。也正因为如此,前面所有复杂的处理步骤,最终都被收敛为一次简单的数据读取。
至此,从数据获取、语义处理,到关系构建,再到结果输出,整个流程已经形成了一个完整的闭环。而这一闭环的最终形态,就是这份可以被直接消费的 JSON 结构。
7 小结:从结构设计到可运行的语义索引
到这里为止,这个语义索引的设计已经形成了一个完整的闭环:从 WordPress 获取文章数据的思路,到结构整理、语义生成,再到关联关系的构建方式,最终收敛为一份可以被直接消费的 JSON 结构。
从结构上看,这一套设计已经具备了一个非常关键的能力:它不再只是“文章的集合”,而是一个可以描述文章之间关系的索引结构。每一篇文章都拥有自己的语义信息,并通过 related 字段与其他内容建立了连接,从而在整体上形成了一张隐含的内容网络。
在这个过程中,我们刻意控制了复杂度,保留了最核心的结构与处理逻辑,使整套方案能够以较低成本落地。这也意味着,它并不是一个依赖复杂系统的完整工程方案,而是一种可以被实现的基础形态。
但当这个结构真正进入实现阶段时,一些更加现实的问题也会随之出现,而这些问题,往往并不会在设计阶段被优先考虑。
例如:
- 数据获取应该如何组织?如何稳定地遍历全部文章?
- 语义信息的生成具体应该如何处理?采用什么样的策略更合适?
- 文章之间的相关性,应该如何计算与排序?
- 最终生成的 JSON 文件,应该如何被博客系统使用?
这些问题并不会改变这套结构本身的合理性,但它们决定了这套方案在真实环境中的表现方式。换句话说,到目前为止,我们完成的是一个“结构层面的确定性设计”;而接下来需要解决的,则是“如何将这套设计转化为一段可以稳定运行的代码”。也正是在这个分界点上,问题的重心开始发生变化:从“如何定义语义结构”,逐渐转向“如何在具体环境中实现这套结构”。
这些内容,将在下篇中继续展开。