「我們的內容都在付費牆後面,GEO 是不是做不了?」
這是最常見的問題,也是最常見的錯誤前提。 付費內容站要問的不是「開不開放」, 而是「開放哪一層,才能讓模型有東西引用、又不讓人不用付費」。
一、情境
平台 資料工程 / 後端開發線上課程,繁中為主
規模 8,000 名付費學員,18 門課,1,200 支影片,年營收約 NT$6,000 萬
內容 90% 是影片(平均 12 分鐘)+ 課後練習 + 討論區
技術 Next.js(前台)+ Django(後台)+ PostgreSQL
部署 GCP:Cloud Run(前後台)+ Cloud CDN + Cloud SQL + GCS(影片)
付費 單門課 NT$3,600-6,800,或年費 NT$14,800 全站通行
業務動機:學員來源高度依賴 FB 廣告與 KOL 合作,獲客成本連續兩年上升(CAC 從 NT$1,900 漲到 NT$3,400)。同時發現學員在課程討論區問的問題,很多人是先去問 AI——但 AI 從沒提過這個平台。
基線量測(35 題 × 3 引擎,2026-01)
──────────────────────────────────
提及率 6%
引用率 2%
Coverage Gap 71%
可被 crawler 讀取的內容佔全站 3% ← 核心問題
二、真正的問題:內容存在,但不可見
內容資產盤點
─────────────────────────────────────────────────────
1,200 支影片 付費牆後,且只有影片沒有文字
1,200 份自動字幕(VTT) 存在 GCS,從未公開
↑ 這是被完全浪費的資產
340 篇課後練習與解答 付費牆後
2,800 則討論區問答 付費牆後 ← UGC,AI 最愛引用的類型
18 個課程介紹頁 公開,但都是行銷文案
0 篇部落格 —
可被 AI crawler 讀到的:18 個課程介紹頁 ≈ 全站內容的 3%
關鍵發現:字幕檔已經存在了。1,200 支影片 × 平均 1,800 字 = 約 216 萬字的技術內容,躺在 GCS 上從來沒被任何人或機器讀過。
三、核心決策:付費牆的三層設計
這是整個案例最重要的一個決定,也是所有付費內容站都要面對的問題。
╔═══════════════════════════════════╗
║ Layer A:完全公開 ║
║ 目標:被引用、建立權威 ║
╠═══════════════════════════════════╣
║ • 課程大綱(逐單元,含學習目標) ║
║ • 每支影片的「重點摘要」(200-400 字)║
║ • 每門課 1-2 支完整免費單元 ║
║ • 討論區的「問題 + 第一段解答」 ║
║ • 名詞解釋詞條(從字幕自動抽取) ║
║ • 課程 FAQ、先修需求、適合對象 ║
╚═══════════════════════════════════╝
│
╔═══════════════════════════════════╗
║ Layer B:預覽(可見前 30%) ║
║ 目標:展示深度,製造缺口感 ║
╠═══════════════════════════════════╣
║ • 影片逐字稿前 30%(然後截斷) ║
║ • 練習題目(公開)+ 解答(不公開) ║
║ • 用 isAccessibleForFree 誠實標註 ║
╚═══════════════════════════════════╝
│
╔═══════════════════════════════════╗
║ Layer C:完全封閉 ║
║ 目標:保護核心價值 ║
╠═══════════════════════════════════╣
║ • 影片本身 ║
║ • 完整逐字稿 ║
║ • 練習解答與程式碼 ║
║ • 作業批改、助教問答 ║
║ • 證書、社群 ║
╚═══════════════════════════════════╝
設計原則:
開放「知道有這件事」 → Layer A
保留「知道怎麼做」 → Layer C
中間放「知道大概怎麼做但缺細節」 → Layer B
一個具體判準:如果一段內容被完整引用後,讀者就不需要買課了,那它屬於 Layer C。 但「Kafka 的 consumer group rebalance 會造成什麼問題」這種概念說明屬於 Layer A——知道問題存在,不等於知道怎麼調參解決。
四、實作
決策 1:把字幕變成可引用的頁面(最大的一項)
1,200 份 VTT 字幕 → 1,200 個公開的「單元重點」頁面。
處理管線(一次性批次,跑在 Cloud Run Job)
GCS: /captions/{course}/{lesson}.vtt
│
▼
① 清洗:去時間軸、合併斷句、修正 ASR 錯字(術語字典)
│
▼
② LLM 生成三種產出:
a. 重點摘要(200-400 字,Layer A)
b. 5-8 條 key takeaways(Layer A)
c. 名詞解釋(抽取本單元出現的技術名詞,Layer A)
│
▼
③ 逐字稿前 30% 截斷(Layer B)
│
▼
④ 寫回 PostgreSQL,Next.js ISR 渲染
1# jobs/caption_to_page.py —— Cloud Run Job,一次性處理 1,200 份
2import re, json, os
3from google.cloud import storage
4import anthropic
5
6TERM_FIX = { # ASR 常錯的技術名詞
7 "卡夫卡": "Kafka", "波斯特格雷": "PostgreSQL",
8 "達克": "Docker", "庫伯內特斯": "Kubernetes",
9}
10
11def clean_vtt(vtt: str) -> str:
12 lines = []
13 for ln in vtt.splitlines():
14 if ln.startswith("WEBVTT") or "-->" in ln or ln.strip().isdigit() or not ln.strip():
15 continue
16 lines.append(ln.strip())
17 text = " ".join(lines)
18 for wrong, right in TERM_FIX.items():
19 text = text.replace(wrong, right)
20 return re.sub(r"\s+", " ", text)
21
22
23PROMPT = """以下是一支線上課程影片的逐字稿。請產出 JSON,包含:
24
25- "summary": 200-400 字的重點摘要。必須包含逐字稿中出現的具體技術名詞、
26 數字、指令與參數。用完整句子,主詞明確(不要用「這個」「它」)。
27 這段摘要會被獨立展示,讀者看不到影片,所以必須自成一體。
28- "takeaways": 5-8 條 key takeaway,每條 20-40 字,各自可獨立成立。
29- "terms": 本單元出現的技術名詞,每個附 30-60 字解釋。
30- "questions": 這支影片能回答的 3-5 個具體問題(使用者會怎麼問)。
31
32只輸出 JSON,不要其他文字。
33
34【課程】{course}
35【單元】{lesson}
36【逐字稿】
37{transcript}
38"""
39
40def process(course, lesson, transcript):
41 client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
42 msg = client.messages.create(
43 model="claude-sonnet-5",
44 max_tokens=2000,
45 messages=[{"role": "user", "content": PROMPT.format(
46 course=course, lesson=lesson, transcript=transcript[:24000])}],
47 )
48 data = json.loads(re.search(r"\{.*\}", msg.content[0].text, re.S).group())
49 # Layer B:逐字稿前 30%
50 cut = int(len(transcript) * 0.3)
51 data["preview"] = transcript[:cut].rsplit("。", 1)[0] + "。"
52 return data
成本:1,200 支 × 約 8k input + 1.5k output token ≈ US$78 的一次性費用。這大概是本系列所有案例中投報率最高的一筆支出。
產出的頁面結構:
/courses/data-engineering/lessons/kafka-consumer-group/
<h1>Kafka Consumer Group 與 Rebalance 機制</h1>
<p>課程:資料工程實戰 · 第 4 章第 3 節 · 影片長度 14:22</p>
<section id="summary">
<h2>這個單元在講什麼?</h2>
<p>(200-400 字重點摘要,Layer A)</p>
</section>
<section id="takeaways">
<h2>重點整理</h2>
<ul>(5-8 條,Layer A)</ul>
</section>
<section id="questions">
<h2>這個單元能回答的問題</h2>
<ul>
<li>Consumer group rebalance 會造成什麼問題?</li>
<li>為什麼 session.timeout.ms 設太短會頻繁 rebalance?</li>
<li>Static membership 什麼時候該用?</li>
</ul>
</section>
<section id="terms">
<h2>本單元名詞解釋</h2>
<dl>(Layer A)</dl>
</section>
<section id="transcript" class="paywalled">
<h2>影片逐字稿</h2>
<p>(前 30%,Layer B)</p>
<div class="paywall-notice">
完整逐字稿與影片需要課程權限。<a href="/courses/data-engineering">了解課程</a>
</div>
</section>
注意 <section id="questions">:明確列出「這頁能回答哪些問題」。這對 rerank 有直接幫助——它讓 chunk 與使用者查詢的語意距離大幅縮短。這是課程平台特別適合用的一招,因為課程大綱本來就是問題導向的。
決策 2:正確標註付費牆(不是偽裝)
1{
2 "@context": "https://schema.org",
3 "@type": ["Article", "LearningResource"],
4 "headline": "Kafka Consumer Group 與 Rebalance 機制",
5 "isAccessibleForFree": false,
6 "hasPart": [
7 {
8 "@type": "WebPageElement",
9 "isAccessibleForFree": true,
10 "cssSelector": "#summary, #takeaways, #terms, #questions"
11 },
12 {
13 "@type": "WebPageElement",
14 "isAccessibleForFree": false,
15 "cssSelector": "#transcript"
16 }
17 ],
18 "learningResourceType": "影片單元",
19 "educationalLevel": "中階",
20 "teaches": ["Kafka consumer group", "rebalance 調校", "static membership"],
21 "isPartOf": {
22 "@type": "Course",
23 "@id": "https://example.com/courses/data-engineering/#course",
24 "name": "資料工程實戰"
25 },
26 "timeRequired": "PT14M22S"
27}
課程層級的 Course schema:
1{
2 "@context": "https://schema.org",
3 "@type": "Course",
4 "@id": "https://example.com/courses/data-engineering/#course",
5 "name": "資料工程實戰:從 ETL 到 Streaming",
6 "description": "18 小時、82 個單元的資料工程課程,涵蓋 Airflow、Kafka、dbt 與 Spark,以四個可上線的專案為主軸。適合有 1 年以上後端經驗的工程師。",
7 "provider": { "@id": "https://example.com/#organization" },
8 "inLanguage": "zh-Hant-TW",
9 "teaches": ["Airflow", "Kafka", "dbt", "Spark", "資料建模"],
10 "coursePrerequisites": "熟悉 Python 與 SQL;具備 1 年以上後端或資料相關經驗",
11 "educationalLevel": "中階",
12 "hasCourseInstance": {
13 "@type": "CourseInstance",
14 "courseMode": "online",
15 "courseWorkload": "PT18H"
16 },
17 "offers": {
18 "@type": "Offer",
19 "price": "5800", "priceCurrency": "TWD",
20 "category": "Paid",
21 "availability": "https://schema.org/InStock"
22 },
23 "aggregateRating": {
24 "@type": "AggregateRating",
25 "ratingValue": "4.7", "reviewCount": 412
26 },
27 "syllabusSections": [
28 { "@type": "Syllabus", "name": "第 4 章:Kafka 與串流處理",
29 "description": "Kafka 架構、producer/consumer 調校、exactly-once 語意",
30 "timeRequired": "PT3H10M" }
31 ]
32}
為什麼要誠實標註而不是給 bot 看全文:
偽裝的三個問題
① 被判定為 cloaking,全站信任度下降
② 模型引用了付費內容 → 使用者點進來發現要付費 → 負面體驗
③ 你的完整內容進了訓練語料,永久失去控制
誠實標註的收益
① 模型知道「這裡有更完整的內容,需要付費」
→ 實際觀察到引擎會在答案中提示「完整內容需訂閱」
② Layer A 內容足以被引用,不需要犧牲 Layer C
決策 3:討論區問答部分公開
2,800 則討論區問答是被低估的資產——UGC 是 AI 引擎最偏好引用的內容類型之一。
公開策略
問題全文 ✔ 公開
講師回答的第一段 ✔ 公開(通常是結論)
完整回答與程式碼 ✘ 需要課程權限
提問者身分 匿名化為「學員」
/qa/why-does-airflow-task-stuck-in-queued/
<h2>Airflow 的 task 一直卡在 queued 狀態,可能是什麼原因?</h2>
<p class="qa-meta">來自「資料工程實戰」課程討論區 · 2026-03-14 · 學員提問</p>
<blockquote class="question">
我的 DAG 排程時間到了,task 顯示 queued 但一直沒有變成 running,
scheduler 的 log 也沒有錯誤訊息……
</blockquote>
<div class="answer">
<p><strong>講師回答(節錄):</strong></p>
<p>
task 卡在 queued 最常見的三個原因是:① executor 的 slot 用完了
(檢查 parallelism 與 dag_concurrency);② pool 的 slot 被佔滿;
③ worker 沒有正常連上 broker。先用
<code>airflow celery inspect active</code> 確認 worker 狀態,
再看 <code>airflow pools list</code>……
</p>
<div class="paywall-notice">完整解答與排查腳本需要課程權限。</div>
</div>
這類頁面的引用率是全站最高的——因為它精確對上了使用者遇到問題時的提問方式。
決策 4:GCP 側的實作要點
┌──────────────────┐
Bot / 使用者 ──▶ │ Cloud Load │
│ Balancer │
│ + Cloud Armor │ ← 檢查點 ①
└────────┬──────────┘
▼
┌──────────────────┐
│ Cloud CDN │ ← 檢查點 ②
└────────┬──────────┘
▼
┌──────────────┴──────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Cloud Run │ │ Cloud Run │
│ (Next.js 前台) │ │ (Django API) │
└─────────────────┘ └─────────────────┘
檢查點 ①:Cloud Armor
原本的 preconfigured WAF rule 中,OWASP CRS 的某條規則會對高頻的非瀏覽器 UA 打分並封鎖。加一條優先權最高的例外:
1gcloud compute security-policies rules create 100 \
2 --security-policy=course-platform-policy \
3 --expression='request.headers["user-agent"].contains("OAI-SearchBot") ||
4 request.headers["user-agent"].contains("Claude-SearchBot") ||
5 request.headers["user-agent"].contains("PerplexityBot") ||
6 request.headers["user-agent"].contains("Googlebot")' \
7 --action=allow \
8 --description="Allow AI retrieval bots"
檢查點 ②:Cloud CDN 的 Vary 與 Cookie
原本 Cloud Run 對所有回應都帶 Set-Cookie(session),導致 Cloud CDN 完全不快取,且部分 bot 的請求被導向登入流程。
1# Django middleware:對公開路徑不設 session cookie
2PUBLIC_PREFIXES = ("/courses/", "/qa/", "/glossary/", "/blog/")
3
4class NoSessionForPublicPaths:
5 def __init__(self, get_response):
6 self.get_response = get_response
7
8 def __call__(self, request):
9 response = self.get_response(request)
10 if (request.path.startswith(PUBLIC_PREFIXES)
11 and not request.user.is_authenticated):
12 response.cookies.clear()
13 response["Cache-Control"] = "public, max-age=300, s-maxage=3600"
14 response["Vary"] = "Accept-Encoding" # 不要 Vary: Cookie
15 return response
Next.js 側:確保 Layer A 內容走 SSG/ISR,只有 Layer B/C 的權限判斷在 request 時做。
1export const revalidate = 3600
2
3export default async function LessonPage({ params }) {
4 // Layer A:建置期就有,所有人(含 bot)都看得到
5 const lesson = await getLessonPublicData(params.slug)
6
7 return (
8 <article>
9 <PublicSections lesson={lesson} />
10 {/* Layer B/C:client component,依權限決定顯示 */}
11 <PaywalledTranscript lessonId={lesson.id} preview={lesson.preview} />
12 </article>
13 )
14}
五、結果
指標 改前(2026-01) 改後(2026-07) 變化
──────────────────────────────────────────────────────
提及率 6% 41% +35pp
引用率 2% 27% +25pp
Coverage Gap 71% 18% -53pp
可被讀取的內容佔比 3% 34% ×11
被引用的頁面類型
討論區 QA 頁 38% ← 最高
單元重點頁 31%
名詞解釋詞條 14%
課程介紹頁 11%
其他 6%
商業側
自然流量(含 AI referral) 4,200/月 19,800/月 ×4.7
試聽轉付費率 2.1% 2.4% +0.3pp
新學員數 210/月 385/月 +83%
CAC NT$3,400 NT$2,050 -40% ← 核心成果
最重要的數字是 CAC 下降 40%。因為新增的學員大多來自自然流量而非付費廣告。
那個所有人都在擔心的問題:營收有沒有被吃掉?
擔心:公開摘要 → 使用者看完摘要就不買課了
實測(對比 6 個月的同期資料)
現有學員的續訂率 87% → 88% 無變化
單門課的完課率 62% → 63% 無變化
客訴「內容外流」 0 件
發現盜版逐字稿 0 件(Layer C 從未公開)
「看了摘要頁後購買」的轉換率 3.8%
「直接進課程介紹頁」的轉換率 2.4%
→ 讀過摘要頁的人反而更容易買
原因很直觀:200-400 字的摘要能證明「這門課真的講到細節」,但完全無法取代 14 分鐘的影片教學。摘要是最好的試聽。
投入分解
項目 人天 費用
────────────────────────────────────────────────
字幕處理 pipeline 6 US$78(LLM 一次性)
單元頁模板 + ISR 4
討論區公開化(含匿名化) 5
Course / LearningResource 2
schema
Cloud Armor + CDN 修正 2
名詞詞條頁自動生成 3
監測系統 2
────────────────────────────────────────────────
合計 24 人天 + 約 US$120
六、三個 insight
1. 付費內容站的資產通常已經存在,只是沒有被渲染
盤點時要問的問題
□ 影片有字幕嗎?→ 那就是文字內容
□ 有討論區嗎?→ 那就是 UGC
□ 有課後練習嗎?→ 題目可以公開,解答不用
□ 有課程大綱嗎?→ 那本來就是問題清單
這個案例的 216 萬字字幕,在改造前的價值是零。
2. 「開放摘要會吃掉營收」是錯的前提
實測的三個發現:
① 讀過摘要頁的訪客,購買轉換率比直接進介紹頁的高 58%
② 摘要無法取代教學——它展示深度,不傳遞技能
③ 真正該保護的是 Layer C(完整逐字稿、解答、程式碼),
而這些從頭到尾都沒有公開
判準:一段內容如果被完整引用後讀者就不用付費了,才屬於 Layer C。多數人把這條線劃得太保守,結果是整站不可見。
3. 討論區 QA 是課程平台最強的 GEO 資產
為什麼?
① 問題是使用者用自己的話問的 → 天然對上檢索查詢
② 有具體錯誤訊息、版本號、環境 → 高度可接地
③ 是 UGC → AI 引擎偏好真實經驗
④ 量大且持續增長 → 零邊際內容成本
做法
問題全文公開 + 回答第一段公開 + 完整解答付費
匿名化提問者
每則獨立成頁,標題就是問題本身
本案例中討論區 QA 頁佔了 38% 的引用,而它的內容成本是零——那些回答本來就寫了。
七、可以直接抄的清單
內容分層(先做這個決定,其餘都是執行)
□ Layer A(公開):大綱、單元摘要、takeaways、名詞、能回答的問題、
1-2 支免費單元、討論區問題 + 回答首段
□ Layer B(預覽):逐字稿前 30%、練習題目
□ Layer C(封閉):影片、完整逐字稿、解答、程式碼、社群
□ 判準:完整引用後讀者就不用付費 → Layer C,否則往上放
字幕轉內容
□ 清洗 VTT(去時間軸 + 術語字典修正 ASR 錯字)
□ LLM 批次生成 summary / takeaways / terms / questions
□ 摘要要求「主詞明確、自成一體」(chunk 獨立性)
□ 明列「這個單元能回答的問題」
□ 成本抓 US$0.06-0.10 / 支影片
Schema
□ Course(含 syllabusSections、coursePrerequisites、offers)
□ 每單元 LearningResource + isAccessibleForFree
□ hasPart + cssSelector 明確標出付費區塊
□ 絕不給 bot 看付費內容(cloaking 的代價遠大於收益)
討論區
□ 每則 QA 獨立成頁,標題即問題
□ 問題全文 + 回答首段公開
□ 提問者匿名化
□ 標註課程來源與日期
GCP
□ Cloud Armor 加 AI bot 的 allow rule(優先權最高)
□ 公開路徑不下 session cookie,Vary 不含 Cookie
□ 公開內容走 SSG/ISR,權限判斷放在 client component
□ Cloud CDN 對公開路徑設 s-maxage
驗證
□ 用 curl -A "OAI-SearchBot" 確認:Layer A 看得到、Layer C 看不到
□ 每月抽查:模型有沒有洩漏 Layer C 內容
本系列文章:
