怎么让 AI 引擎认出"你是谁"?给网页加一张机器能读的名片

你在知乎有主页,在 GitHub 有资料页,在自建站有关于页。你都知道这是同一个人——都是"纪优"。但 AI 引擎(秘塔、Kimi、豆包、Perplexity)不知道。它看到的是三个不同网站上、三个独立的页面,没有任何信息明确告诉它"这三个都是同一个人"。

实体消歧(Entity Disambiguation)就是解决这个问题的:给 AI 一张机器能读懂的"身份证",让它把散落在各处的"纪优"拼成同一个人。拼对了,AI 会把你当作可信来源;拼不对,你在 AI 眼里就是"身份不明的人",不会在回答里引用你。

本文记录一次真实的改造:把一个自建站的网页"名片"从"每页各写一份、互不相认"升级成"用身份证号串起来的整体"。改造对象是一个自动生成网页的小脚本,但原理对所有网站都成立——你哪怕用 WordPress、用知乎专栏,理解了原理一样能用。

先搞懂:AI 怎么读你的网页

人看网页,看到的是排版好的文字和图片。AI 看网页,看的是网页背后的一段段"机器名片"。这段名片叫 JSON-LD,藏在一个叫 <script> 的标签里,人眼通常看不到,但 AI 和搜索引擎专门去读它。

JSON-LD 长这样——本质上就是一段用花括号 {} 包起来的、按"键: 值"格式写的说明:

{
  "@type": "Person",
  "name": "纪优",
  "url": "https://www.jiyou.site"
}

读法:@type 说"这是一张人(Person)的名片",name 说"名字叫纪优",url 说"主页网址是这个"。AI 读懂了这张名片,就知道这个网页讲的是谁。

问题来了。你的每篇文章页、首页、知乎主页、GitHub 资料页,各自都塞了一张这样的名片。但每张名片都是独立写的,名片和名片之间没有任何关联标记。AI 看到四张名片,名字都叫"纪优",但它没法确认这四张是不是同一个人——就像你拿到四张身份证,照片和名字都对得上,但身份证号各不相同,你不敢断定这是同一个人。

实体消歧要做的,就是给所有这些名片发同一个"身份证号",让 AI 一看就知道是同一个人。

三个字段,就是身份证的三行信息

实体消歧靠三个字段配合。先看它们各自在名片里对应什么,再看怎么串起来。

第一行:@id —— 身份证号。

"@id": "https://www.jiyou.site/#person"

这是给"纪优"这个实体发的全局唯一身份证号。注意它长成一个网址——这是 Schema.org(一个全球通用的网页名片标准)规定的格式:身份证号必须是一个网址,网址后面加个 #person 表示"这个网址指向的那个人"。无论这个身份证号出现在文章页、首页还是别的地方,AI 都知道它指向的是同一个"纪优"。这是拼图的底座。

第二行:sameAs —— 多平台身份锚点。

"sameAs": [
  "https://www.zhihu.com/people/yeah-98-35",
  "https://github.com/jaykaai"
]

sameAs 是"等同于"的意思。这两行告诉 AI:"这个纪优在知乎是 yeah-98-35,在 GitHub 是 jaykaai。" AI 会顺着这两个网址去验证——打开知乎主页看看,打开 GitHub 看看,确认是不是同一个人。这是拼图的胶水,跨平台验证通过,可信度上升。

第三行:knowsAbout —— 专业领域。

"knowsAbout": ["生成式引擎优化", "AI 智能体应用", "结构化数据", "搜索引擎优化"]

knowsAbout 说"这个人懂哪些领域"。这是帮 AI 判断的:如果有人问 AI"GEO 是什么",AI 会优先去找一个 knowsAbout 里有"生成式引擎优化"的人来回答,而不是找一个名片上写"美食博主"的人。这是拼图的标签。

三个字段串起来,就是一张完整的实体图。下面这张图就是改造完之后的整体结构,每个框是一张名片,箭头就是"指向"——箭头上的字对应名片里的那一行字段:

内容实体 人物实体 机构实体 ARTICLE 文章名片 WEBSITE 网站名片 PERSON 人物名片 · 纪优 @id …/#person sameAs 知乎 · GitHub knowsAbout 4 个领域 ORGANIZATION 机构名片 · 纪优 @id …/#organization founder → #person author author publisher worksFor founder 箭头 = "指向":名片里用 @id 引用对方,不重写一份

上图:文章名片和网站名片都不自己定义作者,用 author 指向"人物名片";人物与机构互相指(就职于 / 创始人)形成闭环。AI 从任一节点进入,沿箭头都能找到同一份完整的"纪优"名片。

改造前:每张名片各写各的,互不相认

改造前,脚本给每篇文章生成的名片是这样:文章名片里把作者写成一个完整的内联对象,另外再单独写一张人物名片。每篇文章都重复一遍,首页又是一份。

// 改造前 · 文章名片里的作者字段
"author": {
  "@type": "Person",
  "name": "纪优",
  "url": "https://www.jiyou.site"
}

问题在哪?这段作者信息是"内联"的——意思是它直接写在文章名片里,和别的文章名片里的作者信息没有关联。四篇文章就有四份各自独立的"纪优"作者信息,谁也不认谁。AI 没法确认这四份是同一个人。

还有一个隐藏问题。人物名片里有一行声明专业领域:

// 改造前 · 人物名片的专业领域字段
"knowsAbout": ["GEO", "自足段落", "答案优先"]

这里的值跟着每篇文章的标签(tag)变。讲 GEO 的文章,名片说"纪优懂 GEO";讲段落写作的文章,名片说"纪优懂自足段落"。AI 拼不出一个稳定的"纪优研究什么"的画像——这个人怎么每篇文章都在换专业?

改造第一步:把身份证号集中管理

改造的第一步,是把所有身份信息集中到一个地方管理,这样改一次,全站同时生效。在脚本里,这就是定义几个"常量"——可以理解为"全局变量",定义一次,下面所有地方都用这个值:

// 身份证号(用网址形式,这是 Schema.org 规定的)
const PERSON_ID = `${SITE_URL}/#person`;           // 人物的身份证号
const ORG_ID = `${SITE_URL}/#organization`;        // 机构的身份证号
const WEBSITE_ID = `${SITE_URL}/#website`;         // 网站的身份证号

// 多平台锚点:只填你真实拥有的平台账号
const AUTHOR_SAMEAS = [
  'https://www.zhihu.com/people/yeah-98-35',   // 知乎
  'https://github.com/jaykaai',                  // GitHub
];

// 稳定专业领域:不随文章变,是"纪优"作为研究者的固定身份
const AUTHOR_KNOWS_ABOUT = [
  '生成式引擎优化',
  'AI 智能体应用',
  '结构化数据',
  '搜索引擎优化',
];

这几行要说明三点。

身份证号为什么是网址?因为 Schema.org 标准规定 @id 必须是一个网址,网址后面跟 #person 表示"这个网址指向的那个人"。这是全球通用的写法,不是随便编的。

AUTHOR_SAMEAS 只填了知乎和 GitHub 两个——因为这是真实拥有的账号。sameAs 不是社交链接列表,是身份验证锚点,AI 会顺着去验证。填一个打不开的页面比不填更糟(下一节细说)。

AUTHOR_KNOWS_ABOUT 写的是"纪优作为研究者的稳定身份",不是某篇文章的主题。无论写什么文章,"纪优"研究的就是这四个领域。这修掉了改造前"专业领域跟着文章标签变"的问题。

改造第二步:文章名片改用"指向",不再重写

第二步改文章名片的生成。核心变化就一行:文章名片的 author 字段,从"重新写一份完整作者信息"改成"指向那张人物名片"。

// 改造前 · 文章名片里重写一份作者
"author": { "@type": "Person", "name": "纪优", "url": "…" }

// 改造后 · 文章名片里只写"作者是 #person 那个人"
"author": { "@id": "https://www.jiyou.site/#person" }

对比一下:值从一整个对象 { "@type": "Person", "name": ..., "url": ... },变成了一行 { "@id": "…/#person" }。从"在这里重新定义一遍作者"变成"指向实体图里已有的那个作者"。这就是实体消歧的核心动作——用一个身份证号引用,替代一份重复的内联定义。

对应到代码里,文章名片长这样(Article 块):

const articleJsonLd = JSON.stringify({
  '@context': 'https://schema.org',              // 声明用 Schema.org 标准
  '@id': `${SITE_URL}/articles/${article.slug}.html#article`,  // 这篇文章自己的身份证号
  '@type': 'Article',                             // 这是一张文章名片
  headline: article.title,                        // 标题
  description: article.description || '',         // 摘要
  author: { '@id': PERSON_ID },        // ← 作者:指向人物名片,不重写
  publisher: { '@id': ORG_ID },        // ← 发布者:指向机构名片
  datePublished: article.date,
  dateModified: article.date,
  inLanguage: 'zh-CN',                            // 中文
  mainEntityOfPage: `${SITE_URL}/articles/${article.slug}.html`,
}, null, 2);

然后是人物名片和机构名片,两块互相指,形成闭环:

// 人物名片
const personJsonLd = JSON.stringify({
  '@context': 'https://schema.org',
  '@id': PERSON_ID,                    // ← 身份证号
  '@type': 'Person',
  name: AUTHOR_NAME,
  url: AUTHOR_URL,
  description: AUTHOR_DESCRIPTION,
  sameAs: AUTHOR_SAMEAS,               // ← 多平台锚点(直接用数组,别再包[])
  knowsAbout: AUTHOR_KNOWS_ABOUT,     // ← 稳定专业领域
  worksFor: { '@id': ORG_ID },        // ← 就职于:指向机构名片
}, null, 2);

// 机构名片
const orgJsonLd = JSON.stringify({
  '@context': 'https://schema.org',
  '@id': ORG_ID,                        // ← 身份证号
  '@type': 'Organization',
  name: AUTHOR_NAME,
  url: AUTHOR_URL,
  sameAs: AUTHOR_SAMEAS,
  founder: { '@id': PERSON_ID },      // ← 创始人:指回人物名片,闭环
}, null, 2);

注意 sameAs: AUTHOR_SAMEAS 这行——AUTHOR_SAMEAS 已经是数组了,直接用,不能再加方括号包成 sameAs: [AUTHOR_SAMEAS],那样会变成"数组套数组"。这是改造前的一个 Bug,改造后直接用常量就对了。

构建后,每篇文章的网页里就嵌了三张名片(文章、人物、机构),靠身份证号互相串。AI 读到文章名片的 author: {"@id": "…/#person"} 时,会沿着这个号去找人物名片——在同页能找到。在首页也能找到(首页也有一张同号的人物名片)。两个页面的名片指向同一个身份证号,AI 据此确认:这是同一个人。

改造第三步:首页也接进实体图

首页之前只有一张网站名片,作者信息内联在里面,和其他页面互不相认。改造后首页也用身份证号引用,并补上独立的人物、机构名片。但首页这段代码是手写模板,有个坑:JS 的数组插进字符串时,会被悄悄转成"用逗号拼起来的字符串",JSON 就坏了。

// 改造前 · 数组被插值成字符串(坏的)
"sameAs": ["${AUTHOR_SAMEAS}"]
// 实际输出变成:"sameAs": ["https://…,https://…"]  ← 这是一个带逗号的字符串,不是数组

// 改造后 · 用 JSON.stringify 转成合法的 JSON 数组
"sameAs": ${JSON.stringify(AUTHOR_SAMEAS)}
// 实际输出:"sameAs": ["https://…","https://…"]  ← 合法数组

JSON.stringify() 的作用就是把一个 JS 数组转成"合法的 JSON 文本"。["a","b"] 这样的文本能直接嵌进 JSON 对象里,语法正确。这是改造前首页的核心 Bug,用 JSON.stringify 一次性修掉。

改完后,首页的网站名片长这样:

{
  "@context": "https://schema.org",
  "@id": "${WEBSITE_ID}",             // 网站身份证号
  "@type": "WebSite",
  "name": "${AUTHOR_NAME}",
  "url": "${SITE_URL}/",
  "author": { "@id": "${PERSON_ID}" },     // ← 指向人物名片
  "publisher": { "@id": "${ORG_ID}" },    // ← 指向机构名片
  "inLanguage": "zh-CN"
}

首页从一个孤立的网站名片,变成了实体图的入口——AI 从首页进来,也能沿着身份证号找到完整的人物和机构名片。

改造第四步:验证名片真的能拼起来

改完要验证三件事:每张名片语法没错、身份证号能对上、内容正确。语法验证就是用程序把每张名片重新解析一遍,能解析通就是合法的:

const fs = require('fs');
const re = /type="application\/ld\+json">\s*([\s\S]*?)<\/script>/g;
['site/articles/zi-zu-duan-luo.html', 'site/index.html'].forEach(f => {
  const h = fs.readFileSync(f, 'utf8');
  let i = 0, m;
  while ((m = re.exec(h))) {
    i++;
    try { JSON.parse(m[1]); console.log(f + ' 名片' + i + ': ✅ 合法'); }
    catch (e) { console.log(f + ' 名片' + i + ': ❌ ' + e.message); }
  }
});

验证结果:文章页 4 张名片(文章 + 人物 + 机构 + 问答)全部合法,首页 3 张(网站 + 人物 + 机构)全部合法。身份证号用检索确认:每个 @id 都能找到对应的指向目标,没有断链。实体图闭环成立。

为什么 sameAs 只能填真实拥有的平台?

sameAs 是身份验证锚点,AI 会顺着这些网址去验证身份是否一致。填一个你确实拥有、且资料和自建站一致的页面,是一次成功的身份验证,增加你的可信度。填一个打不开的、或资料对不上的页面,是一次失败的身份验证,反而稀释可信度。

填之前问自己两个问题:这个网址我自己能打开吗?这个页面上的人和我自建站说的是同一个人吗?两个都是"是"才填进去。知乎是国内 AI 引擎能抓取的中文身份平台,GitHub 是全球通用的代码贡献者身份平台,这两个覆盖了中文和英文两个圈层,是天然适合做 sameAs 的平台。

为什么 knowsAbout 不用文章标签?

knowsAbout 声明的是"纪优作为一个研究者,稳定研究哪些领域",不是"这篇文章讲了什么"。如果它跟着文章标签变,讲 GEO 的文章说"我知道 GEO",讲段落写作的说"我知道自足段落"——AI 拼不出一个稳定的身份画像,只看到一堆随文章波动的话题标签。

一个研究边界稳定的人,比一个每篇文章都在换话题的人,更值得被 AI 当作可引用的来源。knowsAbout 就是告诉 AI:无论我这次写了什么,我的专业边界始终是这四个领域。

三个字段怎么配合工作?

AI 沿着实体图工作的完整链条是这样的:在某篇文章里看到 author: {"@id": "…/#person"} → 沿这个身份证号找到人物名片 → 读到 sameAs 去知乎和 GitHub 验证"纪优"身份 → 读到 knowsAbout 确认这个人在"GEO"领域有专业声明 → 综合判断这个实体是否可信、是否值得在回答里引用。

@id 解决"是不是同一个实体",sameAs 解决"在其他平台是谁",knowsAbout 解决"在什么话题上有发言权"。三个字段各司其职,缺一环,AI 拼出的就是一个残缺或矛盾的身份画像。实体图的价值不在于名片数量,而在于这些名片之间能不能通过身份证号连成一张没有断链的网。