GEO 落地到教材页:Book 与 isbn 结构化让课程资料被 AI 引用

2026-10-08 08:36:48 1 次浏览
GEOAI搜索Schema.orgJSON-LD知识付费

适用读者:在线教育/知识付费平台的课程运营与前端 SEO 同学、负责结构化数据落地的后端工程师、关心生成式引擎优化(Generative Engine Optimization, GEO)如何影响「课程配套资料」曝光的团队。假设你已经会用 JSON-LD 写 Course 标记,本文直接进入 Book 实体与 isbn 的字段细节、章节拆分与跨源对齐的踩坑。

上个月有个学员在 AI 搜索框里问了一句:「你们那门《数据结构与算法》配的是哪本教材?」AI 回了一本书名,作者和出版社写得挺像回事——但那不是我们指定的那本,是另一家出的同类教材。截图丢到运营群里,第一反应是「爬虫是不是抓错了」。

我去翻那门课的教材页,问题朴素得很:教材信息被拍成一张封面图贴在正文里,页面上只有一句「点击下载讲义.pdf」。爬虫当然能抓到这张图,可抓不到「书名是什么、作者是谁、第几版、分几个章节」。AI 要回答「用什么教材」,只能靠猜,或者顺手拿它自己认识的同类书来凑。这类答非所问,根子不在抓取,在于页面没告诉机器「这是一本书」。

解法也不复杂:把教材页当成一个图书实体来标,用 Schema.org 的 Book 类型,把 isbn、author、publisher、bookEdition、numberOfPages、hasPart(章节)这些字段喂给爬虫。这篇记录我们平台从零把教材页 Book 结构化跑通的过程——ISBN 到底填 13 位还是 10 位、章节怎么拆、Book 和 Course 怎么互相指、上线前后 AI 回答口径差多少,都写清楚。

事故现场:一张 PDF 封面图引发的答非所问

先复盘我们踩的那版页面。它其实不是完全没做数据,Course 标记是有的:课程名、讲师、课时、价格一应俱全。问题恰恰出在「教材」这块被当成了附件,而不是实体。

教材书籍与 ISBN 条码连接 AI 网络节点的扁平科技插画

当时的教材区长这样:一张 800×1130 的封面 JPG,一段「本课程配套教材为《XXX》,购买后可下载前三章试读」的说明文字,加一个 PDF 下载按钮。这种呈现对人没问题,对机器就是一团模糊。AI 引擎在生成回答时,拿不到结构化的书名,只能从说明文字里做实体识别,识别不准时就会去对齐它训练语料里更常见的教材。

改造的方向很明确:教材页要有一个能被独立识别的 Book 节点,它不依赖图片里的文字,也不依赖正文描述。为此我们把三个东西拆开建:

  1. 教材详情页(/textbook/{slug}):承载 Book 节点,是引用的落点。
  2. 课程详情页(/course/{slug}):承载 Course 节点,指向教材详情页。
  3. 讲义与练习册:作为 Book 的 hasPart 或独立 CreativeWork 出现,不塞进图片。

下面这张字段清单,是我们上线后回填进内部文档的版本,改动最大的一列是「常见坑」。

字段 类型 建议必填 说明 常见坑
name Text 是 教材全名,含版本号 只写简称,AI 引用时书名残缺
isbn Text 是 13 位或 10 位,建议统一 13 位 带连字符会让跨源匹配率下降
author Person 是 一名或多名作者 误填成出版社,实体类型错位
publisher Organization 是 出版社名称 只给字符串,没带 @type
bookEdition Text 建议 版次,如 "3" 写成「第三版」导致排序异常
numberOfPages Number 建议 总页数 填成字符串,数值比较失效
hasPart Chapter[] 建议 章节目录 漏 position,目录顺序乱掉
inLanguage Text 建议 语言代码,如 zh-CN 省略后跨语言对齐权重变弱
url URL 是 教材详情页地址 指向 PDF 直链而非详情页

我们最初只标了 name 和 author,结果 AI 能把书名说出来,却始终不引用页面链接——因为缺 url,它没法回链。补上 url 后,引用作者页面的情况才出现。

isbn:13 位和 10 位到底填哪个

ISBN 这栏是我们内部争论最久的一点。有些老教材只有 10 位编号,教务处给的 Excel 里两种混在一起,还有的带了连字符。核对下来,能稳定跨源对齐的写法只有一种:

维度 ISBN-10 ISBN-13
位数 10 位,末位可以是 X 13 位纯数字
前缀 无 978 或 979
校验算法 加权和取模 11 加权和取模 10
当前状态 2007 年后新书基本不再分配 现行标准
建议写法 尽量换算成 13 位再入库 不带连字符,纯数字字符串

结论很干脆:入库阶段统一归一化成无连字符的 13 位,模板层不要再做任何转换。原因在下一节讲机制时说。至于 978 之外的 979 前缀,一般出现在小众或特定语种的出版物上,遇到别当成异常数据丢掉。

底层机制:ISBN 如何成为 AI 的实体锚点

这一节不操作,只讲 AI 引擎怎么用 ISBN 做跨源对齐。

一本教材在互联网上会同时出现在课程站的教材页、电商的图书详情页、书评社区、图书馆目录里。这些页面的书名写法往往不同——有的带副标题,有的把作者放前面,有的干脆用「XX 教材(第3版)」这种简称。光靠书名做匹配,误差很大。ISBN 是这批数据里少数全球范围内稳定通用的标识符,它在出版领域的分量,相当于身份证号在人身上的分量。AI 引擎做实体对齐时,会把它当成锚点。

我理解的对齐链路大概是这样:抓到一个页面,从 JSON-LD 里读到 isbn,用这个值去比对知识库里已有的图书实体;命中后,把这一页的作者、出版社、版次信息并入同一个实体;回答「这门课用什么教材」时,输出的是这个被多个来源共同确认过的实体,而不是某一页的孤立文本。

sequenceDiagram
    participant U as 用户
    participant AI as AI 检索引擎
    participant KG as 实体对齐层
    participant S as 教材页
    U->>AI: 这门课用什么教材?
    AI->>S: 抓取教材页 JSON-LD
    S-->>AI: 返回 Book 节点与 isbn
    AI->>KG: 以 isbn 为锚点查实体
    KG-->>AI: 命中作者/出版社/章节的交叉证据
    AI-->>U: 给出书名并引用教材页

这解释了前面那个归一化的决定。如果同一个 ISBN 在 A 页写成 978-7-302-56001-2,在 B 页写成 9787302560012,对齐层做字符串精确匹配时就可能把它们当成两条记录,跨源证据反而分散了。连字符对人类友好,对机器是噪音。所以归一化做在入库,最省事。

反过来也成立:缺 ISBN 的教材页,AI 只能退回用书名猜。这也是我们那门课被答错书的直接原因——页面上根本没有这个锚点。

一份完整可跑的教材页 JSON-LD

字段确定后,先手写一份完整的 Book 节点跑通校验,再交给脚本批量生成。环境说明:JSON-LD 无需额外依赖,直接放进页面 <head> 的 <script type="application/ld+json"> 里即可。

下面这份是教材详情页的实际结构(书名与 ISBN 为虚构示例,example.edu 是保留域名,可放心替换)。

{
  "@context": "https://schema.org",
  "@type": "Book",
  "@id": "https://example.edu/textbook/ds-algo-3e",
  "name": "数据结构与算法实训教程(第 3 版)",
  "isbn": "9787302560012",
  "bookEdition": "3",
  "numberOfPages": 428,
  "inLanguage": "zh-CN",
  "url": "https://example.edu/textbook/ds-algo-3e",
  "author": {
    "@type": "Person",
    "name": "周明远"
  },
  "publisher": {
    "@type": "Organization",
    "name": "启程教育出版社"
  },
  "hasPart": [
    { "@type": "Chapter", "name": "第 1 章 引论", "position": 1 },
    { "@type": "Chapter", "name": "第 2 章 算法分析", "position": 2 },
    { "@type": "Chapter", "name": "第 3 章 表、栈和队列", "position": 3 },
    { "@type": "Chapter", "name": "第 4 章 树与平衡树", "position": 4 },
    { "@type": "Chapter", "name": "第 5 章 散列", "position": 5 }
  ],
  "about": [
    { "@type": "Thing", "name": "数据结构" },
    { "@type": "Thing", "name": "算法分析" }
  ],
  "isBasedOn": "https://example.edu/course/ds-algo-2026"
}

几个字段单独拎出来说:

  • @id 用教材详情页的稳定 URL。它是这个节点在站内的身份,换 slug 等于换身份,所以别在改版时随手动它。
  • bookEdition 写成 "3" 而不是「第三版」。数值或短字符串便于做「第 3 版 vs 第 2 版」的比较,中文序数词容易让比较逻辑失效。
  • hasPart 的每一章都是独立 Chapter 节点,position 必须是数字。目录顺序是 AI 回答「有没有配套资料、讲了啥」时最常引用的部分,顺序错了很尴尬。
  • about 标明学科主题,帮助引擎在「数据结构课程用什么教材」这类问题里做主题匹配。
  • isBasedOn 反向指回课程页,形成教材与课程的闭环。

用脚本批量生成,别手写

一门课手写还行,我们平台上百门课、每门课还有讲义和练习册,手写必然漏字段。我们写了个 Python 生成器,从课程资料库导出教材记录,统一产出 JSON-LD。环境:Python 3.11,只用标准库 json 和 typing,没有第三方依赖。

注释写得比较密,因为接手的人容易在 hasPart 上翻车。

# -*- coding: utf-8 -*-
# 教材页 Book JSON-LD 生成器
# 环境:Python 3.11,仅用标准库 json 与 typing,无第三方依赖
# 用途:从课程资料库导出教材记录,批量产出可注入 <head> 的 JSON-LD

import json
# Any 只是为了给返回值加类型标注,不引入任何运行时开销
from typing import Any


# 章节记录形如 [(序号, 章节名), ...],单独拆成一个函数
# 是因为 hasPart 是数组,手写最容易漏掉 position 字段
def build_parts(chapters: list[tuple[int, str]]) -> list[dict[str, Any]]:
    # 每一章都要是独立的 Chapter 节点
    # position 用编号,保证 AI 引擎能还原目录顺序
    # 返回列表推导而不是就地 append,避免调用方拿到共享引用被改
    return [
        {
            "@type": "Chapter",
            "name": name,
            "position": pos,
        }
        for pos, name in chapters
    ]


# 生成单个 Book 节点
# 注意:isbn 必须在入库阶段归一化成无连字符的 13 位
def build_book(book: dict[str, Any], chapters: list[tuple[int, str]]) -> str:
    # 归一化放在这里之前,模板层不再做任何转换
    # 否则同一个 ISBN 会出现带连字符与不带连字符两种写法
    node = {
        "@context": "https://schema.org",
        # @type 固定 Book,误写 CreativeWork 会丢掉图书特有的字段语义
        "@type": "Book",
        # @id 用教材详情页稳定 URL,换 slug 等于换身份,改版时别动
        "@id": book["detail_url"],
        # name 用教材全名,含版本号,别用内部简称
        "name": book["title"],
        # isbn 不带连字符,13 位纯数字
        "isbn": book["isbn13"],
        # 版次写成短字符串,便于「第 3 版 vs 第 2 版」比较
        "bookEdition": str(book.get("edition", "")),
        "numberOfPages": book.get("pages"),
        # 语言固定 zh-CN,教材以简体中文为主
        "inLanguage": "zh-CN",
        # author 用 Person,publisher 用 Organization,两者别搞反
        "author": {"@type": "Person", "name": book["author"]},
        "publisher": {"@type": "Organization", "name": book["publisher"]},
        # 章节目录是 AI 回答「有没有配套资料」时最常引用的部分
        "hasPart": build_parts(chapters),
        # 教材详情页自身地址,用于回链
        "url": book["detail_url"],
    }
    # ensure_ascii=False 保证中文可读,indent=2 方便人工核对
    return json.dumps(node, ensure_ascii=False, indent=2)


if __name__ == "__main__":
    # 一段最小可跑的调用示例,替换成真实数据即可
    # title 里带上「第 X 版」,避免 AI 引用时书名残缺
    sample = {
        "title": "数据结构与算法实训教程(第 3 版)",
        "isbn13": "9787302560012",
        "author": "周明远",
        "publisher": "启程教育出版社",
        "edition": "3",
        "pages": 428,
        "detail_url": "https://example.edu/textbook/ds-algo-3e",
    }
    # 章节从资料库里读,演示里先写死
    # 实际项目里这一步换成数据库查询即可,函数签名不变
    chapters = [(1, "引论"), (2, "算法分析"), (3, "表、栈和队列")]
    # 输出到标准输出,交给构建流程注入页面模板
    print(build_book(sample, chapters))

这套脚本跑下来,最直接的收益不是省人力,而是字段口径统一。以前 A 课的 isbn 带连字符、B 课不带,对齐层看到的像是两本书;现在是同一个生成器出的,格式不会漂。

Book 和 Course 怎么互相指

教材页有 Book,课程页有 Course,两边得说清关系。我们的做法是双向:课程页用 about 和 coursePrerequisites 描述课程属性,教材页的 Book 用 isBasedOn 反向指回课程页。

课程页那侧的结构大致如下(同样用 example.edu 占位)。

{
  "@context": "https://schema.org",
  "@type": "Course",
  "name": "数据结构与算法(2026 秋季班)",
  "about": "数据结构、算法复杂度分析",
  "coursePrerequisites": "掌握一门编程语言的基础语法",
  "hasCourseInstance": {
    "@type": "CourseInstance",
    "courseMode": "online"
  },
  "isBasedOn": "https://example.edu/textbook/ds-algo-3e"
}

这里有两个容易忽略的点。

coursePrerequisites 是纯文本,写的是「学这门课前需要会什么」,不是教材信息。有人图省事把教材名塞进去,结果 AI 回答「先修要求」时把书名念了出来,很出戏。教材归 Book,先修归 Course,界限要清楚。

isBasedOn 在 Course 和 Book 上都能用,因为两者都属于 CreativeWork。方向别写反:课程依托教材,所以是 Course 指 Book。我们在教材页也放了一个 isBasedOn 指回课程,主要是为了让单独被抓到的教材页也能顺藤摸到课程,方向是从教材到课程这条引用链。

整个链路的落地顺序可以按下面这张图走,我第一次做的时候就是漏了最后的校验环节,上线一周才发现 hasPart 里的中文逗号把 JSON 撑坏了。

flowchart LR
    A[教材信息散落在 PDF 与图片里] --> B[抽字段 isbn/author/publisher]
    B --> C[归一化 isbn 为 13 位]
    C --> D[生成 Book JSON-LD]
    D --> E[注入教材页 head]
    E --> F[Rich Results Test 校验]
    F --> G[AI 爬虫抓取]
    G --> H[AI 回答说出书名/作者/章节目录]

上线前怎么校验

校验分两层:先保语法,再保语义。语法用本地脚本,语义交给 Google Rich Results Test。环境:Linux/macOS/WSL,装了 curl、jq 和 Python。

下面这套命令我们固化成上线检查清单,跑一遍不到一分钟。

# 环境:Linux/macOS/WSL,curl + jq + python 已安装
# 1) 拉取教材页,确认 JSON-LD 脚本标签确实存在
#    -s 静默模式,避免把进度条写进管道干扰 grep
curl -s https://example.edu/textbook/ds-algo-3e | grep -o 'application/ld+json'

# 2) 把页面里的 JSON-LD 抽出来单独校验语法
#    json.tool 能揪出多余逗号、中文引号这类低级错误
python -m json.tool book.json > /dev/null && echo "JSON 语法 OK"

# 3) 检查 isbn 是否 13 位纯数字
#    长度卡死 13,避免 10 位 ISBN 混进来
grep -oP '"isbn"\s*:\s*"\K[0-9]+' book.json \
  | awk '{ if (length($0)==13) print "ISBN 位数 OK"; else print "ISBN 位数不对" }'

# 4) 用 jq 核对章节目录数量与教材实际目录是否一致
#    数量对不上说明生成器漏读了章节记录
jq '.hasPart | length' book.json

# 5) 最后过一遍 Google Rich Results Test(浏览器打开,粘贴页面 URL)
echo "校验地址 https://search.google.com/test/rich-results"

语义校验里我们撞过一次比较闷的报错:Book 节点里的 numberOfPages 被填成了字符串 "428",Rich Results Test 提示 Invalid value type。改成数字后通过。还有一次是 author 写成了 Organization,工具没报错,但 AI 回答里把作者说成了出版社,属于典型的不报错但不正确。

改造前后:AI 回答口径对照

改造上线后我们做了一轮人工自测。口径说明:在 3 个主流 AI 产品里,各自对同一批问题问 10 次,人工判读回答里有没有出现目标信息,取平均。样本小、有主观成分,只看趋势。

自测问题 改造前(纯 PDF/图片) 改造后(Book 结构化)
能说出配套教材书名 1/10 9/10
能说出作者与出版社 0/10 8/10
能列出章节目录 0/10 7/10
回答里带上教材页链接 2/10 9/10

数据是我们自己抽样的,别外推到别的平台。趋势可信:有了 ISBN 锚点后,AI 引用教材页的概率明显上升,回答也不再张冠李戴。

最直观的变化是运营侧反馈。以前问「这门课用什么教材」,回答里常出现「通常使用某某教材」这种含糊说法;现在会直接给书名,并把教材页当来源列出。讲义和练习册这块,我们把它们做成 Book 的 hasPart 之后,AI 回答「有没有配套资料」时能点出「含讲义与练习册」——这是之前完全没有的。

误区澄清与趋势预判

几个我们踩过或见别人踩过的坑,单列出来。

  1. Book 不是把纸质书信息照抄一遍就完事。在线课程里的「配套讲义」「练习册」「实验手册」同样是 Book 或 CreativeWork,它们该有自己的节点,而不是塞进课程描述的文字里。图片形式的封面图,对机器等于不存在。
  2. ISBN 归一化要早做。放在入库阶段做,比在模板层做省事得多。混着连字符的 ISBN,跨源对齐会打折。
  3. 别为了 SEO 硬编章节目录。hasPart 里的章节要和实际教材一致,编造的目录一旦被用户发现「对不上」,反而伤可信度,这条线不能碰。
  4. Course 和 Book 的关系别搅在一起。coursePrerequisites 写先修要求,教材信息交给 Book,混填会让回答串味。

趋势上,我个人判断 AI 引擎对「实体锚点」的依赖会继续变重。图书、课程、讲义这类内容,天然适合用稳定的标识符做对齐——ISBN 只是其中一个。对教育平台来说,尽早把教材、讲义、练习册这些实体拆出来结构化的收益,比在正文里堆关键词大得多。做 GEO 的落地,说到底就是把机器回答问题时需要的那几个事实,提前用它能读懂的格式摆好。

参考与延伸

  • Schema.org Book 类型定义 — https://schema.org/Book
  • Schema.org isbn 属性说明 — https://schema.org/isbn
  • Google 搜索中心:结构化数据通用指南 — https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data
  • Google Rich Results Test 校验工具 — https://search.google.com/test/rich-results
  • JSON-LD 官方规范(W3C) — https://www.w3.org/TR/json-ld11/

GEO, AI优化AIO, 课程资料被 AI 引用, Book, isbn, hasPart, JSON-LD, 结构化数据

🤖
本内容由 AI 辅助生成,经人工校对审核;部分素材、资料来源于公开网络,仅作个人观点分享与交流使用,无任何商业侵权意图。若内容、图片、文字涉及您的合法著作权、版权权益,请联系本人,核实后将第一时间删除、修改相关内容。