课程资源的类型标签怎么贴:learningResourceType 与 inLanguage 的规范写法
适用读者:做在线课程平台、知识付费站点的后端与 SEO/GEO 工程师;负责课程页结构化数据(Schema.org)落地的前端同学。
TL;DR:三个字段的核心规范
- learningResourceType:写资源形态,不写营销词。
- inLanguage:写教学语言,不写课程名语言。
- educationalUse:与 learningResourceType 配合,说明用途。
- Course是实体类型,learningResourceType 是属性。
- inLanguage用 BCP 47 格式,如
zh-CN。 - 双语课写成数组,如
["zh-CN", "en"]。
你需要已经会在课程页里塞 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 搜索的索引链路大致是这样的:
关键在实体对齐那一步。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 门课手改到明年也改不完。我们的流程分四步,画成时序更清楚:
实测数据(自建口径,2026-08-11 到 2026-09-05):解析器给出的候选标签和人工判断一致率约 87%,剩下 13% 主要是混合形态课(视频+配套讲义),机器不知道该以哪个为主,这部分走了人工。整个整改 25 天,动了两轮 CMS 模板和一处历史数据迁移脚本。
校验方法:发布前把标签拦住
光靠人记规范没用,要放进门禁。两个层次:
离线校验(CI 里跑):环境:Python 3.12,仅标准库 + BeautifulSoup4。
importjson,refrombs4importBeautifulSoup# 只认站内规范里的形态词,营销词(爆款/好课)在这里直接拦下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})?$")defcheck_course_jsonld(html:str)->list[str]:# 输入是课程页完整 HTML,输出问题列表,非空即阻断发布# 每页可能有多段 JSON-LD,任何一段非法都算整页不过soup=BeautifulSoup(html,"html.parser")problems=[]# 一个页面可能有多段 JSON-LD,逐段校验fortaginsoup.find_all("script",type="application/ld+json"):try:data=json.loads(tag.string)exceptjson.JSONDecodeError:problems.append("JSON-LD 解析失败,检查是否混入了模板变量")# 模板变量没渲染就发布,是最常见的低级错误continue# learningResourceType 统一转小写再比对,忽略大小写抖动lrt=str(data.get("learningResourceType","")).lower()iflrtandlrtnotinALLOWED_TYPE:problems.append(f"learningResourceType 非法:{lrt}")# 运营贴营销词会命中这里,需要人工改成合法词汇# inLanguage 可能是字符串也可能是数组,统一按数组处理lang=data.get("inLanguage")langs=langifisinstance(lang,list)else[lang]forlinlangs:ifnotlornotALLOWED_LANG.match(str(l)):# 缺失时 l 是 None,同样报错,等于强制补齐problems.append(f"inLanguage 非法或缺失:{l}")# 问题列表非空时,CI 直接判失败并输出到 PR 评论returnproblems在线校验(发布后):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 区看到过相关讨论,官方还没有定论。这块如果哪位读者有更好的实践,评论区聊。
常见问题 FAQ
learningResourceType 能否写多个值?
可以写成数组,但主标签只能有一个。一门课 40 节视频 + 12 份讲义 + 3 次测验,learningResourceType 只能写一个主值,写 video 就把讲义"藏"起来了。我们目前的折中是hasPart里全部展开,主标签写占比最高的形态。
inLanguage 缺失时 AI 搜索如何降级处理?
缺这个字段的页面根本进不了候选池,内容写得再好也白搭。回答「法语网课」时,引擎先按意图拆解出「语言=法语」这个约束,然后用 inLanguage 做硬过滤。这也是为什么我们说:inLanguage 对多语言平台不是锦上添花,是准入门槛。
educationalUse 与 learningResourceType 是否必须成对出现?
不是必须,但成对出现效果最好。learningResourceType 说「我是什么形态」,educationalUse 说「我服务于什么学习目的」。一个实用组合:录播视频标learningResourceType: video+educationalUse: lecture,配套测验标learningResourceType: quiz+educationalUse: assessment。
混合形态课程的主标签如何选择?
learningResourceType 只能写一个主值,我们目前的折中是hasPart里全部展开,主标签写占比最高的形态。AI 引擎会不会正确展开 hasPart,各家的表现不一样,暂时只能靠监测数据慢慢调。
BCP 47 格式中地区码大小写是否敏感?
规范上不敏感,但建议统一。语言主码 2-3 位小写,可选地区子码,如zh-CN/en-US。校验脚本里ALLOWED_LANG = re.compile(r"^[a-z]{2,3}(-[A-Z][a-zA-Z]{2})?$")会拒绝"CN"这种纯国家码。
校验脚本如何接入 CI 流水线?
把check_course_jsonld放进 CI,问题列表非空时直接判失败并输出到 PR 评论。我们加门禁后,结构化数据报错数从每周 40+ 降到了个位数。
参考与延伸
- Schema.org Course 官方定义
- Schema.org LearningResourceType 相关条目
- Google 搜索中心:课程结构化数据
- web.dev 结构化数据入门
GEO · AI搜索 · learningResourceType · inLanguage · educationalUse · JSON-LD · Schema.org