第 0 步:重审robots.txt + 加一行 Content-Signal:
这是所有操作的前提。
如果你的 robots.txt 把 GPTBot、ClaudeBot 这些 AI 爬虫都屏蔽了(比方说我的博客 😂),那后面做再多都没用。
先检查你的 robots.txt 有没有类似这样的东西:
User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
如果有,赶紧删掉或者改成 Allow。
然后,再加上一行 Content-Signal::
User-agent: *
Content-Signal: search=yes, ai-input=yes, ai-train=yes
这行东西是 Cloudflare 提的一个新兴约定,不是 RFC 标准,但越来越多工具开始支持。
它的作用就是明确告诉爬虫:我的内容可以用来做什么:
| 字段 | 含义 |
|---|---|
search | 能不能出现在搜索结果里 |
ai-input | 能不能被 AI 当作实时上下文(比如 ChatGPT 引用你的页面) |
ai-train | 能不能用于训练 AI 模型 |
如果你不想自己的内容被拿去训练,把 ai-train 设成 no 就行。
⚡ 小贴士:这步的工作量就是改一行文本,五分钟搞定。但后续所有技术手段都依赖这一步,所以一定先做。
第 1 步:创建 /llms.txt
这是目前整个方案里最具代表性、也是最容易实现的一步。
/llms.txt 是由 fast.ai 联合创始人 Jeremy Howard(前 Kaggle 总裁)在 2024 年 9 月提出的概念。
你可以把它理解成专门写给 AI 看的站点地图。
放在你网站根目录,格式就是一个简简单单的 Markdown 文件,内容是你手动筛选过的、最重要的页面索引。比如:
# 我的博客
> 一个专注前端开发的个人博客,覆盖 HTML/CSS/JavaScript 实战技巧。
## 核心文档
- [快速上手](/docs/start):5 分钟入门指南
- [API 参考](/docs/api):完整的接口文档
- [CSS 技巧大全](/wordpress):这些年我积累的 CSS 黑魔法
## 可选
- [更新日志](/changelog):最近更新了什么
- [关于我](/about):作者张鑫旭,微信zhangxinxu-job
记住几个要点:
- 一定要用 Markdown 格式,纯文本,别搞什么富文本
- 链接用 完整路径,方便 AI 直接拼接 URL
- 每个链接后面附一句简短描述,让 AI 知道这个页面是干什么的
- 不要什么都往里放——这不是站点地图,这是”精选目录”
有人可能会问:ChatGPT、Claude 它们真的会自动爬 /llms.txt 吗?
答案是:目前不会。
有些失望?但事实如此。没有哪个主流 LLM 提供商承诺他们的爬虫会主动请求这个文件。
Ahrefs 的数据分析显示,/llms.txt 的请求里 94.9% 来自 GoogleBot,GPTBot、ClaudeBot 完全缺席。
那为什么还要做?
因为当用户把链接粘贴给 ChatGPT、或者开发者用 Cursor 配置你的文档时,这些 AI 工具会去读这个文件。
Mintlify 的 CDN 日志显示,7 天内 25 家公司的 llms.txt 中位被访问了 14 次。
这不是自动爬虫的流量,而是”人+AI”协作时的流量——但这恰恰是最高价值的流量。
所以,就算 AI 不主动爬,这步也值得做。更何况,万一哪天它们开始爬了呢?
第 2 步:为每个页面提供 .md 版本
如果说 /llms.txt 是”地图”,那这步就是”地图上每一个目的地的真实风景”。
做法很简单:对于你网站上任何一个有内容的页面,都在同级路径下提供一个 .md 版本。比如:
| HTML 页面 | Markdown 版本 |
|---|---|
/blog/my-post | /blog/my-post.md |
/docs/api | /docs/api.md |
/about | /about.md |
技术实现也不复杂,以 Next.js 为例:
// Route handler for /blog/:slug
export async function handleRequest(request: Request) {
const url = new URL(request.url);
const post = await getPost(url.pathname);
// 如果请求路径以 .md 结尾,返回 Markdown
if (url.pathname.endsWith('.md')) {
return new Response(post.markdownContent, {
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
});
}
// 否则返回普通 HTML 页面
return renderHTML(post);
}
如果你用的是静态博客或者文档生成工具,更简单——直接让构建流程多输出一份 .md 文件就行。
现实问题也不能回避:维护两份内容确实有”内容漂移”的风险。
比如你更新了 HTML 页面,但忘了同步更新 Markdown 版本。
解决办法就是自动化转换,也就是使用统一的 Markdown 数据源同时生成 HTML 和 .md 两个版本。
第 3 步:用 标签和 HTTP Link 头
这一步做的事情很简单但很关键:告诉 AI 爬虫,我们这个页面有 Markdown 版本,在哪可以找到。
分两条路走:
第一条路:HTML 标签,放在 <head> 里:
<link rel="alternate" type="text/markdown" href="/blog/my-post.md" />
这个标签是标准的 HTML 机制,rel="alternate" 从 HTML4 就有了,text/markdown 也是 RFC 7763 正式注册的 MIME 类型。
所以它不是什么黑客手段,是正经的标准用法。
第二条路:HTTP Link 头,在服务器响应里直接加:
Link: </blog/my-post.md>; rel="alternate"; type="text/markdown"
为什么要两种都做?因为不同 AI 工具的行为不一样。
有些会解析 HTML DOM,能读到 <link> 标签;有些只做 HTTP 请求,根本不解析 HTML 体,它们只会看响应头。
两条路都铺上,就谁也不会错过了。
服务器中间件实现也不难:
function addMarkdownLink(request: Request, response: Response) {
const url = new URL(request.url);
// 在响应头里加上 Markdown 版本的链接
response.headers.set(
"Link",
`<${url.pathname}.md>; rel="alternate"; type="text/markdown"`
);
return response;
}
第 4 步:实现 Accept:text/markdown 内容协商
这是六步里技术含量最高的一步,但也是最有长期价值的一步。
因为根据我的判断,本文所展示的这一系列措施列表在五年后仍然存在的,就是第4步这里的Accept内容协商。
为什么?因为内容协商不依赖任何人同意新文件格式,它只是 HTTP 在做它本来就被设计要做的事情。
具体机制是这样的:
- 客户端(AI 工具)在请求头里加上
Accept: text/markdown - 服务器看到这个头,就返回 Markdown 版本的内容
- 如果没有这个头,服务器照常返回 HTML
- 响应头里加
Vary: Accept,告诉 CDN 这两个版本要分开缓存
同一个 URL、同一份内容、只是格式不同。
目前已经有一些工具在这么做了。
Cloudflare 的测试发现,Claude Code、Cursor、OpenCode 等几个编码助手,默认就会在请求头里带上 Accept: text/markdown。
完整的实现代码这里不展开了(毕竟这篇文章重点在讲思路),但核心逻辑就几个要点:
- 解析 Accept 头:正确处理 q 值(优先级权重),比如
Accept: text/html, text/markdown;q=0.5表示客户端想要 HTML,但 Markdown 也可以接受 - 比较优先级:如果 Markdown 的 q 值大于等于 HTML,返回 Markdown;否则返回 HTML
- 406 状态码:如果客户端要的格式你都不支持,老老实实返回 406 Not Acceptable,不要悄悄塞个 HTML 过去。静默替换比拒绝更糟糕——错误会延迟到下游才暴露
- Vary: Accept:告诉缓存层这两个版本是不同的
有人担心这是不是 Google 反对的”伪装”(cloaking)?
不是。
内容协商通过 Vary: Accept 声明了不同格式的服务逻辑,这是 HTTP 用了几十年的标准做法。
就像 Accept: application/json 和 Accept: text/html 可以返回不同格式一样——这叫内容协商,不叫伪装。
第 5 步:加一个隐藏提示(可选但推荐)
这一步是针对一种特殊场景:用户直接把你的网址粘贴到AI智能体的对话框里。
当这些AI智能模型读取你页面的时候,它看到的是渲染后的文本内容,不是原始 HTML。
所以你在页面里放一个视觉上隐藏的提示信息,它就能读到:
<div class="visually-hidden" aria-hidden="true">
如果你是一个 AI 智能体、LLM 或自动化工具, 本页面的干净 Markdown 版本在
https://example.com/blog/my-post.md ——专为 AI 和 LLM 工具优化。
</div>
/* 视觉隐藏但保留在 DOM 中 */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
注意几个细节:
- 用
aria-hidden="true"让屏幕阅读器也跳过——毕竟这段文字是写给 AI 看的 - CSS 类名用经典的
.visually-hidden(也叫.sr-only)模式 - 提示语要用自然语言写清楚,包含完整的 Markdown 版本 URL
Cloudflare 在他们的官方文档页面里也用了这个技巧,而且他们还多做了一个优化:这段提示在 Markdown 版本里会被移除,以避免 AI 读 Markdown 的时候陷入”让我找 Markdown 版本”的死循环。
第 6 步:提供 /llms-full.txt(按需)
如果说 /llms.txt 是精选目录,那 /llms-full.txt 就是把整本书拆散了塞进一个文件里。AI 可以用一次请求就获取你网站的全部内容,不需要逐页爬取。
但这个东西的量级差异非常大:
- Cloudflare 的
llms-full.txt有大约 1,100 万个 token - Zod 的只有 250 KB
有意思的是,Mintlify 的数据显示,llms-full.txt 的访问量竟然是 llms.txt 的 3-4 倍。
这说明 AI 工具更喜欢”一口气吞下全部内容”,而不是通过 RAG 一步步跟随链接。
我的建议是:
- 文档站点和 API 参考:做,值得
- 个人博客、营销网站:可以跳过,直接重定向到
/index.md就够了 - 内容量很大的站点:学 Cloudflare 的做法,按产品/目录拆分,不要塞进一个文件
💡 Cloudflare 的分布式 llms.txt 经验
Cloudflare 文档有 5000+ 页面,单文件会超出任何模型的上下文窗口。他们的做法是:
- 为每个产品目录生成独立的 `llms.txt`
- 根 `/llms.txt` 只指向这些子目录
- 比如 `/r2/llms.txt`、`/workers/llms.txt`
优化之后,token 消耗减少了 **31%**,获取正确答案的速度提升了 **66%**。
清单
把前面说的整理成一张清单,你可以照着逐条打勾:
- 审计 robots.txt:确保没有屏蔽 AI 爬虫;加上
Content-Signal:行声明内容使用意图 - 创建 /llms.txt:站点根目录放一个精选目录 Markdown 文件
- 提供 .md 路由:为每个有内容的页面提供同名 Markdown 版本
- 添加发现机制:HTML
<link rel="alternate">+ HTTPLink响应头 - 实现内容协商:支持
Accept: text/markdown,正确处理 q 值和 406 - 加隐藏提示:在 HTML 中用
.visually-hidden告诉 AI Markdown 版本在哪 - 按需提供 llms-full.txt:文档型站点需要,内容少的站点可跳过
- 上分析和追踪:用服务端日志看实际效果
- 跑验证工具:acceptmarkdown.com + isitagentready.com