用Accept标头让AI代理直接获取Markdown:内容协商实用指南 这次我们来看一个和 AI 代理Agent协作非常相关的小技巧用Accept标头让服务端直接返回 Markdown 内容。很多做 Agent 工具链、知识库抓取、网页内容提取的开发者应该都遇到过同一个痛点抓回来的内容是一堆 HTML 标签和脚本噪声喂给大模型后 token 消耗大、解析容易出错。传统做法是抓完再清洗、再转换中间还要维护一堆解析规则。但如果服务端本身支持内容协商客户端只需要在请求里加一个Accept: text/markdown就能直接拿到结构干净的 Markdown。这篇文章不聊概念直接讲清楚四件事Accept标头在 AI 代理场景里到底怎么用服务端怎么实现按标头返回 Markdown客户端和批量任务怎么写踩坑之后怎么排查。如果你正在做 AI 代理、RAG 知识库、网页摘要工具或者想把 Markdown 内容接入自己的工作流这篇文章可以直接收藏。1. 核心能力速览先说结论这个方案的核心不是某个具体软件而是 HTTP 内容协商机制在 AI 代理链路上的应用。能力项说明主题类型HTTP 内容协商 / AI Agent 集成 / 文档获取核心机制请求标头Accept: text/markdown服务端按客户端偏好返回 Markdown主要功能让 AI 代理直接获取结构化 Markdown替代 HTML 清洗后转换适合场景AI 代理网页工具、RAG 知识库、网页摘要、批量内容抓取、Markdown 渲染链路涉及技术HTTP、HTTPX/requests、FastAPI/Flask、SSE 流式输出、Markdown 解析渲染支持 API取决于服务端实现客户端侧只要支持自定义请求标头即可是否支持批量支持按请求级别处理可并发批量抓取显存需求不涉及启动方式服务端按业务框架启动客户端代码调用也可在 API 网关/代理层实现使用门槛需要了解 HTTP 标头语义能改请求标头能读服务端响应从实际价值看这个方案最值得关注的不是协议本身而是它能减少 AI 代理链路里的“内容清洗”环节。只要服务端或者中间代理层支持客户端一次请求就能获得结构化 Markdown后续无论是存知识库、做摘要还是转其他格式都省一步。2. 适用场景与使用边界2.1 这个方案适合谁第一类是 AI 代理开发。Agent 调用网页工具时拿到 Markdown 比拿到 HTML 更容易让模型理解。尤其是有代码块、表格、链接列表的页面Markdown 能保持结构语义损耗更低。第二类是 RAG 知识库构建。知识库入库前最耗时的一步是清洗 HTML。如果数据源支持 Accept 内容协商抓取阶段直接得到 Markdown清洗成本会大幅下降。第三类是内容工具链开发。很多 Markdown 编辑器、文档转换工具参考常见的 Markdown 转 Word 工作流、Markdown 渲染 HTML、Markdown 表格复制等场景都依赖标准 Markdown 作为中间格式。用 Accept 标头获取内容可以统一输入格式。2.2 不适合什么场景这套方案不适合必须保留原始 DOM 结构的场景。有些前端页面依赖 JavaScript 渲染服务端返回的 Markdown 是降级内容缺失交互信息。此时强行用Accept: text/markdown反而丢了数据。也不适合所有服务端已经写死返回 HTML 的场景。如果服务端没有实现内容协商发什么 Accept 都不会改变响应体格式这时候只能靠解析层兜底。2.3 使用边界与合规提醒使用网页内容时要注意版权和授权边界。爬取内容后用于 AI 训练、商用产品、内容再发布都需要确认数据源的授权情况。不要把未经授权的文章整篇入库后对外输出。涉及需要登录、付费内容、个人隐私数据时更要注意合规只抓取公开且允许访问的数据并遵守目标站点的 robots 协议和服务条款。3. 环境准备与前置条件这个方案不挑硬件普通开发机就可以。需要准备的是运行环境和依赖库。项目说明操作系统Windows / Linux / macOS 均可Python建议 3.9 以上依赖库FastAPI、uvicorn、requests、httpx测试工具curl 或 Postman网络环境能访问目标服务端即可安装依赖pip install fastapi uvicorn requests httpx如果没有特殊要求不需要 GPU、不需要 Docker、不需要额外模型文件。整个方案是 HTTP 层面的和本地算力无关。4. 服务端实现按 Accept 标头返回 Markdown4.1 FastAPI 内容协商示例服务端的关键逻辑是读取请求的Accept标头如果包含text/markdown就返回 Markdown否则返回 HTML 或纯文本。from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse, PlainTextResponse from fastapi.responses import Response app FastAPI() MARKDOWN_CONTENT # 项目说明 这是一个支持 **Accept: text/markdown** 的示例服务。 ## 功能列表 - 内容协商 - Markdown 返回 - AI 代理友好 | 格式 | 说明 | | --- | --- | | HTML | 完整页面 | | Markdown | 结构化文本 | HTML_CONTENT !DOCTYPE html html headtitle项目说明/title/head body h1项目说明/h1 p这是一个支持 strongAccept: text/markdown/strong 的示例服务。/p ul li内容协商/li liMarkdown 返回/li liAI 代理友好/li /ul /body /html app.get(/docs) async def get_docs(request: Request): accept_header request.headers.get(accept, ) if text/markdown in accept_header: return Response(contentMARKDOWN_CONTENT, media_typetext/markdown) return HTMLResponse(contentHTML_CONTENT)启动服务uvicorn main:app --host 0.0.0.0 --port 80004.2 代理层转换方案如果你的数据源是第三方站点服务端不能改你可以在自己的代理服务里做转换。收到客户端的Accept: text/markdown后代理层去抓取目标 HTML再通过 html2text 等工具转成 Markdown 返回给客户端。import html2text import requests from fastapi import FastAPI, Request from fastapi.responses import Response app FastAPI() app.get(/proxy) async def proxy(request: Request, url: str): accept_header request.headers.get(accept, ) resp requests.get(url, timeout15) resp.encoding resp.apparent_encoding if text/markdown in accept_header: converter html2text.HTML2Text() converter.ignore_links False markdown_content converter.handle(resp.text) return Response(contentmarkdown_content, media_typetext/markdown) return Response(contentresp.text, media_typetext/html)这种代理层方案是目前 AI 代理工具里比较通用的做法上游数据源格式不可控但下游统一输出 MarkdownAgent 拿到的内容始终是干净的。5. 客户端请求与效果验证5.1 curl 请求验证先验证服务端是否支持内容协商。不带Accept标头默认返回 HTMLcurl -s http://127.0.0.1:8000/docs带Accept: text/markdown返回 Markdowncurl -s -H Accept: text/markdown http://127.0.0.1:8000/docs判断成功的标准第一段命令返回 HTML 页面第二段命令返回带#标题、-列表、|表格的 Markdown 文本。如果两段命令返回结果一样说明服务端没有做内容协商。5.2 Python 客户端调用import requests url http://127.0.0.1:8000/docs headers { Accept: text/markdown, } response requests.get(url, headersheaders, timeout15) print(response.status_code) print(response.headers.get(content-type)) print(response.text)如果控制台输出包含# 项目说明、功能列表等 Markdown 结构说明请求链路正常。如果输出是 HTML 标签说明服务端忽略了Accept标头。5.3 SSE 流式输出场景AI 代理场景经常用 SSEServer-Sent Events做流式输出。Markdown 内容在流式输出时要特别注意部分 Markdown 语法是跨片段组合的比如表格的|分隔行、代码块的 围栏、列表的多行结构。如果 SSE 直接把文本按片段抛给前端渲染器可能会在中间状态出现闪烁。解决办法是前端配合 Markdown 渲染器做“合并渲染”只对累积后的完整内容渲染或者用支持流式渲染的 Markdown 组件。很多开发者检索 Markdown 渲染器、SSE 流式输出 Markdown 渲染器核心都是要处理这种“边输出边渲染”的体验问题。6. 接口 API 与批量任务6.1 批量抓取 Markdown 内容实际业务中Agent 往往要一次处理多个 URL。批量任务的核心是每个请求都携带Accept: text/markdown并做好并发控制和错误重试。import asyncio import httpx URLS [ http://127.0.0.1:8000/docs, http://127.0.0.1:8000/docs, http://127.0.0.1:8000/docs, ] async def fetch_markdown(client, url): headers {Accept: text/markdown} try: response await client.get(url, headersheaders, timeout20) response.raise_for_status() return { url: url, status: response.status_code, content_type: response.headers.get(content-type), content: response.text, } except Exception as exc: return { url: url, status: failed, error: str(exc), } async def main(): async with httpx.AsyncClient() as client: tasks [fetch_markdown(client, url) for url in URLS] results await asyncio.gather(*tasks) for result in results: if result[status] 200: print(f成功: {result[url]} - {result[content_type]}) print(result[content][:200]) else: print(f失败: {result[url]} - {result.get(error)}) if __name__ __main__: asyncio.run(main())6.2 批量工程的几个建议批量任务的稳定性比单次请求更重要。常见问题是目标站点限流、超时、编码混乱、上游偶发 500。建议按这个顺序处理控制并发数不宜过高给目标服务留出余量。添加重试逻辑对 429、503 等状态做指数退避。记录每个 URL 的抓取日志方便失败后定点排查。输出结果落盘保存避免进程中断后全部重跑。如果目标是同一个站点要控制请求频率避免对目标服务造成压力。7. 资源占用与性能观察这个方案不消耗 GPU性能瓶颈主要在网络请求、HTML 转换、Markdown 渲染这几个环节。观察点有三个。7.1 服务端响应体积HTML 页面通常包含大量标签、脚本、样式返回体积可能几十 KB 甚至更大。Markdown 内容通常只有几 KB 到十几 KB。对 AI 代理来说体积越小token 消耗越低模型理解越直接。7.2 转换耗时如果使用代理层做 HTML 转 Markdown转换耗时会随页面复杂度增加。大表格、多层嵌套标签、图片链接较多的页面转换时间会更长。建议在代理服务里加缓存同一个 URL 的 Markdown 结果缓存一段时间避免重复抓取和重复转换。7.3 并发连接数批量任务下连接数直接取决于并发设置。HTTPX 的AsyncClient默认连接池是有限制的建议显式配置避免连接耗尽。async with httpx.AsyncClient(limitshttpx.Limits(max_connections20, max_keepalive_connections10)) as client: ...如果发现大量请求 socket 超时优先检查是否触发了目标服务限流而不是无脑调高并发。8. 常见问题与排查方法问题现象可能原因排查方式解决方案服务端返回 HTML不是 Markdown服务端没有实现内容协商逻辑curl 带 Accept 标头看响应头和响应体在代理层做 HTML 转 Markdown修改 Accept 标头后响应没变请求标头没传到服务端或被网关剥离服务端打印 request.headers 检查检查网关、反向代理的标头透传配置Markdown 内容里中文乱码上游响应编码识别错误打印响应源码检查 meta charset使用resp.apparent_encoding或指定 UTF-8 解码批量任务中途大量超时并发过高触发限流查看目标服务返回状态码降低并发增加重试退避返回的 Markdown 里表格解析不全上游 HTML 表格结构不规范检查原始 HTML 的 table 结构在转换前预处理或改用更强解析库流式输出时 Markdown 渲染闪烁SSE 片段和 Markdown 语法组合冲突观察累积文本渲染效果合并累积内容后渲染使用流式 Markdown 渲染器URL 带中文参数请求失败未做 URL 编码检查请求日志使用urllib.parse.quote或 requests 自动编码8.1 Accept 标头被忽略怎么处理这是最常遇到的问题。很多服务端框架默认返回 HTML 或 JSON不解析Accept标头。不要指望所有站点都支持内容协商。处理方式有两种。第一种是服务端自己实现参考第 4.1 节的 FastAPI 示例在路由里读取请求标头并分支返回。第二种是加到自己的 AI 代理层。代理层保持对下游的兼容性对上游可以是任意格式对客户端统一输出 Markdown。这样即使上游是 HTMLAgent 拿到的依然是 Markdown。8.2 缓存导致旧内容如果服务端或代理层加了缓存修改 Markdown 内容后客户端可能拿到旧版本。排查时可以给请求加一个随机的查询参数打散缓存curl -H Accept: text/markdown http://127.0.0.1:8000/docs?ts1234567890也可以用Cache-Control: no-cache标头curl -H Accept: text/markdown -H Cache-Control: no-cache http://127.0.0.1:8000/docs但要注意目标服务端不一定遵守这个标头。最稳妥的做法还是在代理层明确控制缓存策略。9. 最佳实践与使用建议9.1 客户端默认带上 Accept 标头做 AI 代理时可以把Accept: text/markdown设为默认请求头。如果目标服务端不支持内容协商结果就是返回原内容不会有额外损失如果支持就拿到了更干净的输入。9.2 不要让 Accept 标头承担鉴权职责Accept标头只表达内容偏好不能用于身份验证和权限控制。不要把“允许 Markdown 访问”当作“已授权内容获取”的依据。敏感内容的访问控制要依赖认证授权机制而不是内容协商。9.3 把 Markdown 结果缓存到本地对 AI 代理链路来说重复抓取同一个 URL 的成本很高。建议在代理层维护一套 Markdown 缓存缓存 key 可以是 URL 加 Accept 标头的组合。这样既减少目标服务压力也减少网络等待时间。9.4 与 Markdown 工具链结合拿到标准 Markdown 后生态非常丰富。可以接 Markdown 编辑器做人工审阅可以用渲染器转成 HTML 预览可以配合 Markdown 转 Word 工作流输出报告也可以直接存入知识库做向量化。让整条链路围绕 Markdown 展开可以避免“每个环节维护一套格式”的重复开发。9.5 版权和数据合规内容抓取只是第一步。抓取后如果还要喂给本地模型做摘要、做训练、做二次发布必须确认数据来源是否允许。优先处理自己拥有授权、开源许可明确、或者公开允许抓取的数据。对来源不明的 HTML 页面即使能通过Accept: text/markdown获取到内容也不能默认获得商用授权。9.6 保留一套最小验证流程建议在项目里保留一个最小例子一个 FastAPI 服务端、一个 curl 命令、一个 Python 客户端脚本。后续改造代理层、新增数据源、调批量逻辑时先用这套最小流程验证内容协商是否正常再接入复杂业务能省很多定位问题的时间。10. 总结与下一步用Accept标头向 AI 代理提供 Markdown 内容思路本身很简单客户端声明偏好服务端按偏好返回。但它对 AI 代理链路的优化是实打实的省掉了 HTML 清洗、标签去除、格式转换这些最耗时的中间环节。如果你现在正在做 AI 代理或者知识库抓取建议先做两件事一是给客户端请求加上Accept: text/markdown确认数据源是否支持内容协商二是在自己的代理层实现一个 HTML 转 Markdown 的兜底转换保证下游始终拿到统一格式。最容易踩的坑是三个服务端根本没做内容协商标头发了也白发批量任务并发过高触发限流HTML 转 Markdown 后表格和代码块结构丢失。这三个问题都可以用“代理层兜底 控制并发 转换后抽查”的方式解决。把这套逻辑跑通之后后面还可以继续扩展接入 SSE 流式输出做增量渲染把 Markdown 结果缓存成独立的知识库或者把转换能力封装成内部 API 服务。内容协商只是第一步关键是把 Markdown 变成后续所有 AI 代理任务的统一输入格式。