一、开篇:你的网站有排名,但AI可能根本不想理你

以前做内容优化,很多人盯着三个指标:

  • 关键词有没有排名?
  • 页面有没有收录?
  • 流量有没有涨?

但现在问题变了。

用户不一定再打开10个网页慢慢看,而是直接问AI:

“怎么选择一家靠谱的工业设备供应商?”
“某类产品采购时要注意哪些参数?”
“A方案和B方案哪个更适合中小企业?”

AI会先检索、理解、归纳,然后生成答案。

这就带来一个很扎心的问题:

你的内容被搜索引擎收录了,不代表AI能理解;
AI能找到你的页面,不代表它愿意引用;
AI知道你存在,不代表它会把你写进答案。

这就是 GEO 要解决的问题。

GEO,全称 Generative Engine Optimization,可以理解为“生成式引擎优化”。它关注的不是单纯把页面排到搜索结果前面,而是让内容更容易被生成式AI理解、引用、整合进答案。

简单说:

SEO 解决“搜不搜得到”。
GEO 解决“AI看不看得懂、信不信、用不用”。

本文不讲玄学,不讲营销口号。我们直接用技术文章的方式,拆解 GEO 的内容结构,并写一个可运行的 Python 小工具,检测一篇文章是否具备基本的 GEO 友好特征。


二、问题现场:为什么AI不引用你的内容?

很多内容不是不努力,而是长得太像下面这样:

我们拥有多年行业经验,产品质量优秀,服务专业,欢迎咨询。

这句话人看了没感觉,AI看了也很难用。

因为它缺少几个关键元素:

  1. 没有明确回答用户问题
  2. 没有结构化信息
  3. 没有事实、数据、流程或判断标准
  4. 没有FAQ
  5. 没有定义、方法、步骤、案例等可引用片段
  6. 页面主题不清晰
  7. 缺少语义关联

AI更喜欢什么样的内容?

选择工业过滤设备供应商时,建议从过滤精度、材料兼容性、压力范围、认证资质、交付周期和售后支持六个维度评估。

如果应用场景涉及高温液体,应优先确认滤材耐温范围;如果用于食品行业,还需要关注食品接触材料合规性。

这段内容更容易被AI引用,因为它具备:

  • 明确的问题场景
  • 可拆解的判断维度
  • 条件判断
  • 行业术语
  • 可复用结论

所以,GEO不是简单“多写文章”,而是让内容变成AI能拆、能懂、能引用的知识结构。


三、GEO的核心逻辑:从“争排名”到“进答案”

传统SEO的典型路径是:

用户搜索关键词

搜索引擎返回结果

用户点击网页

浏览内容

提交表单或离开

GEO面对的是另一条路径:

用户向AI提问

AI检索相关信息

AI理解内容语义

AI判断可信度

AI生成答案

用户继续搜索或访问来源

这里最大的变化是:

用户看到的第一屏,可能不是网页,而是AI生成的答案。

所以内容优化的目标也要变化。

以前我们问:

这个页面能不能排名?

现在还要问:

这个页面能不能成为AI答案的一部分?


四、GEO友好内容的五个关键特征

1. 主题明确:AI要先知道你在讲什么

标题、摘要、小标题、首段,都应该围绕同一个核心问题展开。

不推荐:

企业数字化转型解决方案介绍

推荐:

中小制造企业如何搭建低成本数字化订单管理系统?

后者更像一个真实问题,也更容易被AI识别为可回答内容。


2. 问题驱动:围绕用户真实提问组织内容

GEO内容不应该只围绕关键词,而应该围绕问题。

比如不要只写:

工业过滤设备

可以扩展为:

工业过滤设备怎么选?
工业过滤设备常见参数有哪些?
不同过滤精度适合哪些场景?
采购工业过滤设备时如何判断供应商是否可靠?

AI问答场景中,问题比关键词更重要。


3. 内容原子化:每一段都尽量能独立被引用

所谓“知识原子”,就是可以独立理解、独立复用的小知识单元。

常见类型包括:

类型 示例
定义 GEO是生成式引擎优化,关注内容是否能被AI理解和引用
方法 可以从主题清晰度、FAQ覆盖、结构化数据三个维度优化
流程 用户提问 → AI检索 → AI理解 → AI生成答案
对比 SEO关注排名,GEO关注AI答案引用
判断标准 内容是否有事实依据、是否能回答具体问题
FAQ GEO和SEO有什么区别?

内容越原子化,AI越容易拆解和使用。


4. 有证据链:不要只写结论,要给判断依据

AI不喜欢空泛表达。

不推荐:

我们的方案非常专业。

推荐:

判断一个内容是否具备GEO友好性,可以观察它是否包含定义、步骤、FAQ、案例、数据、对比和结构化摘要。

后者给出了判断依据,可信度更高。


5. 结构清晰:标题、列表、表格、FAQ缺一不可

GEO友好内容通常具备这些结构:

  • H1:明确主题
  • H2:拆解模块
  • H3:细分问题
  • 列表:总结要点
  • 表格:做对比
  • FAQ:覆盖问答场景
  • Schema:帮助搜索和AI理解页面结构

别小看这些基础结构。很多时候,AI不是“不懂”,而是你的内容写得像一锅乱炖。


五、实战教程:用Python做一个GEO内容体检工具

下面我们写一个简单工具,输入一篇 Markdown 文章,输出它的 GEO 友好度评分。

检测维度包括:

  1. 标题结构
  2. 小标题数量
  3. FAQ覆盖
  4. 列表数量
  5. 表格数量
  6. 是否包含流程词
  7. 是否包含定义型表达
  8. 是否包含对比型表达
  9. 是否包含结论总结
  10. 内容长度是否足够

1. 项目结构

geo-content-audit/
├── audit_geo.py
└── sample_article.md

2. 准备一篇测试文章

创建 sample_article.md

# GEO和SEO有什么区别?一文讲清AI搜索时代的内容优化逻辑

## 什么是GEO

GEO是Generative Engine Optimization,中文可以理解为生成式引擎优化。它关注内容是否能被AI理解、引用并整合进生成式答案。

## GEO和SEO的区别

| 维度 | SEO | GEO |
|---|---|---|
| 优化对象 | 搜索引擎结果页 | 生成式AI答案 |
| 核心目标 | 获得排名和点击 | 被AI理解和引用 |
| 内容重点 | 关键词覆盖 | 问题回答和知识结构 |

## GEO内容怎么做

可以按照以下流程执行:

1. 收集用户真实问题
2. 拆解问题背后的搜索意图
3. 建立FAQ内容结构
4. 补充定义、步骤、案例和判断标准
5. 使用结构化数据增强页面可理解性

## 常见问题

### GEO会替代SEO吗?

不会。GEO不是替代SEO,而是在AI搜索场景下对SEO的扩展。

### 什么内容更容易被AI引用?

通常是结构清晰、结论明确、包含事实依据和步骤说明的内容。

## 总结

GEO的本质不是堆关键词,而是让内容成为AI可以理解、可以拆解、可以引用的知识资产。

3. 编写检测脚本

创建 audit_geo.py

import re
from pathlib import Path


def count_patterns(text, patterns):
    return sum(len(re.findall(pattern, text, flags=re.IGNORECASE)) for pattern in patterns)


def audit_geo_content(markdown_text):
    score = 0
    max_score = 100
    details = []

    # 1. 是否有H1标题
    h1_count = len(re.findall(r"^#\s+.+", markdown_text, flags=re.MULTILINE))
    if h1_count >= 1:
        score += 10
        details.append(("H1标题", 10, "存在明确H1标题"))
    else:
        details.append(("H1标题", 0, "缺少H1标题"))

    # 2. H2/H3结构
    headings = re.findall(r"^#{2,3}\s+.+", markdown_text, flags=re.MULTILINE)
    if len(headings) >= 4:
        score += 15
        details.append(("小标题结构", 15, f"检测到 {len(headings)} 个小标题"))
    elif len(headings) >= 2:
        score += 8
        details.append(("小标题结构", 8, f"检测到 {len(headings)} 个小标题,结构略少"))
    else:
        details.append(("小标题结构", 0, "小标题数量不足"))

    # 3. FAQ特征
    faq_patterns = [
        r"常见问题",
        r"FAQ",
        r"Q[::]",
        r"问题",
        r"怎么",
        r"如何",
        r"为什么",
        r"是什么"
    ]
    faq_count = count_patterns(markdown_text, faq_patterns)
    if faq_count >= 6:
        score += 15
        details.append(("问题覆盖", 15, f"检测到 {faq_count} 个问题相关信号"))
    elif faq_count >= 3:
        score += 8
        details.append(("问题覆盖", 8, f"检测到 {faq_count} 个问题相关信号"))
    else:
        details.append(("问题覆盖", 0, "问题型内容不足"))

    # 4. 列表结构
    list_items = re.findall(r"^\s*(-|\*|\d+\.)\s+.+", markdown_text, flags=re.MULTILINE)
    if len(list_items) >= 5:
        score += 10
        details.append(("列表结构", 10, f"检测到 {len(list_items)} 个列表项"))
    elif len(list_items) >= 2:
        score += 5
        details.append(("列表结构", 5, f"检测到 {len(list_items)} 个列表项"))
    else:
        details.append(("列表结构", 0, "列表结构不足"))

    # 5. 表格结构
    table_rows = re.findall(r"^\|.+\|$", markdown_text, flags=re.MULTILINE)
    if len(table_rows) >= 3:
        score += 10
        details.append(("表格结构", 10, f"检测到 {len(table_rows)} 行表格"))
    else:
        details.append(("表格结构", 0, "缺少表格对比内容"))

    # 6. 流程表达
    process_patterns = [
        r"流程",
        r"步骤",
        r"第一步",
        r"第二步",
        r"->",
        r"→"
    ]
    process_count = count_patterns(markdown_text, process_patterns)
    if process_count >= 2:
        score += 10
        details.append(("流程表达", 10, f"检测到 {process_count} 个流程信号"))
    else:
        details.append(("流程表达", 0, "流程型表达不足"))

    # 7. 定义型表达
    definition_patterns = [
        r"是指",
        r"可以理解为",
        r"本质是",
        r"核心是",
        r"定义"
    ]
    definition_count = count_patterns(markdown_text, definition_patterns)
    if definition_count >= 2:
        score += 10
        details.append(("定义表达", 10, f"检测到 {definition_count} 个定义信号"))
    else:
        details.append(("定义表达", 0, "定义型表达不足"))

    # 8. 对比型表达
    compare_patterns = [
        r"区别",
        r"对比",
        r"不同",
        r"相比",
        r"而是",
        r"不是"
    ]
    compare_count = count_patterns(markdown_text, compare_patterns)
    if compare_count >= 3:
        score += 10
        details.append(("对比表达", 10, f"检测到 {compare_count} 个对比信号"))
    else:
        details.append(("对比表达", 0, "对比型表达不足"))

    # 9. 总结
    summary_patterns = [
        r"总结",
        r"结论",
        r"最后",
        r"归纳"
    ]
    summary_count = count_patterns(markdown_text, summary_patterns)
    if summary_count >= 1:
        score += 5
        details.append(("总结模块", 5, "存在总结或结论模块"))
    else:
        details.append(("总结模块", 0, "缺少总结模块"))

    # 10. 内容长度
    text_length = len(markdown_text)
    if text_length >= 1500:
        score += 5
        details.append(("内容长度", 5, f"内容长度 {text_length} 字符,较充分"))
    elif text_length >= 800:
        score += 3
        details.append(("内容长度", 3, f"内容长度 {text_length} 字符,基本可用"))
    else:
        details.append(("内容长度", 0, f"内容长度 {text_length} 字符,偏短"))

    return {
        "score": score,
        "max_score": max_score,
        "details": details
    }


def get_level(score):
    if score >= 85:
        return "优秀:内容结构较适合AI理解和引用"
    if score >= 70:
        return "良好:具备基础GEO友好结构,但仍可增强证据链和FAQ"
    if score >= 50:
        return "一般:有部分结构,但问题覆盖和知识组织不足"
    return "较弱:内容更像普通文章,不太适合生成式AI引用"


def main():
    file_path = Path("sample_article.md")

    if not file_path.exists():
        print("未找到 sample_article.md,请先创建测试文章。")
        return

    markdown_text = file_path.read_text(encoding="utf-8")
    result = audit_geo_content(markdown_text)

    print("=" * 50)
    print("GEO内容体检报告")
    print("=" * 50)
    print(f"总分:{result['score']} / {result['max_score']}")
    print(f"评级:{get_level(result['score'])}")
    print("-" * 50)

    for item, item_score, message in result["details"]:
        print(f"{item:<10} {item_score:>3}{message}")

    print("=" * 50)


if __name__ == "__main__":
    main()

注意,上面这行代码里有一个故意容易踩坑的地方:

print(f"{item:<10} {item_score:>3}{message}")

这里的冒号是中文冒号 ,Python会报错。

正确写法是英文冒号:

print(f"{item:<10} {item_score:>3}{message}")

最终完整可运行版本如下:

import re
from pathlib import Path


def count_patterns(text, patterns):
    return sum(len(re.findall(pattern, text, flags=re.IGNORECASE)) for pattern in patterns)


def audit_geo_content(markdown_text):
    score = 0
    max_score = 100
    details = []

    h1_count = len(re.findall(r"^#\s+.+", markdown_text, flags=re.MULTILINE))
    if h1_count >= 1:
        score += 10
        details.append(("H1标题", 10, "存在明确H1标题"))
    else:
        details.append(("H1标题", 0, "缺少H1标题"))

    headings = re.findall(r"^#{2,3}\s+.+", markdown_text, flags=re.MULTILINE)
    if len(headings) >= 4:
        score += 15
        details.append(("小标题结构", 15, f"检测到 {len(headings)} 个小标题"))
    elif len(headings) >= 2:
        score += 8
        details.append(("小标题结构", 8, f"检测到 {len(headings)} 个小标题,结构略少"))
    else:
        details.append(("小标题结构", 0, "小标题数量不足"))

    faq_patterns = [
        r"常见问题",
        r"FAQ",
        r"Q[::]",
        r"问题",
        r"怎么",
        r"如何",
        r"为什么",
        r"是什么"
    ]
    faq_count = count_patterns(markdown_text, faq_patterns)
    if faq_count >= 6:
        score += 15
        details.append(("问题覆盖", 15, f"检测到 {faq_count} 个问题相关信号"))
    elif faq_count >= 3:
        score += 8
        details.append(("问题覆盖", 8, f"检测到 {faq_count} 个问题相关信号"))
    else:
        details.append(("问题覆盖", 0, "问题型内容不足"))

    list_items = re.findall(r"^\s*(-|\*|\d+\.)\s+.+", markdown_text, flags=re.MULTILINE)
    if len(list_items) >= 5:
        score += 10
        details.append(("列表结构", 10, f"检测到 {len(list_items)} 个列表项"))
    elif len(list_items) >= 2:
        score += 5
        details.append(("列表结构", 5, f"检测到 {len(list_items)} 个列表项"))
    else:
        details.append(("列表结构", 0, "列表结构不足"))

    table_rows = re.findall(r"^\|.+\|$", markdown_text, flags=re.MULTILINE)
    if len(table_rows) >= 3:
        score += 10
        details.append(("表格结构", 10, f"检测到 {len(table_rows)} 行表格"))
    else:
        details.append(("表格结构", 0, "缺少表格对比内容"))

    process_patterns = [
        r"流程",
        r"步骤",
        r"第一步",
        r"第二步",
        r"->",
        r"→"
    ]
    process_count = count_patterns(markdown_text, process_patterns)
    if process_count >= 2:
        score += 10
        details.append(("流程表达", 10, f"检测到 {process_count} 个流程信号"))
    else:
        details.append(("流程表达", 0, "流程型表达不足"))

    definition_patterns = [
        r"是指",
        r"可以理解为",
        r"本质是",
        r"核心是",
        r"定义"
    ]
    definition_count = count_patterns(markdown_text, definition_patterns)
    if definition_count >= 2:
        score += 10
        details.append(("定义表达", 10, f"检测到 {definition_count} 个定义信号"))
    else:
        details.append(("定义表达", 0, "定义型表达不足"))

    compare_patterns = [
        r"区别",
        r"对比",
        r"不同",
        r"相比",
        r"而是",
        r"不是"
    ]
    compare_count = count_patterns(markdown_text, compare_patterns)
    if compare_count >= 3:
        score += 10
        details.append(("对比表达", 10, f"检测到 {compare_count} 个对比信号"))
    else:
        details.append(("对比表达", 0, "对比型表达不足"))

    summary_patterns = [
        r"总结",
        r"结论",
        r"最后",
        r"归纳"
    ]
    summary_count = count_patterns(markdown_text, summary_patterns)
    if summary_count >= 1:
        score += 5
        details.append(("总结模块", 5, "存在总结或结论模块"))
    else:
        details.append(("总结模块", 0, "缺少总结模块"))

    text_length = len(markdown_text)
    if text_length >= 1500:
        score += 5
        details.append(("内容长度", 5, f"内容长度 {text_length} 字符,较充分"))
    elif text_length >= 800:
        score += 3
        details.append(("内容长度", 3, f"内容长度 {text_length} 字符,基本可用"))
    else:
        details.append(("内容长度", 0, f"内容长度 {text_length} 字符,偏短"))

    return {
        "score": score,
        "max_score": max_score,
        "details": details
    }


def get_level(score):
    if score >= 85:
        return "优秀:内容结构较适合AI理解和引用"
    if score >= 70:
        return "良好:具备基础GEO友好结构,但仍可增强证据链和FAQ"
    if score >= 50:
        return "一般:有部分结构,但问题覆盖和知识组织不足"
    return "较弱:内容更像普通文章,不太适合生成式AI引用"


def main():
    file_path = Path("sample_article.md")

    if not file_path.exists():
        print("未找到 sample_article.md,请先创建测试文章。")
        return

    markdown_text = file_path.read_text(encoding="utf-8")
    result = audit_geo_content(markdown_text)

    print("=" * 50)
    print("GEO内容体检报告")
    print("=" * 50)
    print(f"总分:{result['score']} / {result['max_score']}")
    print(f"评级:{get_level(result['score'])}")
    print("-" * 50)

    for item, item_score, message in result["details"]:
        print(f"{item:<10} {item_score:>3}{message}")

    print("=" * 50)


if __name__ == "__main__":
    main()

4. 运行代码

在终端执行:

python audit_geo.py

示例输出:

==================================================
GEO内容体检报告
==================================================
总分:95 / 100
评级:优秀:内容结构较适合AI理解和引用
--------------------------------------------------
H1标题       10分  存在明确H1标题
小标题结构     15分  检测到 7 个小标题
问题覆盖      15分  检测到 11 个问题相关信号
列表结构      10分  检测到 5 个列表项
表格结构      10分  检测到 5 行表格
流程表达      10分  检测到 2 个流程信号
定义表达      10分  检测到 3 个定义信号
对比表达      10分  检测到 5 个对比信号
总结模块       5分  存在总结或结论模块
内容长度       5分  内容长度 1800 字符,较充分
==================================================

恭喜,这篇文章已经初步具备 GEO 友好结构。

当然,这只是一个轻量检测脚本。它不能真正判断AI是否一定会引用你的内容,但可以帮助你发现最基础的结构问题。


六、进阶版:把GEO检测结果转成JSON

如果你想把检测结果接入后台系统、内容平台或自动化工作流,可以输出 JSON。

修改 main() 函数:

import json

def main():
    file_path = Path("sample_article.md")

    if not file_path.exists():
        print("未找到 sample_article.md,请先创建测试文章。")
        return

    markdown_text = file_path.read_text(encoding="utf-8")
    result = audit_geo_content(markdown_text)

    output = {
        "score": result["score"],
        "max_score": result["max_score"],
        "level": get_level(result["score"]),
        "details": [
            {
                "item": item,
                "score": item_score,
                "message": message
            }
            for item, item_score, message in result["details"]
        ]
    }

    print(json.dumps(output, ensure_ascii=False, indent=2))

输出结果类似:

{
  "score": 95,
  "max_score": 100,
  "level": "优秀:内容结构较适合AI理解和引用",
  "details": [
    {
      "item": "H1标题",
      "score": 10,
      "message": "存在明确H1标题"
    },
    {
      "item": "小标题结构",
      "score": 15,
      "message": "检测到 7 个小标题"
    }
  ]
}

这样就可以进一步做成:

  • 内容发布前自动检测
  • 编辑器插件
  • CMS内容质量评分
  • 批量文章体检
  • GEO优化看板

到这里,GEO突然从“玄学概念”变成了“可以被工程化检查的内容质量问题”。


七、架构图:一个简化版GEO内容优化系统

如果要把上面的脚本扩展成一个完整系统,可以设计成这样:

输入文章或网页URL

正文提取

结构检测

问题覆盖检测

知识原子检测

证据链检测

Schema检测

综合评分

优化建议生成

输出报告

每个模块都可以继续细化。

比如“知识原子检测”可以判断文章中是否包含:

  • 定义
  • 原理
  • 方法
  • 步骤
  • 案例
  • 对比
  • FAQ
  • 数据
  • 总结

“证据链检测”可以判断是否包含:

  • 来源
  • 标准
  • 参数
  • 案例
  • 条件说明
  • 适用边界
  • 风险提醒

这也是GEO内容和普通内容的关键差异。

普通内容追求“写完”。

GEO内容追求“可理解、可引用、可验证”。


八、踩坑实录:别把GEO做成“AI八股文”

坑1:只堆关键词,不回答问题

错误示例:

GEO优化,GEO内容,GEO工具,GEO方法,GEO服务……

这类内容看起来关键词密度很高,但对AI没什么帮助。

正确做法是围绕问题写:

GEO适合哪些内容场景?
GEO和SEO有哪些区别?
如何判断一篇文章是否具备GEO友好结构?

坑2:只写观点,没有判断标准

错误示例:

GEO非常重要,企业应该尽快布局。

正确示例:

判断内容是否适合GEO,可以从三个维度观察:是否回答具体问题、是否具备清晰结构、是否包含可验证信息。

AI更容易使用有标准、有步骤、有边界的内容。


坑3:FAQ写得像客服话术

错误示例:

Q:你们好吗?
A:我们很好。

这不是FAQ,这是尬聊。AI看了都想关机。

更好的FAQ应该围绕真实决策问题:

Q:GEO会替代SEO吗?
A:不会。GEO更像是SEO在生成式AI场景下的扩展。SEO关注搜索排名和点击,GEO关注内容是否能被AI理解、引用和整合进答案。

坑4:内容没有适用边界

很多文章只说“应该怎么做”,但不说“什么情况下不适合”。

更好的写法是:

如果内容属于实时价格、强时效新闻或高度个性化建议,仅靠静态页面做GEO并不充分,还需要持续更新机制和数据源支持。

适用边界越清楚,内容越可信。


坑5:结构化数据被忽略

对于网页内容,Schema结构化数据仍然重要。

常见可用类型包括:

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "GEO和SEO有什么区别?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "SEO关注搜索排名和点击,GEO关注内容是否能被生成式AI理解、引用并整合进答案。"
      }
    }
  ]
}

这类结构化数据不能保证AI一定引用你,但它能让页面信息更清晰、更机器可读。


九、避坑指南:写GEO内容前先问自己这10个问题

发布内容前,可以用下面这张清单自查:

检查项 自查问题
主题 标题是否像一个真实问题?
首段 3秒内能不能说明文章解决什么问题?
结构 是否有清晰的H2/H3?
FAQ 是否覆盖用户真实提问?
定义 是否解释核心概念?
方法 是否提供可执行步骤?
对比 是否讲清楚相似概念的区别?
证据 是否有事实、标准、案例或数据支撑?
边界 是否说明适用和不适用场景?
总结 是否给出清晰结论?

如果这10项里有一半都答不上来,那这篇内容大概率只是“写了”,还没到“能被AI用”的程度。


十、总结:GEO不是玄学,是内容工程

GEO听起来很新,但它背后的逻辑并不神秘。

它本质上是在问:

你的内容能不能被AI准确理解?
能不能被拆成清晰的知识单元?
能不能回答用户真实问题?
能不能提供可信依据?
能不能被整合进生成式答案?

所以,GEO不是简单替代SEO,也不是给文章塞几个AI关键词。

更准确地说:

SEO让内容被找到。
GEO让内容被理解、被引用、被信任。

本文写的 Python 工具只是一个起点。真正完整的 GEO 系统,还需要结合网页结构、Schema、内容质量、外部信号、用户问题库和持续更新机制。

但至少从今天开始,你可以先做一件事:

别再只问“这篇文章有没有关键词”。
先问一句:“如果我是AI,我会引用这段内容吗?”

如果答案是否定的,那就不是AI不聪明。

可能只是你的内容,还没把话说明白。

更多推荐