试听音频抓不到文字内容:给课程站补 AudioObject 与文字稿的结构化改造

2026-09-27 01:19:29 1 次浏览
GEOAI搜索AudioObjectWhisperJSON-LDSchema.org

一、先说一个真实的尴尬场景

我们运营的职业技能课程站,每门课的详情页都挂了 10 分钟左右的试听音频,用的是一个纯播放器组件:一段 <audio> 标签加进度条,页面上没有一个字的文字稿。用户听完只能凭记忆回忆讲师讲了什么,AI 搜索这边更惨——用户在 DeepSeek、豆包里问「XX 认证考试备考要注意哪些坑」,答案里会引用别家博客的转述帖,就是不会引用我们试听音频里的第一手内容。

课程试听音频转文字稿示意图

问题的根源不难猜:主流 AI 引擎的抓取器拿到页面后,读到的是一个指向 mp3 的 URL。它们既不会在抓取阶段下载音频文件,也不会实时听音频再转文字。对我们来说,10 分钟试听音频里最值钱的干货,在 AI 引擎眼里约等于不存在。

今年 3 月我们做了一次针对性改造,给试听音频补上 Schema.org 的 AudioObject 结构化标注,并用 Whisper(OpenAI 开源的语音识别模型)在本地批量生成了文字稿。改造上线后第 3 周,AI 搜索引用开始零星出现,第 6 周时试听页被引用次数从每周 0 次涨到 14 次,课程咨询量也有明显变化。下面把整个改造过程拆开讲。

二、原理剖析:为什么音频内容必须转成文字才能进 AI 引用管线

这是整个改造的核心机制问题,值得单独讲清楚。

当前主流 AI 引擎不做实时音频理解。 无论是 DeepSeek、豆包、Kimi 还是 Perplexity,它们的引用来源都是文本语料。抓取器在抓取页面时做的是 HTML 解析和正文抽取,遇到 <audio src="lesson01.mp3"> 这种标签,最多把 URL 记下来,不会去下载几 MB 的音频文件、跑一遍语音识别再决定要不要引用——抓取成本和数据清洗成本都不允许这么做。也就是说,音频内容对 AI 引擎而言是「不可索引的黑盒」,除非你把内容以文本形式主动递给它。

那怎么递?两条通路:

  1. 页面可见文字稿:把 transcript 直接渲染在试听页上,AI 爬虫正常抓取就能读到;
  2. 结构化标注(Schema.org):在页面的 JSON-LD 里通过 AudioObject 的 transcript 属性显式声明文字稿,并和课程实体建立关联。

两条都做,效果叠加。页面可见的文字稿保证了内容可抓取,结构化标注则告诉 AI 引擎「这段文字就是那段音频的内容,而这段音频是这门课程的试听」——这就是 transcript 与 audio 的语义关联机制:transcript 属性挂在 AudioObject 实体上,AudioObject 又通过嵌套关系挂在 Course 实体下,AI 引擎做知识图谱对齐时,能把「课程 → 试听音频 → 文字稿」这条语义链完整还原出来。文字稿里提到的考点、讲法、案例,就有了明确归属,可以被引用到具体课程上,而不是变成一篇无主体的悬空文本。

这也是生成式引擎优化(Generative Engine Optimization, GEO)里容易被忽略的一块:大多数团队只做文本页面的优化,音视频内容的存量价值被白白丢掉了。

三、AudioObject 挂在 Course 下的正确嵌套写法

Schema.org 官方对 AudioObject 的定义在 https://schema.org/AudioObject,对 Course 的定义在 https://schema.org/Course。两者怎么组合,官方没有统一模板,但实践中有两种合理写法。

写法 A:Course → hasCourseInstance → courseWork / 直接挂 AudioObject 属性。 Course 类型本身没有 hasAudio 这种属性,实践里通行的做法是把试听音频作为 associatedMedia 挂在 Course 或 CourseInstance 上,属性值是 AudioObject。

写法 B:AudioObject 单独作为顶层实体,用 about 指回课程。 适合试听音频有独立落地页的场景。

我们的试听音频就在课程详情页里,所以选了写法 A,核心字段如下:

字段 作用 我们的取值示例
@type 实体类型 AudioObject
contentUrl 音频文件直链,抓取器可访问 https://cdn.example.com/lesson01.mp3
duration 时长,ISO 8601 格式 PT10M30S
encodingFormat 文件 MIME 类型 audio/mpeg
transcript 文字稿全文(或开头段落) 讲师口述内容的文字版
name / description 音频标题与描述 「第 1 课试听:真题里的三个高频陷阱」

特别注意 duration 必须用 ISO 8601 的 PT 表示法:10 分 30 秒写成 PT10M30S,纯数字秒数或「10:30」这种写法都不符合规范,解析器会直接丢弃。

下面是我们课程页实际的 JSON-LD 片段(简化后),依赖环境:任意后端模板引擎即可,前端也可以直接服务端注入。

// 课程详情页 JSON-LD 结构化数据(服务端模板渲染输出)
const courseJsonLd = {
  "@context": "https://schema.org",
  "@type": "Course",
  "name": "PMP 六周冲刺班",
  // 课程一句话描述,AI 引擎引用时常用作摘要
  "description": "面向零基础考生的 PMP 认证六周系统课",
  "provider": {
    "@type": "Organization",
    "name": "课程站技术示例",
    // 机构官网地址
    "url": "https://www.example.com"
  },
  // 试听音频通过 associatedMedia 挂在课程实体下
  "associatedMedia": {
    "@type": "AudioObject",
    // 音频文件直链,必须是抓取器可访问的公开 URL
    "contentUrl": "https://cdn.example.com/audio/lesson01-trial.mp3",
    // 时长用 ISO 8601 格式:10 分 30 秒
    "duration": "PT10M30S",
    // 文件 MIME 类型
    "encodingFormat": "audio/mpeg",
    // 音频标题
    "name": "第 1 课试听:真题里的三个高频陷阱",
    // 文字稿全文,语音内容进 AI 引用管线的关键字段
    "transcript": "大家好,今天这节试听课我们讲 PMP 真题里出现频率最高的三类陷阱题……",
    // 内容语言标识
    "inLanguage": "zh-CN"
  }
};
// 挂载到页面 head 中的 script 标签
// <script type="application/ld+json"> 里放 JSON.stringify(courseJsonLd)
# 视频同源场景可选:多档音频码率时用 encoding 列表声明
# 这里演示同一音频的多个封装格式
audio_encoding = {
    "@type": "AudioObject",
    "contentUrl": "https://cdn.example.com/audio/lesson01-trial.mp3",
    "duration": "PT10M30S",
    "encodingFormat": "audio/mpeg",
    "transcript": transcript_text,  # 文字稿变量,由转写脚本生成
    # 播放页面地址,方便回跳
    "url": "https://www.example.com/course/pmp/trial/lesson01"
}
# 实际部署时 JSON-LD 由模板引擎在服务端拼装
# 避免前端动态注入导致爬虫抓不到

两个代码块,一个讲嵌套结构,一个讲多封装格式的备选写法,生产环境按需取用。

四、用 Whisper 本地批量生成 transcript

文字稿从哪来?我们有 217 门课、平均每门 2.2 段试听音频,人工转写不现实。选了 openai-whisper 在本地转写,模型是 base 与 small 两档混合使用——中文课程内容实测 small 档准确率明显更好,GPU 单卡转一段 10 分钟音频约 40 秒。

依赖安装(Python 3.9+,建议先装好 ffmpeg 并加入 PATH):

# 安装 openai-whisper 及其依赖
pip install -U openai-whisper
# 音频解码依赖 ffmpeg,Windows 可用 conda 安装
conda install -c conda-forge ffmpeg
# 转写脚本运行命令示例,lang 指定中文
whisper lesson01-trial.mp3 --model small --language zh

批量转写的 Python 脚本骨架如下:

# -*- coding: utf-8 -*-
# 批量转写课程试听音频并输出 transcript 文本
import os
import json
import whisper

# 加载 small 档模型,中文场景准确率比 base 档好一截
model = whisper.load_model("small")

# 音频文件所在目录
audio_dir = "D:/trial_audio/batch_202603"
# 转写结果输出目录
out_dir = "D:/trial_audio/transcripts"

for fname in sorted(os.listdir(audio_dir)):
    # 只处理 mp3 与 m4a 格式
    if not fname.lower().endswith((".mp3", ".m4a")):
        continue
    audio_path = os.path.join(audio_dir, fname)
    # 执行转写,language 参数显式指定中文
    result = model.transcribe(audio_path, language="zh")
    # 拼接全部分段文本为一份完整文字稿
    transcript = "".join(seg["text"].strip() for seg in result["segments"])
    # 按同名规则写出 json 结果,便于后续入库
    out_path = os.path.join(out_dir, os.path.splitext(fname)[0] + ".json")
    with open(out_path, "w", encoding="utf-8") as f:
        json.dump({"audio": fname, "transcript": transcript}, f, ensure_ascii=False)
    # 打印进度,方便批量任务观察
    print(f"完成转写: {fname} -> {len(transcript)} 字")

转写完的 transcript 走了两步处理再上线:一是人工抽查了 30 段修正专有名词(讲师口播里的英文缩写、产品名,Whisper 偶尔会转错);二是文字稿同时渲染到试听页面上(折叠面板形式,默认展开前两段),保证纯文本爬虫也能读到。

五、课程页与转写管线的整体结构

两图回顾全貌。先是课程页的结构化数据嵌套关系:

graph TD
    A[Course 课程实体] --> B[CourseInstance 期次]
    A --> C[associatedMedia]
    C --> D[AudioObject 试听音频]
    D --> E[contentUrl 音频直链]
    D --> F[duration PT10M30S]
    D --> G[encodingFormat audio/mpeg]
    D --> H[transcript 文字稿全文]
    A --> I[provider 机构实体]

再是音频内容从原始文件到被 AI 引擎引用的完整管线:

graph LR
    A[试听音频 mp3] --> B[Whisper 本地转写]
    B --> C[transcript 文本]
    C --> D[人工抽查修正]
    D --> E[试听页渲染文字稿]
    D --> F[JSON-LD transcript 字段]
    E --> G[AI 引擎抓取页面文本]
    F --> H[结构化解析建立语义关联]
    G --> I[进入引用候选池]
    H --> I
    I --> J[AI 搜索回答中引用试听内容]

六、改造前后 6 周对照

单站观测数据(仅本站后台与日志统计,样本量有限,仅供参考)。统计起点为 3 月 9 日 JSON-LD 上线,文字稿页面渲染 3 月 12 日完成。

观测项 改造前 4 周均值 第 1-2 周 第 3-4 周 第 5-6 周
试听页被 AI 引用次数(周) 0 2 7 14
引用来源覆盖的 AI 引擎数 0 1 3 4
试听页自然搜索点击(周) 86 92 128 155
课程咨询量(周,含表单+客服) 61 58 73 89

几点补充观察:

  • 引用出现滞后约 3 周,符合结构化数据被重新抓取、解析、进入索引的常规周期,第 1 周那 2 次引用出现在 Kimi,恰好是它近期重抓了我们全站;
  • 咨询量里「直接问试听课内容」的会话占比从接近 0 涨到约 22%,说明引用流量在往转化漏斗里走;
  • 有 3 次引用来自 transcript 里的「陷阱题」表述,这种讲师口语里的高频关键词,文本页面反而没写。

七、两个容易踩的坑

坑一:PodcastEpisode 的播客经验不能照搬。 社区里关于音频结构化的文章大多在讲 PodcastEpisode 配 transcript,播客和课程试听是两类实体:播客是独立内容单元,课程试听是课程的附属物料。试听音频挂 associatedMedia 而不是另造 PodcastEpisode,否则 AI 引擎做实体对齐时会把试听音频理解成一个独立节目,语义归属反而乱了。

坑二:transcript 别只塞 JSON-LD。 有段时间我们的文字稿只存在于结构化数据里、页面上不渲染,某类只做正文抽取的抓取器完全读不到。文字稿必须双通道:页面可见 + 结构化标注,缺一不可。

趋势上判断一点:多模态输入的 AI 引擎迟早会直接理解音频,但文本 transcript 作为最稳定的语义载体,在可预见的周期内仍是语音内容被引用的基础设施。这个改造做完,站点结构化的地基又厚了一层,和传统的 SEO 收录优化互不冲突,收尾一句点到:网页侧的收录排序问题是另一条线,后续单独展开。

参考与延伸

  1. Schema.org AudioObject 官方定义:https://schema.org/AudioObject
  2. Schema.org Course 官方定义:https://schema.org/Course
  3. openai-whisper 开源仓库(安装说明与模型列表):https://github.com/openai/whisper

关键词:GEO、AI优化AIO、课程被 AI 推荐、AudioObject、transcript 文字稿、语音转写、JSON-LD、课程结构化

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