尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

AI代理读取网页:用Accept标头让服务端直接返回Markdown

AI代理读取网页:用Accept标头让服务端直接返回Markdown 最近我在调试一个 AI 代理读取网页内容的流程时发现一个经常被忽略却非常关键的细节Accept标头。过去我们写接口默认这行请求头是给浏览器或者 API 客户端用的但现在 AI 代理正在成为新的网页读者Accept标头能不能把 Markdown 内容直接递给它会直接影响代理读取信息的成本、准确率和整个工作流的稳定程度。这件事并不是什么新框架的新特性。它用的还是 HTTP 里老掉牙的内容协商机制只是“读者”变了。文章写多了之后你会发现真正的问题从来不是没有技术方案而是很多人没有意识到“为什么需要这个方案”。下面我从 AI 代理读网页的痛点开始把机制、收益、实现方法和边界一次讲清楚。1. AI 代理读取网页内容为什么这么麻烦1.1 浏览器负责“看网页”AI 代理只负责“读源码”浏览器和 AI 代理访问同一个 URL拿到的响应头一模一样但两者的处境完全不同。浏览器拿下 HTML 之后会构建 DOM、解析 CSS、执行 JavaScript最后渲染出一个有视觉层级的页面。用户看到的是排版、字体、颜色、动效。AI 代理没有这套视觉系统它拿到的就是一串源代码文本。它需要自己从这一大段文本中判断导航栏在哪里正文从哪里开始哪些链接是推荐内容哪些段落才是真正的观点。这种差异在普通静态页面上还不算致命顶多是多花点 token。但进入现代前端工程之后情况会迅速恶化。很多 SPA 页面返回的 HTML 只是空壳和打包后的脚本地址真正的内容要等 JavaScript 运行之后才会出现在页面上。如果 AI 代理只做一次简单的请求它读到的可能就是一段 loading 占位符或者一个空的div。就算代理发展出了抓取能力能够等待页面渲染完再提取内容它依然要面对一个现实问题渲染后的 DOM 里到底哪部分值得进入模型上下文哪部分只是 UI 框架的骨架1.2 HTML 是给渲染引擎用的却不是给模型用的说 HTML“不好读”并不是否定 HTML。HTML 的标签体系从头到尾就是为布局和交互服务的。div、span、section这类标签承担着布局职责class和id是 CSS 选择器和 JavaScript 钩子nav、header、footer标记的是页面区块而不是文档逻辑。这些信息对浏览器是必要指令对大语言模型却是噪音。模型真正关心的信息是什么是这页讲了一个什么主题作者的观点是什么支持观点的论据有哪些代码块里是什么语言。这些信息在 HTML 里是存在的但被大量无关标签稀释了。比如一个博客页面正文核心可能只有 4000 字但完整 HTML 可能是 40KB。里面有一半是导航、侧边栏、页脚、统计脚本、样式表和广告位。如果 AI 代理把整段 HTML 全部交给模型模型需要先学会忽略噪音再从中提取答案。这不仅浪费 token还可能因为某些标签结构复杂导致提取结果失真。比如一个表格被拆成无数个div和span模型未必能重新拼回表格结构。1.3 Markdown 已经悄悄变成了 AI 内容生态的中间层不知道你有没有注意到Markdown 在今天的开发者工具链里已经接近“事实标准”了。日常场景中你在 VSCode 里写技术文档大概率用的就是 Markdown在知识库工具里整理笔记底层存储往往也是 MarkdownCoze 这类工作流平台上做内容转换日常操作是“Markdown 转 Word”“Markdown 转 PDF”SSE 流式输出里前端需要渲染一个实时 Markdown 块甚至飞书这类办公软件都需要靠插件解析 Markdown 里的 Mermaid 流程图。Markdown 不只是一个文件格式它已经变成人和机器之间一种共同认可的“中间语言”。这种语言的好处非常明显它有明确的结构语义但语法负担极轻。标题、列表、表格、代码块、引用、链接每一个元素都有直观的标记规则。人类读起来不费力模型训练语料里也大量存在模型理解起来同样不费力。所以逻辑自然就变成如果 AI 代理最终需要的是一种结构清晰、没有标签噪音、又能保留语义的文本那 Markdown 就是比 HTML 更适合作为响应内容的格式。问题是服务端怎么知道“这个请求方想要 Markdown”答案就在Accept标头里。2. Accept 标头在协商什么为什么这件事能成立2.1 内容协商的本质客户端声明偏好服务端裁决返回HTTP 协议里有一个很经典的设计思路内容协商。客户端发起请求时可以通过Accept请求头告诉服务器我能接受哪些媒体类型以及我的偏好顺序。服务器拿到这个头之后会根据自己支持的内容表示方式选择最合适的一种返回。在这个模型里客户端负责“提需求”服务端负责“做决定”。服务端可以完全支持客户端的偏好也可以在无法满足时返回替代格式甚至返回 406 Not Acceptable。整个过程不需要在 URL 里增加额外的参数也不需要建立新的 API 端点。这比“在 URL 里手动指定 format 参数”优雅得多原因有两点。第一URL 是资源的地址不应该承载太多渲染偏好第二同一个资源可以同时服务不同需求的客户端服务端可以根据请求头自动分流。2.2 一次典型的 Accept 请求头里有哪些信息我们可以看三个非常常见的请求头客户端类型典型 Accept期望响应浏览器text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8HTML 页面API 调用方application/jsonJSON 数据AI 代理 / 内容采集器text/markdown, text/html;q0.9Markdown 优先HTML 兜底浏览器把text/html排在最前面所以服务端会优先返回 HTML 页面API 客户端声明自己只要 JSON服务端就返回 JSONAI 代理可以把text/markdown放到最前面同时留着 HTML 作为兜底。q0.9是优先级权重值越高越优先。AI 代理设置Accept: text/markdown, text/html;q0.9意思就是“我最希望拿到 Markdown如果服务端做不到再给我 HTML 也行”。2.3 text/markdown 不是临时自造它是已注册的媒体类型很多人可能会担心在响应头里写Content-Type: text/markdown是不是一种“野路子”服务端会不会不认这个担心没必要。text/markdown并不是临时发明的 MIME 类型它已经由 IANA 正式注册对应的规范是 RFC 7763。也就是说它和text/html、application/json一样是有规范背书的媒体类型。只要你的 HTTP 框架支持自定义响应头就可以合法地使用它。不过在真实生产环境里text/markdown在响应头中出现的频率仍然远低于text/html和application/json。这主要是因为大多数网站都是面向浏览器和 API 设计的真正考虑“AI 代理作为读者”的站点还不够多。所以服务端如果决定支持它通常需要显式做一层分支处理不能指望框架自动帮你完成格式转换。2.4 重要边界Accept 是偏好不是命令这里必须强调一个容易误判的点Accept标头只是客户端声明的偏好服务端可以接受也可以不完全接受甚至可以忽略。如果服务端程序没有针对text/markdown做任何响应分支处理它就直接忽略这个偏好继续返回 HTML。如果服务端实现了一个严格的内容协商逻辑而客户端声明的类型全都无法满足服务端也可能返回 406 Not Acceptable。所以在客户端侧永远不要假设“我带了Accept: text/markdown服务端就一定会给我 Markdown”。拿到响应之后一定要先检查Content-Type响应头再决定后续到底按 Markdown 处理还是按 HTML 继续清洗。注意判断服务端是否真的返回了 Markdown要看响应头里的Content-Type而不是只看返回内容看起来像不像 Markdown。3. 用 Accept 返回 Markdown到底改变了什么3.1 对 AI 代理从“清洗 HTML”变成“直接阅读”如果 AI 代理拿到的是干净的 Markdown整个信息读取流程会明显简单。正常的 RAG 流程通常是这样的下载 HTML 页面用某种抽取逻辑去除导航、脚注、脚本和样式把留下的正文转换成 Markdown按标题切块向量化后存库如果源站点在请求阶段就返回text/markdown流程会缩短成下载 Markdown 内容按标题切块向量化后存库少掉的这一步看起来不大但实际影响很大。因为 HTML 清洗是所有步骤里最容易出问题的。有些页面的正文嵌在复杂嵌套结构里抽取器可能漏掉一部分有些页面有多层导航和动态加载的推荐文章清洗后可能残留大量无意义链接还有前端框架渲染后的 DOM和原始 HTML 差别巨大清洗逻辑需要单独适配。这些问题在源头返回 Markdown 之后就都不存在了。3.2 对内容提供者多一种面向 Agent 的内容表示对内容提供方来说支持Accept: text/markdown并不会破坏现有业务。浏览器正常请求时继续返回text/htmlAPI 客户端请求时继续返回application/jsonAI 代理声明自己想要 Markdown 时服务端才返回 Markdown。这三种响应方式可以在同一个 URL 上共存互不干扰。这也是这个方案最有吸引力的一点不需要新增接口不需要改 URL 结构不需要在页面上增加一个“AI 版”入口。你只需要在服务端增加一条内容协商分支按照请求头返回不同的表示形式。内容提供方还能保留控制权。你可以只允许登录用户获取 Markdown 版本也可以只对特定的 User-Agent 开放这种返回可以对 Markdown 响应单独做日志、限流和审计。所有这些控制都可以在现有权限体系内完成不需要额外设计一套面向代理的接口。3.3 对 RAG 工作流少掉一个清洗转换步骤在 RAG 知识库构建中Markdown 的价值不只是省 token更重要的是它天然适合切块。Markdown 的标题层级非常清晰#是一级标题##是二级标题代码块有明确的围栏边界表格也有明确的分隔行。切块时完全可以根据这些语义边界来切而不是硬按固定字符长度切。固定长度切块很容易把一个代码示例拦腰截断或者把表格的一行和上一段正文混在一起。如果源头返回的是 HTML你要先把 HTML 转成树结构再做正文提取再去掉样式标签才能得到类似 Markdown 的语义结构。这一整条链路里每一环都是可能出错的地方。如果服务端直接返回 Markdown省掉的不是一个两个命令而是整条“内容清洗管线”。4. 服务端落地最小实现路线4.1 最理想Markdown 本来就是你的源内容如果你维护的是一个文档站、技术博客或者知识库站点最理想的情况是文章内容在数据库或文件系统里本来就以 Markdown 格式存储。这种情况下返回 Markdown 的成本几乎为零你只需要在响应逻辑里做一次分支判断即可。下面是一个 FastAPI 的最小示例用来演示这种“根据 Accept 头返回不同格式”的写法from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse, PlainTextResponse import markdown as md app FastAPI() def load_markdown(article_id: int) - str: # 示例从本地文件读取 Markdown实际项目请替换为数据库或对象存储 with open(farticles/{article_id}.md, r, encodingutf-8) as f: return f.read() app.get(/articles/{article_id}) async def get_article(article_id: int, request: Request): accept request.headers.get(accept, ) # AI 代理想拿 Markdown直接给 if text/markdown in accept: return PlainTextResponse( contentload_markdown(article_id), media_typetext/markdown; charsetutf-8, headers{Vary: Accept} ) # 普通浏览器还是拿 HTML html_body md.markdown( load_markdown(article_id), extensions[tables, fenced_code] ) return HTMLResponse( contentfarticle{html_body}/article, headers{Vary: Accept} )这个示例里最关键的不是你能不能照抄而是你要理解它背后的取舍文章内容用 Markdown 存储比用 HTML 存储更灵活。用户需要网页时动态渲染成 HTMLAI 代理需要原始结构时直接把 Markdown 原样返回。你不需要维护两个版本的内容一次存储多处消费。4.2 退一步源内容只有 HTML怎么转换并不是所有项目都有这么好的基础。很多存量系统的内容在数据库里存的就是富文本 HTML甚至页面是由 UI 组件动态拼接出来的。这时候你要返回 Markdown就需要先做一次转换。常见的转换路径是先用 Readability 类型的正文抽取逻辑把主要正文捞出来再用 html2text 之类的库把 HTML 转成 Markdown。import html2text def html_to_markdown(html_text: str) - str: converter html2text.HTML2Text() converter.body_width 0 # 不要自动换行避免破坏表格 converter.ignore_images False # 保留图片链接 converter.ignore_emphasis True # 不把强调符号转成转义符号 return converter.handle(html_text)这里需要特别提醒自动转换出来的 Markdown 质量完全取决于原始 HTML 的干净程度。如果原始 HTML 是结构化良好的正文内容转换结果会很理想如果页面里混杂了大量导航、推荐位、广告位和外链转换出来的 Markdown 也会夹杂大量垃圾信息。所以在转换之前最好先用正文抽取逻辑把页面
返回列表