# 你的博客该不该给 AI 一份 markdown

日期：2026-09-12  
标签：web、agent、http

> 内容协商是 HTTP 自带的能力，但博客圈几乎没人用。三个 agent 会主动要 markdown，四个不会——而现有工具都跳过了最难的那一步。

来源：https://xiaoxusop.github.io/letterpress/markdown-for-agents/
语言：zh-CN
类型：文章

## 被引用

- [内容协商](https://xiaoxusop.github.io/letterpress/wiki/content-negotiation/)

## 正文

一个 AI agent 抓取你的网页时，看到的东西和你看到的不一样。它看到的是两万个
token 的导航栏、页脚和 `<div>` 嵌套，而正文可能只占其中百分之几。

这不是比喻。Cloudflare 实测自己的文档：同一页 HTML 要 16,180 token，
markdown 版只要 3,150 token——**省 80%，内容一模一样**。

HTTP 从 1.1 起就有解决这个问题的机制：内容协商。客户端在 `Accept` 头里说
「我更想要 markdown，不行的话 HTML 也行」，服务端照办。

问题是：**博客圈几乎没人用。**

## 谁在要 markdown

拿七个常见 agent 去请求 `httpbin.org/headers`，
看它们各自发出的 `Accept` 头。结果是这样的：

| Agent | 要 markdown 吗 | Accept 头 |
|---|---|---|
| Claude Code | 是 | `text/markdown, text/html, */*` |
| Cursor | 是 | `text/markdown`, `text/html;q=0.9`, … |
| OpenCode | 是 | `text/markdown;q=1.0`, `text/x-markdown;q=0.9`, … |
| OpenAI Codex | 否 | `text/html`, `application/xhtml+xml`, … |
| GitHub Copilot | 否 | `text/html`, `application/xhtml+xml`, … |
| Gemini CLI | 否 | 通配符 |
| Windsurf | 否 | 通配符 |

七个里三个主动要。**而这七行数据本身就是一份测试用例**——见
[内容协商](https://xiaoxusop.github.io/letterpress/wiki/content-negotiation/) 里关于怎么把它钉成回归测试的部分。

## 一个安静失败的实现细节

Claude Code 那一行值得单独说。

它发的是 `text/markdown, text/html, */*`——**没有 q 值**。在 HTTP 里，
缺省 q 值等于 1.0，于是 markdown 和 html 两个媒体范围并列。

**这里要说清楚：RFC 并没有规定并列时怎么打破平局。**
[RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1)
规定的是「媒体范围可以被更具体的范围覆盖，**更具体的优先**」——
它按*具体程度*排序（`text/plain;format=flowed` → `text/plain` → `text/*` → `*/*`），
而对 q 值相同的两个类型，规范只说它们是 *equally preferred*，没有给顺序。

所以「靠前的优先」是**本项目的实现选择**，不是规范要求。

但如果实现里写的是严格的 `>` 比较：

```js
if (markdown.q > html.q) return 'markdown';
return 'html';
```

那么 `1.0 > 1.0` 为假，判定落到 HTML 上。**Claude Code 永远拿不到 markdown，
而服务端不报错、不警告、日志里什么都没有。** 你只会觉得「这功能好像没用」。

本项目的做法是**并列时比顺序**（如上所述，这是选择而非规范要求）：

```js
if (markdown.q !== html.q) return markdown.q > html.q;
return markdown.index < html.index;
```

这类 bug 的可怕之处在于它**不会以错误的形式出现，只会以「没效果」的形式出现**。

## 为什么现成的工具都不太行

Astro 生态里已经有两个包在做这件事，但都停在半路：

- **astro-markdown-for-agents**：在构建期生成 `.md` 文件，但协商**只在 dev server 里生效**。
  它的 README 直说：「不包含托管平台运行时垫片；静态托管环境下的服务时协商
  应由使用方自行实现。」也就是说**上生产就没了**。
- **astro-markdown-export**：把源 `.md` 复制进产物目录，**完全不做协商**。
  要访问 `/slug/index.md` 才拿得到，而且导出的是未渲染的源文件。

两者的周下载量分别是 106 和 17。而主流 Astro 集成是三十万到两百万。
**这不是「已经解决了只是没人知道」，是「有人碰过就放弃了」。**

## 难在哪

难的不是算法——算法上面已经写完了，四十行。

难的是**静态托管不解析请求头**。静态站就是一堆文件，CDN 把文件原样吐出来，
它不会因为你 `Accept` 写了什么就换一个文件给你。所以你需要一层边缘函数。

这也是为什么 Cloudflare 的 Markdown for Agents 要 Pro 及以上套餐——
它是在边缘替你做了转换。而 Vercel 的实现只在自己的平台内生效。
**跑在免费静态托管上的博客，两头都够不着。**

出路是：构建期生成 `.md` 孪生文件，再用一层几十行的边缘函数做选择。
转换在构建期完成（零运行时成本），边缘只负责「给哪个」。
Cloudflare Pages Functions、Netlify Edge Functions、Vercel Middleware
的免费额度都够用。

## 两件必须做对的事

**一、`Vary: Accept` 不能省。** 少了它，CDN 会把 markdown 版本缓存下来
发给浏览器——用户打开博客看到一坨纯文本。这是个会让人当场关掉标签页的 bug，
而它只在缓存命中时出现。

**二、`*/*` 不算「想要 markdown」。** Gemini CLI 和 Windsurf 只发通配符，
意思是「给什么都行」。如果把通配符也算作 markdown 偏好，这两家会被塞
markdown——它们本来接受 HTML，你硬给别的格式，属于自找麻烦。

## 顺带说一句 llms.txt

你可能想问为什么不提 `llms.txt`。因为它的实际效果被严重高估了：

- **Google 明说不需要。** Search Central 的 AI 功能文档里写着：

  > You don't need to create new machine readable files, AI text files,
  > or markup to appear in these features.

  注意原文说的是「**不需要**」而不是「不支持」——不做也不会因此缺席，
  但 Google 也没说它有害。
  （[原文](https://developers.google.com/search/docs/appearance/ai-features)）
- 多个独立实验都没测到可归因的效果。

> **这组数字曾经被删掉过，2026-09-24 找回来了。** 这里原先写
> 「Ahrefs 2026 年 5 月实测 137,210 个域名：97% 的 llms.txt 从未被请求过」。
> 上一轮我按几种常见路径试 `ahrefs.com/blog/*` 全部 404、Ahrefs 自己的
> sitemap 里没有收录博客，于是**把整组数字删了**，并写明「查不到就不该留着」。
>
> 那个删除是**对的推理、错的结论**：原文确实存在，地址是
> <https://ahrefs.com/blog/llmstxt-study/>，此前只是没试中路径。
> 2026-09-24 重新抓取原文逐字核对，数据全部对得上：
>
> - 样本：Ahrefs Web Analytics 中**2026 年 5 月**获得流量的全部 **137,210** 个域名
>   （方法学章节原文：`all 137,210 domains ... that received traffic in May 2026`）；
> - 其中 28% 发布了 `llms.txt`；
> - 这些文件的 **97% 在 2026 年 5 月零请求**——`97% of those files received zero traffic in May 2026`；
> - 有请求的那 3% 里，**96% 的请求来自 bots**。
>
> **发布日期与数据窗口是两件事**：原文发布于 **2026-06-15**，
> 统计的是 **2026 年 5 月**。先前的写法把两者混成了「2026 年 5 月实测」。
>
> 留着一个可点开的来源，比留着一句「查不到」更有用；
> 但**边界也要跟着一起写**：这是对 `llms.txt` 根文件的 HTTP 200 探测，
> 不是对「AI 有没有读到内容」的观测——**被请求的 3% 里，77% 的 bots 并不是 AI 工具**。

它**零成本、无下行风险**，而且确实有 agent 会抓（Claude Code 抓取 llms.txt
的频率高于任何 AI 搜索机器人），所以本站在生成它。但把它当卖点是误导。

真正的杠杆是**干净的内容协商**和**语义化的 HTML**——后者对静态站来说是白送的。

## 参考文献

- [RFC 9110 §12.5.1（Accept 头）](https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1)、
  [RFC 7763（`text/markdown` 媒体类型）](https://www.rfc-editor.org/rfc/rfc7763.html)
- [Cloudflare, *Markdown for Agents*](https://blog.cloudflare.com/markdown-for-agents/)
  —— 正文里「16,180 → 3,150 token、省 80%」那组数字出自这里。
  **测量条件是 Cloudflare 拿自己的文档做的，本项目没有复现它**；
  它说明的是「干净 HTML 转 markdown 能省多少」，不是本站的节省率
  （本站自己的数字在 [内容协商](https://xiaoxusop.github.io/letterpress/wiki/content-negotiation/) 末尾，且是**启发式估算**，那里附了真实词表的对照）
- [Google Search Central, *AI features and your website*](https://developers.google.com/search/docs/appearance/ai-features)
  —— 正文里「Google 明说不需要」那段引文出自这里
- [Ahrefs, *We Analyzed 137K Sites: 97% of llms.txt Files Never Get Read*](https://ahrefs.com/blog/llmstxt-study/)，
  2026-06-15 发布、数据窗口为 2026 年 5 月
  —— 正文里 137,210 / 97% / 96% 那组数字出自这里。
  **测量条件是 Ahrefs 拿自己的流量数据做的，本项目没有复现它**；
  它观测的是「根文件有没有被请求」，**不是**「AI 有没有读到内容」——
  被请求的那 3% 里 77% 的 bots 并不是 AI 工具
- ~~Ahrefs 那组数字原先作为一条独立引用，2026-09-23 被移除。~~
  **2026-09-24 找到了原始地址并已恢复**（见上一条）——
  那一轮的删除是**对的推理、错的结论**：`ahrefs.com/blog/*` 几个常见路径全 404
  只证明了「我按这几个路径没找到」，不等于「原文不存在」。
  **一个找不到的路径不是不存在的证据。**

> **勘误（2026-09-22 / 09-23 / 09-24）**：这几条原先都是纯文本、点不开，已核实的补了链接；
> **09-23 那一轮把 Ahrefs 那条当作「核实不到」移除了，09-24 又找回来了**——
> 因为原文一直存在，只是当时试的路径没命中。
> 三轮下来值得记住的是：**「我没找到」和「它不存在」之间隔着一整条推理链**，
> 跳过那条链就会把一次失败的检索固化成一条结论。
