大多数 SEO 指南都是清单式的:加这个 meta 标签、装那个插件、改这个配置。但清单无法帮你理解 为什么 ——不理解为什么,你永远不知道自己遗漏了什么。
最近我对这个博客做了一次全面的 SEO 改造。比起逐条记录”发现了什么、修了什么”,我更想分享的是背后的思维模型。一旦用这个视角看 SEO,所有优化都变得显而易见。
第一性原理:搜索引擎是程序
这是唯一需要记住的事。Google、Bing、Baidu——它们本质上都是程序,做三件事:
- 发现你的页面(爬取)
- 解析并理解页面内容(索引)
- 根据相关性和质量排序(排名)
SEO 不是在玩弄算法,而是让你的内容的数据结构显式化、机器可读。搜索引擎的爬虫本质上是一个 parser——和所有 parser 一样,输入的结构越清晰,它工作得越好。
从这个原理出发,我们可以把所有 SEO 工作分成三个层次:
1 | 第三层:分发层 — 你的内容在社交平台上如何呈现? |
每一层都依赖下面一层。我们自底向上来看。
第一层:发现(Discoverability)
搜索引擎在排名你的内容之前,首先得找到它。有几个机制协同工作来实现这一点。
Sitemap:站点的目录
Sitemap 是一个 XML 文件,告诉爬虫你站点上的每个 URL,以及每个页面最后修改的时间。可以类比为一本书的目录——爬虫当然可以通过跟随链接找到你的页面,但 sitemap 把这件事变得显式而完整。
1 | <url> |
但光生成 sitemap 不够——你还需要告诉爬虫去哪里找它。有两个位置:
robots.txt——任何爬虫访问站点时读取的第一个文件:1
Sitemap: https://your-site.com/sitemap.xml
HTML
<head>——给从页面本身开始爬取的爬虫:1
<link rel="sitemap" type="application/xml" href="/sitemap.xml" />
这里有一个容易忽略的细节:lastmod 字段的数据源。如果用文件修改时间(mtime)作为来源,每次 git clone 或 CI 重新构建都会重置所有时间戳——爬虫会以为你整个站点都刚更新过,这反而是一个负面信号。我把 Hexo 配置从 updated_option: 'mtime' 改为 updated_option: 'date',让日期来自文章 front-matter——确定性的、版本控制的。
RSS Feed:被遗忘的发现渠道
RSS/Atom 订阅源经常被认为是早期 Web 的遗物。但对技术博客来说,它有两个重要用途:
- 内容聚合器(Feedly、dev.to 等)通过 feed 发现和索引你的内容。
- 搜索引擎把 feed 作为补充的内容发现机制,特别是对新鲜内容。
HTML <head> 中需要声明 feed,这样浏览器和爬虫才能自动发现:
1 | <link rel="alternate" type="application/atom+xml" title="Your Blog" href="/atom.xml"> |
我的博客之前完全没有 feed。加上 hexo-generator-feed 插件并配置好之后,内容多了一个完全独立的被发现渠道。
URL 结构:每个页面的唯一身份
每个页面需要且只需要一个 canonical URL。同一内容的多个 URL 会稀释排名信号,让爬虫困惑。
Hexo 中一个常见的错误:trailing_index: true 会生成类似 /2024/04/10/my-post/index.html 的 URL。而”干净版” /2024/04/10/my-post/ 是同一个内容、不同的 URL——瞬间产生了重复内容问题。修复很简单:
1 | pretty_urls: |
同时,每个页面应该包含一个 <link rel="canonical"> 标签指向它的权威 URL。这是给爬虫的明确信号:”如果你在多个 URL 找到了这个内容,这个才是算数的。”
第二层:理解(Understanding)
爬虫找到你的页面后,需要理解页面说的是什么、包含什么类型的内容、不同信息之间是什么关系。这是大多数博客最薄弱的环节——也是提升空间最大的地方。
语义化 HTML:用爬虫听得懂的语言说话
HTML5 引入语义元素是有原因的。<div> 告诉 parser 的信息量是零。<article> 告诉它”这是主要内容”。<nav> 说”这是导航”。<time> 说”这是一个日期”。
搜索引擎用这些元素来构建页面的内部表示。没有它们,爬虫只能基于启发式规则猜测——而启发式是有损的。
我的文章模板改造前后对比:
1 | <!-- 改造前 --> |
1 | <!-- 改造后 --> |
这里有几个关键变化:
<article> 包裹 ——显式标记内容的边界。这在有导航、侧边栏、页脚的页面上尤其重要——爬虫需要分清哪部分是真正的内容,哪部分是页面装饰。
<h1> 替代 <h2> ——标题层级很重要。每个页面应该有且只有一个 <h1> 代表页面的主题。用 <h2> 做文章标题是层级违规——暗示文章标题从属于某个不存在的 <h1>。
<time> 及 datetime 属性 ——人类可读的日期 “Apr 10, 2024” 是有歧义的(月在前还是日在前?)。datetime 属性提供了一个无歧义的 ISO 8601 时间戳,任何 parser 都能可靠地解析。
导航的 <nav> 标签 ——我还把头部导航包在了 <nav aria-label="Main navigation"> 里。这同时服务于 SEO(爬虫可以识别并降权导航链接,在分析内容时不被干扰)和无障碍访问(屏幕阅读器可以直接跳到导航或跳过导航)。
JSON-LD 结构化数据:显式的数据契约
语义化 HTML 已经很好了,但它仍然是隐式的——爬虫需要推断关系。JSON-LD 把这些关系变成显式的。它是你和搜索引擎之间的契约:”这是这个页面的结构化数据,不需要推断。”
对于博客,两种 schema 最重要:
首页的 WebSite schema ——告诉搜索引擎你的站点叫什么、在哪里:
1 | { |
每篇文章的 BlogPosting schema ——为单篇文章提供完整的元数据图:
1 | { |
JSON-LD 的关键价值在于它让富媒体结果(Rich Results)成为可能。没有它,Google 只能把你的页面显示为一条普通的蓝色链接。有了它,Google 可以渲染富摘要——作者信息、发布日期、文章面包屑导航——显著提升搜索结果中的点击率。
你可以用 Google 的 Rich Results Test 来验证结构化数据。如果测试结果显示零个合格的富媒体结果,说明你在白白浪费流量。
<title> 标签:搜索结果中的标题
<title> 标签是 SEO 中最显眼的部分——它就是用户在搜索结果中看到的可点击标题。两个规则:
- 每个页面必须唯一。 如果所有页面标题一样,Google 无法区分它们。
- 应该包含站点名称。 这有助于品牌识别,帮助用户在一堆搜索结果中认出你的内容。
我的博客之前文章页面只显示文章标题——“React Performance Optimization”。修改后变成 “React Performance Optimization | Koh Hom’s Blog”。每种页面类型都有描述性的、唯一的标题:
1 | if (is_post()) { |
页面级的 Meta Description 和 Keywords
Meta description 不直接影响排名,但它直接影响点击率——Google 经常把它用作搜索结果标题下面的摘要文本。好的 description 是一段 150-160 字符的总结,告诉读者点进来能得到什么。
Keywords 同理。虽然 Google 已经不看 <meta name="keywords">,但 Bing 和百度仍然参考。更重要的是,页面级别的关键词是主题相关性的信号。我的博客之前每个页面都用全站统一的关键词。修改后,文章页面用自己的标签生成关键词:
1 | <!-- 文章页:使用文章标签 --> |
第三层:分发(Distribution)
SEO 不仅仅是关于 Google。当有人在 Twitter、LinkedIn 或 Slack 分享你的文章时,这些平台也会爬取你的页面——它们用另一套 meta 标签来生成链接预览卡片。
Open Graph:社交媒体的数据契约
Open Graph(OG)标签是 Facebook 发明的协议,但现在被 Twitter、LinkedIn、Slack、Discord 和所有主流平台通用支持。它们控制链接预览卡片中显示什么内容。
我的博客中最常见的错误:**og:type 在所有页面都被设为 "article"**,包括首页和归档页。这在语义上是错的——首页是 website,不是 article。设错不会导致明显的错误,但会降低社交分享预览的质量,发送错误的信号。
更关键的是,博客文章应该包含时间元数据。没有 article:published_time 和 article:modified_time,平台无法展示内容的写作时间——而对于技术内容,时效性极其重要。读者会跳过看起来过时的文章。
1 | <!-- 只在真正的博客文章页面 --> |
Twitter Cards:平台定制化
Twitter (X) 有自己的卡片系统,叠加在 Open Graph 之上。大多数博客缺少的关键标签:twitter:site(把卡片和你的 Twitter 账号关联)和 twitter:creator(标注内容作者)。没有它们,分享链接时就失去了和个人主页的关联——白白错过涨粉和内容归属的机会。
性能维度
Google 的 Core Web Vitals 已经是确认的排名因素。我发现的两个性能相关的 SEO 问题值得展开讨论,因为它们体现了一个更广泛的原则。
资源提示(Resource Hints):降低外部依赖的延迟
页面加载时,浏览器需要为每个外部域名解析 DNS、建立 TCP 连接、执行 TLS 握手。仅 Google Analytics 一项,就是 www.googletagmanager.com——在任何数据传输之前大约 100-300ms 的延迟。
资源提示让你提前启动这个过程:
1 | <link rel="dns-prefetch" href="//www.googletagmanager.com"> |
dns-prefetch 只处理 DNS 解析。preconnect 更进一步——还建立 TCP 连接和 TLS 握手。原理和 Web 性能优化中的 prefetch 一样:把工作提前到时间线更早的位置,让它不再阻塞关键路径。
字体预加载:一个隐蔽的路径解析 Bug
我的博客的字体预加载链接使用了相对路径:../fonts/font.woff2。这在首页(/index.html → 解析为 /fonts/font.woff2)能正常工作,但在嵌套页面(/2024/04/10/post/index.html → 解析为 /2024/04/10/fonts/font.woff2,404)就挂了。
预加载失败时,浏览器回退到按需加载字体,造成 FOUT(Flash of Unstyled Text)并增加 CLS(Cumulative Layout Shift)——三个 Core Web Vitals 指标之一。
修复方法是通过 Hexo 的 config.root 使用绝对路径:
1 | <link rel="preload" href="<%- config.root %>fonts/font.woff2" as="font" type="font/woff2" crossorigin> |
这是一个通用教训:HTML <head> 中引用的资源永远使用绝对路径,因为 <head> 在每个嵌套层级的页面间是共享的。
第四层:AI 搜索就绪(GEO)
有一个新的层次是两年前不存在的。AI 驱动的搜索——Google AI Overviews、ChatGPT 网页搜索、Perplexity——正在从根本上改变内容被发现和引用的方式。这被称为生成式引擎优化(Generative Engine Optimization, GEO),它基于一套不同的原则运作。
llms.txt:给 AI 爬虫的 README
就像 robots.txt 告诉传统爬虫它们能访问什么一样,llms.txt 告诉 AI 爬虫你的站点是关于什么的。它是放在站点根目录的纯文本文件,提供一个结构化的、人类可读的摘要——针对 LLM 的上下文窗口优化,而非 HTML 解析器。
1 | # 你的博客名 |
AI 爬虫访问规则
默认情况下,许多站点会屏蔽 AI 爬虫。如果你希望你的内容被 AI 生成的回答引用,需要在 robots.txt 中显式允许它们:
1 | User-agent: GPTBot |
这是一个有意识的取舍。你在授权 AI 系统训练或引用你的内容。对于个人技术博客来说,可见性的收益远大于成本。
可引用性:为 AI 提取而写作
AI 搜索引擎不只是排名页面——它们提取并引用特定的段落。要被引用:
- 先给答案。 文章顶部的 TL;DR 不仅对读者友好——它给 AI 模型提供了一个干净的、自包含的可引用段落。
- 使用清晰的标题层级。 AI 模型按标题分割内容。模糊的标题如”讨论”或”更多信息”被引用的概率远低于具体的标题如”JSON-LD 如何实现富媒体结果”。
- 显式地结构化数据。 表格、有序列表和代码块比自由流动的段落更容易被模型提取。
用 Claude Code Skills 自动化 SEO 审计
在完成初始的手动改造之后,我发现了 claude-seo——一个社区构建的 Claude Code skill,能自动化全面的 SEO 审计。它是我在 Agent Skills 一文中讨论的 Skills 范式的完美案例。
它做什么
claude-seo 是一个 skill 生态系统,包含 13 个子 skill 和 7 个专业 subagent:
/seo-audit— 全站爬取,并行 subagent 委派/seo-technical— 可爬取性、可索引性、Core Web Vitals、安全头/seo-content— E-E-A-T 评估、可读性、薄内容检测/seo-schema— JSON-LD 检测、验证和生成/seo-hreflang— 国际化 SEO 验证/seo-geo— AI 搜索就绪(GEO)、可引用性评分/seo-page— 单页深度分析
安装
一行命令:
1 | curl -fsSL https://raw.githubusercontent.com/AgriciDaniel/claude-seo/main/install.sh | bash |
它安装到 ~/.claude/skills/seo/,为依赖设置 Python venv,可选安装 Playwright 用于视觉分析。
它在这个博客上发现了什么
对我的博客运行 /seo-audit https://clean99.github.io 后,它确认并扩展了我手动审计的发现。关键收获:skill 驱动的审计能发现你手动检查永远想不到的问题——AI 爬虫可访问性、段落级可引用性、所有页面组合的 hreflang 验证等。
这个 skill 还验证了我之前所有的修复(JSON-LD、hreflang、og:type 等)都正确实现了——本质上为 SEO 提供了自动化回归测试。
为什么 Skills 适合 SEO
SEO 是 Skills 的完美用例,因为:
- 它是固定流程。 审计过程每次都遵循相同的模式:爬取 → 分析 → 评分 → 建议。
- 它需要领域专业知识。 Skill 封装了 SEO 知识(E-E-A-T 标准、schema 验证规则、GEO 最佳实践),否则你需要反复查阅。
- 它是可组合的。 单独的子 skill(
/seo-schema、/seo-hreflang)可以在修改后独立调用进行定向检查。
这完全符合 Skill 的设计原则:单一职责、清晰接口、可复用性。claude-seo 本质上就是这篇文章中的 SEO 思维模型,被操作化为可执行代码。
全貌
自底向上看完整的心智模型:
1 | ┌──────────────────────────────────────────────────┐ |
每一项 SEO 优化都落在这些层中的某一个。当你遇到新的 SEO 建议时,问自己:这属于哪一层?它是帮助爬虫发现我的内容、理解它、分发它、还是让 AI 能引用它?这个框架把一堆看似随机的最佳实践变成了一个连贯的系统。
根本性的收获:SEO 就是面向机器的数据结构设计。你的 HTML 是搜索引擎消费的 API。Meta 标签是响应头。JSON-LD 是响应体。语义元素是 schema 定义。llms.txt 是 API 文档。你把这个 API 设计得越好,机器——无论是传统爬虫还是 AI 模型——就越能理解和呈现你的内容。
而有了 claude-seo 这样的工具,你可以自动化验证整个技术栈。思维模型给你理解力;skill 给你执行力。