课程资源的类型标签怎么贴:learningResourceType 与 inLanguage 的规范写法
你需要已经会在课程页里塞 JSON-LD,想搞清楚为什么 AI 搜索还是不推荐你的课。
上个月我们平台有个韩语入门课,课程质量不差,完课率 62%,但在 Perplexity 和 Bing Copilot 回答「韩语零基础网课推荐」时,引用的却是站内一个写错了类型标签的短视频合集。排课负责人老郑在周会上原话说:「课没毛病,标签全贴歪了。」这话不太好听,但是对的。我们花了两周自查,全站 1,847 门课里,314 门把视频课错误地标成了 Article,超过 400 门没有写 inLanguage——这直接导致 AI 搜索匹配错了受众。这篇把 learningResourceType、inLanguage、educationalUse 三个字段的规范写法讲透,附上我们踩过的坑和校验脚本。
先搞清楚:为什么类型标签对 AI 搜索这么重要
生成式引擎优化(Generative Engine Optimization, GEO)和传统 SEO 有个根本区别:传统搜索引擎给的是链接列表,AI 搜索给的是「答案」。答案要生成,AI 得先判断你这个页面到底「是什么东西」。是文章?是视频课?是题库?是面向哪国语言学习者的?

传统 SEO 时代,类型贴错了顶多影响富媒体摘要(Rich Result)的展示样式,用户点进来还能自己分辨。AI 搜索不一样——它不会点进来。它拿到的是索引阶段的元数据和内容摘要,类型标成 Article 的视频课,在「找一门能系统学的课程」这个意图下,权重天然就低。我们自建监测口径的数据(每天抓取五个 AI 搜索入口、记录被引用的课程 URL 和片段,跑了四周)显示:类型标签修正后的课程,被「课程推荐」类问题引用的次数比修正前高了约 2.4 倍;而缺 inLanguage 的小语种课程,几乎全军覆没,28 门里只有 3 门被正确引用过。
这是自建口径,样本小,别当行业均值用,但趋势很明确。
三个字段到底怎么写:规范原文逐条解读
learningResourceType:写资源形态,不写营销词
Schema.org 官方对 learningResourceType 的定义是「The predominant type or mode of learning of the resource」,也就是资源主要的学习形态。官方推荐的词汇表在 LearningResourceType 相关条目下,常见合法值包括:
| 合法值 | 对应资源 | 我们站内的典型误用 |
|---|---|---|
| Video Object / video | 视频课、录播课 | 写成了 Article 或 Course |
| text / lecture notes | 图文讲义、讲稿 | 写成了「精品好课」 |
| quiz / assessment | 测验、题库 | 根本没写这个字段 |
| course | 完整课程序列 | 和 Video 混用,一页贴两个 |
| slides | 课件 PPT | 漏标 |
| worksheet | 练习作业 | 漏标 |
三条最容易犯的错,都在表里了。展开说一下第一条,因为它最隐蔽:Course 和 learningResourceType 不是一回事。Course 是 schema.org 里的一个 Type,你用 "@type": "Course" 声明「这是一个课程实体」;而 learningResourceType 是 Course/VideoObject/LearningResource 上的一个属性,描述学习材料本身的形态。我们后来定的规矩是:课程详情页用 @type: Course + hasCourseInstance,其下每个学习单元(视频、讲义、测验)分别用对应的 Type 和 learningResourceType 描述,一个页面里形态要有层级,不能一锅端。
还有个反面案例:运营同学在标签里写 "learningResourceType": "爆款AI实战好课"。这种营销词机器不认识,等于没写,还可能让整个 JSON-LD 的可信度打折。写机器认识的词,人话留给标题和描述。
inLanguage:别只写课程名里的语言,要写教学语言
inLanguage 定义是「The language of the content or performance or used in an action」——注意,是内容使用的语言。坑就在这:
- 「日语 N3 备考课(中文授课)」:课程名带日语,教学语言是中文。inLanguage 该写
zh-CN,同时可以用availableLanguage或关键词辅助说明「涉及日语」。我们站内 9 门日语课,有 6 门标成了ja,结果被「日语学习资料」类查询错误引用,被「中文授课日语课」类查询全部漏掉。 - 双语课:可以写成数组,
"inLanguage": ["zh-CN", "en"],但要真的两种语言都占主体,不能只有几个英文单词就标双语。 - 别写国家代码不写语言代码:
"CN"是错的,要用 BCP 47 格式的zh-CN、en-US、ja-JP。
小语种课这个事值得单独强调。我们的越南语课和泰语课,单价低、受众窄,过去一直靠站内搜索硬扛。补上 inLanguage: vi / th 之后第四周,自建监测口径里「越南语入门」「泰语零基础」类 AI 搜索引用从 0 涨到了每周 7 次左右。量不大,但这些课本来就没有其他流量入口。
把几种典型场景的写法整理成对照表,整改时直接对着抄:
| 课程场景 | inLanguage 写法 | 常见错写 | 错写的后果 |
|---|---|---|---|
| 中文授课的日语课 | zh-CN |
ja |
被日语内容类查询错误引用 |
| 英文原版课 | en 或 en-US |
漏写 | 进不了「英文课」候选池 |
| 中英双语课 | ["zh-CN", "en"] |
只写 zh-CN |
双语检索意图漏召回 |
| 越南语课(越语授课) | vi |
漏写或写 VN |
小语种查询全军覆没 |
| 中文题干考韩语的测验 | ["zh-CN", "ko"] |
只写 zh-CN |
韩语学习意图匹配不上 |
educationalUse:说清楚「拿来干嘛用」
educationalUse 描述资源的教育用途,官方例值包括 assignment、exam、exercise、homework、lecture、presentation 等。它和 learningResourceType 的区别:learningResourceType 说「我是什么形态」,educationalUse 说「我服务于什么学习目的」。同一个视频,可能是 lecture(讲课),也可能是 demonstration(演示)。
一个实用组合:录播视频标 learningResourceType: video + educationalUse: lecture,配套测验标 learningResourceType: quiz + educationalUse: assessment。AI 搜索在回答「有没有带练习的 Python 入门课」这类问题时,就是靠这个字段组合判断的。
底层机制:AI 引擎怎么消费这些标签
只讲操作不讲原理,下次换个字段还是不会。AI 搜索的索引链路大致是这样的:
flowchart LR
A[爬取课程页] --> B[解析 JSON-LD 结构化数据]
B --> C{类型标签是否自洽?}
C -- 一致 --> D[进入知识图谱/实体对齐]
C -- 冲突 --> E[降权或丢弃结构化数据]
D --> F[按意图聚类: 语言x形态x用途]
F --> G[生成答案时按意图召回引用]
E --> G
关键在实体对齐那一步。AI 引擎会把你的 JSON-LD 和页面可见内容做交叉验证:页面正文里写着「48 节视频课」,JSON-LD 却说 learningResourceType 是 Article,两者冲突,结构化数据的可信度直接掉。更麻烦的是实体对齐会跨站进行——你标错的课,可能被对齐到别人的正确实体上,流量等于白白送人。
语言字段的消费路径更直白。回答「法语网课」时,引擎先按意图拆解出「语言=法语」这个约束,然后用 inLanguage 做硬过滤。缺这个字段的页面根本进不了候选池,内容写得再好也白搭。这也是为什么我们说:inLanguage 对多语言平台不是锦上添花,是准入门槛。
正确写法:一份可以直接抄的课程页 JSON-LD
环境:ASP.NET Core 8 / Python 3.12 均可校验,JSON-LD 版本基于 schema.org 2025-09 快照,课程页为 Razor 模板渲染。
{
"@context": "https://schema.org",
"@type": "Course",
// Course 是实体类型,声明"这是一个课程",与 learningResourceType 是两回事
"name": "韩语零基础发音入门(中文授课)",
"inLanguage": "zh-CN",
// 教学语言是中文,课程名里的"韩语"不是 inLanguage
"learningResourceType": "video",
// 学习形态是视频,不要写成 Article 或营销词
"educationalUse": ["lecture", "demonstration"],
// 用途:授课+发音示范,数组可多选
"description": "40 节视频课,从字母发音到音变规则,中文讲解配韩语示范。",
"provider": {
"@type": "Organization",
// provider 指内容供给方,别把讲师个人塞进这里
"name": "示例在线课堂"
},
"hasCourseInstance": {
"@type": "CourseInstance",
// courseMode 必填 online/offline/onSite,在线课写 online
"courseMode": "online",
// courseWorkload 用 ISO 8601 时长,PT10H 表示总学习时长 10 小时
"courseWorkload": "PT10H"
},
// 配套练习单独声明,形态与用途分开标
"hasPart": {
"@type": "Quiz",
"name": "第一单元发音自测",
"learningResourceType": "quiz",
"educationalUse": "assessment",
// 题干中文、考察对象韩语,两种语言都占主体,写成数组
"inLanguage": ["zh-CN", "ko"]
}
}
注意最后那个 hasPart 里的 quiz:它是韩语内容的测验,所以 inLanguage 写了 ["zh-CN", "ko"]——题干是中文、考察对象是韩语,两个都占主体。这种细节没有统一答案,按你内容的真实语言构成来。
从错到对:我们全站整改的流程
整改不是逐页手改,1,847 门课手改到明年也改不完。我们的流程分四步,画成时序更清楚:
sequenceDiagram
participant S as 调度脚本
participant P as 内容解析器
participant R as 规则引擎
participant C as CMS 草稿
S->>P: 拉取全站课程页 (每夜一批500页)
P->>P: 提取正文关键词/媒体文件类型
P->>R: 输出候选标签(video/quiz/text)
R->>R: 比对现有JSON-LD,标记冲突页
R->>C: 冲突页生成修正草稿(约12%命中人工复核)
C-->>S: 审核通过后批量发布
实测数据(自建口径,2026-08-11 到 2026-09-05):解析器给出的候选标签和人工判断一致率约 87%,剩下 13% 主要是混合形态课(视频+配套讲义),机器不知道该以哪个为主,这部分走了人工。整个整改 25 天,动了两轮 CMS 模板和一处历史数据迁移脚本。
校验方法:发布前把标签拦住
光靠人记规范没用,要放进门禁。两个层次:
离线校验(CI 里跑):环境:Python 3.12,仅标准库 + BeautifulSoup4。
import json, re
from bs4 import BeautifulSoup
# 只认站内规范里的形态词,营销词(爆款/好课)在这里直接拦下
ALLOWED_TYPE = {"video", "text", "quiz", "course", "slides", "worksheet", "lecture notes", "assessment"}
# BCP 47 格式:语言主码 2-3 位小写,可选地区子码,如 zh-CN / en-US
# 写 "CN" 这种纯国家码是常见错写,正则会拒绝
ALLOWED_LANG = re.compile(r"^[a-z]{2,3}(-[A-Z][a-zA-Z]{2})?$")
def check_course_jsonld(html: str) -> list[str]:
# 输入是课程页完整 HTML,输出问题列表,非空即阻断发布
# 每页可能有多段 JSON-LD,任何一段非法都算整页不过
soup = BeautifulSoup(html, "html.parser")
problems = []
# 一个页面可能有多段 JSON-LD,逐段校验
for tag in soup.find_all("script", type="application/ld+json"):
try:
data = json.loads(tag.string)
except json.JSONDecodeError:
problems.append("JSON-LD 解析失败,检查是否混入了模板变量")
# 模板变量没渲染就发布,是最常见的低级错误
continue
# learningResourceType 统一转小写再比对,忽略大小写抖动
lrt = str(data.get("learningResourceType", "")).lower()
if lrt and lrt not in ALLOWED_TYPE:
problems.append(f"learningResourceType 非法: {lrt}")
# 运营贴营销词会命中这里,需要人工改成合法词汇
# inLanguage 可能是字符串也可能是数组,统一按数组处理
lang = data.get("inLanguage")
langs = lang if isinstance(lang, list) else [lang]
for l in langs:
if not l or not ALLOWED_LANG.match(str(l)):
# 缺失时 l 是 None,同样报错,等于强制补齐
problems.append(f"inLanguage 非法或缺失: {l}")
# 问题列表非空时,CI 直接判失败并输出到 PR 评论
return problems
在线校验(发布后):Google Rich Results Test 能验 Course 的富媒体结构,但注意它不校验 learningResourceType 的词汇合法性(这是 schema.org 的 extension 层概念,Google 只验自己关心的字段),所以词汇白名单得自己守。另外每周用 Rich Result Status Report 看错误趋势,我们加门禁后,结构化数据报错数从每周 40+ 降到了个位数。
没解决的问题与几个取舍
有两个点到现在没有完美方案,写出来免得有人踩重复的坑。
一是混合形态课的主标签选择。一门课 40 节视频 + 12 份讲义 + 3 次测验,learningResourceType 只能写一个主值,写 video 就把讲义"藏"起来了。我们目前的折中是 hasPart 里全部展开,主标签写占比最高的形态。AI 引擎会不会正确展开 hasPart,各家的表现不一样,暂时只能靠监测数据慢慢调。
二是 inLanguage 对「教学语言」和「学习对象语言」没有官方的区分字段。中文授课教日语这种场景,只能靠 availableLanguage 和描述文本兜底。我们在 schema.org 的 issue 区看到过相关讨论,官方还没有定论。这块如果哪位读者有更好的实践,评论区聊。
参考与延伸
GEO · AI搜索 · learningResourceType · inLanguage · educationalUse · JSON-LD · Schema.org