侧边栏壁纸
  • 累计撰写 264 篇文章
  • 累计收到 2 条评论

为了拥抱 AI 搜索给全站上了 llms.txt 与 Markdown 内容协商,传统搜索收录与关键词排名却断崖式暴跌?排查 Vary 标头缺失致 CDN 缓存投毒、llms-full.txt 巨型聚合文本稀释全站权重、AI 爬虫并发风暴打爆 504 与隐藏语义注水被判作弊我踩过的几个深坑

2026-11-9 / 0 评论 / 6 阅读
AI 摘要由 AI 生成

文章探讨在拥抱AI搜索进行网站优化时遇到的问题。通过部署Markdown内容协商和`llms.txt`,网站权重暴跌,百度和谷歌搜索收录下降。问题源于CDN缓存投毒、巨型聚合文本稀释权重和AI爬虫并发风暴,导致504超时和作弊警报。

上个月团队开会讨论业务转型,技术负责人提了一个很火的方向:现在大家都用 Perplexity、ChatGPT Search 和各种大模型找资料,传统的 SEO 流量被截流得厉害,咱们得赶紧做 GEO(生成式引擎优化)。方案定得很快,前端和服务端连续加了两天班,把业内推荐的那套全给上了:根目录部署 llms.txt 和包含全文的 llms-full.txt,服务端接入基于 HTTP Accept: text/markdown 的内容协商,只要客户端或者 AI 爬虫想要 Markdown,直接给吐清洗好的纯文本;页面底部还顺手塞了一批给大模型辅助理解的语义增强数据。

上线当天大家还在测试群里发截图庆祝,觉得这波紧跟技术潮流,不管是 Perplexity 还是各种 Agent 抓取解析肯定顺滑无比。结果仅仅过了五天,运营主管就在早会上拍了桌子:百度搜索资源平台和 Google Search Console 里的数据全线雪崩。百度索引量从二十几万一路掉到不足四万,核心长尾词排名大面积蒸发;谷歌那边的抓取统计图直接走出了倒 V 字,错误日志里飘满了 504 超时、重复内容和软 404 警报。

明明只是给 AI 爬虫开了一扇更友好的门,怎么转头把吃饭的搜索基本盘全给砸了?连续熬了两个通宵抓包、翻 CDN 缓存日志和 Nginx 访问流,才把这几个环环相扣的暗坑挖出来。

边缘 CDN 缓存把 Markdown 当作全站响应,百度蜘蛛抓到的全是纯文本

上线后遇到的第一个致命问题,出在 HTTP 内容协商与边缘 CDN 缓存键的冲突上。

当时为了让 ChatGPT、Claude 和各种 AI 客户端在请求文章时能拿到没有 HTML 标签杂质的干净正文,服务端加了一层中间件:判断请求头如果带有 Accept: text/markdown,或者 User-Agent 命中 AI 爬虫白名单,就跳过模板渲染引擎,直接调用 Markdown 提取器输出纯文本。本地 curl 测试时体验非常丝滑:

# 正常浏览器访问
curl -I https://www.example.com/tech/nginx-cache-tuning.html
# 返回 Content-Type: text/html; charset=utf-8

# 带上 Markdown 请求头
curl -I -H "Accept: text/markdown" https://www.example.com/tech/nginx-cache-tuning.html
# 返回 Content-Type: text/markdown; charset=utf-8

代码逻辑看着挑不出毛病,但在真实的生产环境里,前面顶着一层多节点 CDN。

主流 CDN(包括 Cloudflare、阿里云 CDN、腾讯云等)默认的静态缓存键通常只由四部分组成:协议(Scheme) + 域名(Host) + 路径(URI) + 查询参数(Query String)。默认配置下,CDN 根本不会把请求头里的 Accept 纳入缓存键计算。

这就引发了一场灾难级的缓存投毒:

  1. 某只 AI 爬虫带着 Accept: text/markdown 请求了 /tech/nginx-cache-tuning.html。
  2. CDN 边缘节点本地没有缓存,回源到业务服务器。
  3. 业务服务器看到 Accept: text/markdown,乖乖返回了纯文本 Markdown,HTTP 响应头带了公共缓存指令 Cache-Control: public, max-age=86400。
  4. CDN 边缘节点把这篇纯 Markdown 的正文缓存了下来,绑定的缓存键就是原始 URL。
  5. 几分钟后,百度的 Baiduspider 或者正常用户的浏览器访问同一个 URL,请求头虽然是默认的 Accept: text/html,但 CDN 节点根据 URL 判定命中缓存,直接把刚才那份 Markdown 纯文本甩了出去!

抓包看到当时 CDN 返回的响应头部和内容,冷汗直接下来了:

HTTP/2 200 OK
Date: Mon, 12 Oct 2026 03:14:22 GMT
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=86400
CF-Cache-Status: HIT
X-Cache: HIT from cdn-node-hangzhou-04

# Nginx 高并发缓存调优实战
在进行反向代理配置时,proxy_cache_path 的 keys_zone 必须合理规划内存空间...

百度爬虫来抓取网页,期望看到的是规范的 HTML 骨架、<head> 标签里的 <title>、<meta name="description"> 以及指定权重的 <link rel="canonical">。结果它拿到的是一行行带着 # 和 - 的 Markdown 符号,响应头 Content-Type 还是 text/markdown。

对传统搜索引擎算法来说,这种没有标准 DOM 结构的响应会被直接标记为解析异常或格式损坏。更要命的是,因为没有 HTML 里的 Canonical 规范链接,百度认为页面失效,快照直接变成白板或报错,几天时间就把数十万存量页面的索引标记为无效甚至剔除。

怎么纠正这个缓存错位?

解决这个坑的核心原则是:永远不要指望公网 CDN 能在不加干预的情况下感知请求头分流。

如果确实要在同一路径下支持内容协商,Nginx 必须在响应头中强制注入 Vary 标头,明确告诉所有中间代理和 CDN 节点:这份响应依赖客户端的 Accept 和 User-Agent,缓存时必须把它们纳入缓存键变体:

# Nginx 内容协商 Vary 头强制声明
location / {
    proxy_pass http://backend_upstream;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # 必须通知中间缓存:响应因客户端 Accept 与 User-Agent 而异
    add_header Vary "Accept, User-Agent" always;
}

但现实中部分老旧 CDN 节点对 Vary: Accept 的支持并不可靠,容易导致节点缓存穿透或缓存碎片化。最稳妥、最符合工程直觉的做法是彻底解耦 URL:

把 Markdown 的访问入口固定为独立的子路径或者显式参数,例如 /tech/nginx-cache-tuning.md 或者 /tech/nginx-cache-tuning.html?format=markdown。让 HTML 和 Markdown 拥有天然不同的 URI,CDN 的缓存键自动隔离开,绝不给混淆投毒留任何缝隙。

未加隔离的 llms-full.txt 成了站内最大的重复内容吸血黑洞

为了方便外部大模型和各种知识库一次性索引全站内容,当时按照社区的 llms.txt 提案,在根目录下放了两个文件:

  • /llms.txt:轻量级索引,列出全站核心板块的结构和链接。
  • /llms-full.txt:全量知识库,把全站近两千篇技术文章的正文全部拼成一个超大文件(体积接近 18MB)。

原本的想法很单纯:让第三方 AI 对话产品不用一遍遍爬页面,抓这一个文件就能理解全站知识。但当时疏忽了一个关键隐患:把这个文件当成普通静态文件扔在根目录下,完全没做搜索引擎蜘蛛隔离。

传统的搜索引擎爬虫嗅觉非常灵敏。Googlebot 和 Baiduspider 爬取根目录发现新链接后,立刻顺着爬下了这篇 18MB 的全量文本文件。

搜索引擎的去重算法瞬间被引爆了:

  1. /llms-full.txt 里面一字不差地包含了全站每一篇文章的标题、正文、代码块和结论。
  2. 爬虫抓取并解析了这个文件之后,系统发现这一个 txt 文件里的内容和网站上已收录的上千个 HTML 页面有着 99% 的文本相似度。
  3. 搜索引擎的垃圾与镜像过滤机制(Duplicate Content Filter)开始介入。它无法理解这是专门为大模型准备的聚合知识库,只会按照权重算法机械判定:同一个站点内部存在严重的自我剽窃和海量重复内容。
  4. 算法开始在 HTML 原生页面和这个巨型 txt 之间做主版本裁定。结果由于这个 txt 汇集了所有长尾词,内链集中,它反倒被某些搜索引擎识别成了权重聚合源,导致那些被摘抄的原创 HTML 文章被判定为次级低质副本,排名直接从第一页被降权压到十页开外。

怎么堵住这个权重漏洞?

对所有专门服务 AI 的聚合文件(无论是 llms.txt、llms-full.txt 还是 API 镜像),必须在 Nginx 层对普通搜索引擎实施严格的无条件封死。

不仅要在 robots.txt 中显式封禁,更要在 HTTP 响应头里注入 X-Robots-Tag。因为有些爬虫即使看到了 robots.txt 的 Disallow,只要发现外部有强引用,依然可能建立无快照索引。

在 Nginx 中为这批文件配置专门的拦截规则:

# 对大模型专用索引文件实施严格的搜索引擎隔离
location ~* ^/(llms\.txt|llms-full\.txt|\.well-known/llms\.txt)$ {
    # 强制告诉所有抓取爬虫:绝不收录、绝不建快照、绝不提取片段
    add_header X-Robots-Tag "noindex, nofollow, noarchive, nosnippet" always;
    add_header Content-Type "text/markdown; charset=utf-8";

    # 仅允许受控访问,避免被脚本恶意高频刷取打满带宽
    limit_req zone=ai_doc_limit burst=5 nodelay;

    try_files $uri =404;
}

同时在网站根目录的 robots.txt 顶部加上针对主流通用爬虫的阻止声明:

User-agent: Baiduspider
Disallow: /llms.txt
Disallow: /llms-full.txt

User-agent: Googlebot
Disallow: /llms.txt
Disallow: /llms-full.txt

User-agent: Bingbot
Disallow: /llms.txt
Disallow: /llms-full.txt

# 明确放行官方 AI 爬虫
User-agent: GPTBot
Allow: /llms.txt
Allow: /llms-full.txt

User-agent: ClaudeBot
Allow: /llms.txt
Allow: /llms-full.txt

User-agent: PerplexityBot
Allow: /llms.txt
Allow: /llms-full.txt

双保险加上之后,搜索引擎在爬到这些文件时会立刻丢弃索引建立流程,全站的原创权重才停止被聚合文件持续稀释。

激进的 AI 爬虫打垮后端进程,正常蜘蛛被迫吃下一堆 504 Gateway Timeout

排查过程中的第三个发现更让人头疼:服务器上的 504 Gateway Timeout 报错频率达到了平时的几十倍。

传统的大型搜索引擎爬虫(比如 Googlebot 和正规状态下的 Baiduspider)都有非常成熟的抓取预算(Crawl Budget)自适应算法。如果它发现每次请求返回的延迟从 100ms 攀升到 800ms,或者开始偶尔出现 5xx 错误,它会自动降速,减少并发线程,给源站喘息的机会。

但现在的 AI 搜索生态完全是另一套野蛮生长的逻辑。市面上除了 OpenAI、Anthropic、Perplexity 几家大厂有相对规矩的抓取频率控制外,还混杂了大量自建 Agent 框架、第三方搜索聚合器、向量知识库构建爬虫,甚至各种未经鉴权的自动化抓取脚本。

这些爬虫伪装成普通的 Python 请求或者换着 UA,一次性开数十个线程疯狂扫全站历史分页。因为我们配置的内容协商需要动态把 HTML 或富文本剥离为 Markdown,这需要消耗服务端的正则处理和 DOM 遍历计算。

原本静态或者带缓存的页面,被这波并发一冲,Node/Python 渲染集群的 CPU 瞬时飙到 100%,数据库只读连接池全被吃光。

这时候,正规的搜索引擎爬虫按平时的抓取节奏过来巡检新文章,直接撞上了后端的瓶颈:

2026/10/14 14:22:08 [error] 14210#14210: *890124 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 220.181.108.95, server: www.example.com, request: "GET /tech/mysql-index-optimization.html HTTP/1.1", upstream: "http://127.0.0.1:3000/tech/mysql-index-optimization.html", host: "www.example.com"

日志里清清楚楚记录着:来自百度抓取节点(220.181.108.95)的请求被活活拖死了 30 秒,最后 Nginx 只能给爬虫吐出一个冷冰冰的 504 状态码。

百度平台对连续 504 超时的惩罚极重。一旦探测到源站承载能力崩塌,爬虫为了防止把网站彻底打死,会主动实施断崖式削减抓取配额。原先每天三十万次的抓取配额,两三天内就被砍到了三千次。新发布的文章无法被及时索引,老文章的快照也无法刷新,直接把整个站点的搜索引擎信誉拖入冰点。

怎么给爬虫划分优先级?

在单一网关下,必须将传统 SEO 搜索引擎与消耗极大的 AI 检索爬虫在 Nginx 层面做物理隔离,分流到完全不同的处理队列中。

核心思路是用 Nginx 的 map 指令把客户端分类,针对 AI 爬虫设置极严苛的限流漏桶,而对经过 rDNS 验证的合规主流搜索引擎给予独立放行通道:

# 根据 User-Agent 标记 AI 爬虫与普通爬虫
map $http_user_agent $crawler_type {
    default                         "normal_user";
    ~*(GPTBot|ChatGPT-User)         "ai_bot";
    ~*(ClaudeBot|anthropic-ai)      "ai_bot";
    ~*(PerplexityBot|Bytespider)    "ai_bot";
    ~*(Baiduspider)                 "search_engine";
    ~*(Googlebot)                   "search_engine";
    ~*(bingbot)                     "search_engine";
}

# 针对 AI 爬虫建立单独的限流区域,限制每个 IP 的并发与速率
limit_req_zone $binary_remote_addr zone=ai_crawl_limit:10m rate=2r/s;
# 针对普通用户与搜索引擎的常规保护区域
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s;

server {
    listen 443 ssl http2;
    server_name www.example.com;

    location / {
        # 如果是 AI 爬虫,强制接入低速限流队列
        if ($crawler_type = "ai_bot") {
            limit_req zone=ai_crawl_limit burst=3 nodelay;
        }

        # 针对普通请求走基础防护
        limit_req zone=general_limit burst=20 nodelay;

        proxy_pass http://backend_pool;
        proxy_connect_timeout 5s;
        proxy_read_timeout 10s;
        proxy_set_header Host $host;
    }
}

通过这一层限流,AI 爬虫即使发起几百个突发并发,超出阈值直接在 Nginx 层被返回 429 Too Many Requests,既不会消耗后端的渲染算力,也不会霸占数据库连接池,从而给百度和谷歌爬虫留出了稳定、低延迟的绿色通道。

为讨好 AI 注入的隐藏上下文 DOM,踩中了搜索引擎作弊惩罚红线

最后一个深坑藏在前端页面代码里。

当时前端工程师为了让 LLM 在解析 HTML 页面时能更快提取出文章的要点、上下文关联背景和一问一答的归纳,在页面模板底部加了一块隐藏的 DOM 节点:

<!-- 原先错误的注入方式:用 CSS 把语义知识库藏起来 -->
<div class="ai-knowledge-context" style="display: none; visibility: hidden;">
    <h3>文章核心实体与上下文摘要</h3>
    <p>关键词:Nginx 优化, 高并发调优, CDN 缓存击穿, 状态码映射, 反向代理配置...</p>
    <div class="qa-block">
        <p>问:Nginx 缓存击穿该如何处理?</p>
        <p>答:配置 proxy_cache_use_stale updating error timeout 并在回源层加互斥锁...</p>
    </div>
</div>

工程师觉得这招很聪明:在不影响人类读者视觉排版的前提下,大模型拿到了结构化提炼出来的纯净语义。

但在各大搜索引擎的反作弊规则(比如百度的烽火算法、谷歌的 Helpful Content System 与垃圾内容防御机制)里,这种做法有极其明确的定性:隐藏文本(Hidden Text)与关键词堆砌(Keyword Stuffing)。

在搜索引擎眼中,任何使用 display: none、visibility: hidden 或者把文本文字颜色设置成与背景色相同(如白底白字)、通过绝对定位把文字移出屏幕外(text-indent: -9999px)的行为,都被默认视为利用欺诈手段欺骗爬虫的黑帽手段(Cloaking)。

检测程序抓取到这部分代码后,发现该页面对机器输出的内容远多于对人眼可见的内容,反作弊模型自动触发了页面违规标记。那几天站点好几个栏目被降权,快照收录甚至带出了“页面内容存在安全隐患或异常”的风险提示,正是这块隐藏文本惹的祸。

怎么做才算合规的语义注水?

想把结构化数据同时喂给大模型和搜索引擎,唯一的合规途径是标准的 Schema.org JSON-LD。

搜索引擎官方文档中明确声明:放在 <script type="application/ld+json"> 标签里的数据不仅不会被算作作弊隐藏文本,反而会被解析为富媒体摘要(Rich Snippets),帮助页面提升在搜索结果中的展现形式:

<!-- 合规的标准结构化语义注水方案 -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Nginx 高并发缓存调优实战踩坑",
  "description": "系统排查 Nginx 缓存穿透、Vary 标头缺失导致 CDN 投毒及代理超时的实战经验复盘",
  "author": {
    "@type": "Person",
    "name": "运维老兵"
  },
  "keywords": "Nginx, CDN缓存, 状态码, 抓取优化",
  "mainEntity": {
    "@type": "Question",
    "name": "Nginx 缓存击穿该如何处理?",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "配置 proxy_cache_use_stale updating error timeout 并在回源层加互斥锁机制。"
    }
  }
}
</script>

必须注意一个红线:JSON-LD 中的所有事实陈述和答案,必须在页面可见区域有相对应的文字内容。坚决不能在 JSON-LD 里凭空编造正文里根本没有的引流词,否则同样会被算法判定为虚假元数据操纵排名。

生产环境完整的双轨分流与防护配置参考

把上面踩过的深坑逐一摸透之后,我们在网关层重新梳理并落地了一套完整的双轨治理配置。

既满足了对外暴露标准 Markdown 与 llms.txt 的业务需求,又彻底切断了对核心 SEO 抓取体系的干扰:

# 1. 定义爬虫类型映射表
map $http_user_agent $bot_category {
    default                    "user";
    ~*(Baiduspider)            "legit_spider";
    ~*(Googlebot)              "legit_spider";
    ~*(bingbot)                "legit_spider";
    ~*(GPTBot|ChatGPT-User)    "ai_crawler";
    ~*(ClaudeBot)              "ai_crawler";
    ~*(PerplexityBot)          "ai_crawler";
    ~*(Bytespider)             "ai_crawler";
}

# 2. 限流漏桶配置
limit_req_zone $binary_remote_addr zone=ai_pool:10m rate=3r/s;

server {
    listen 443 ssl http2;
    server_name www.example.com;

    # 3. llms 聚合规范隔离:阻断搜索引擎抓取,仅放行受控白名单
    location ~* ^/(llms\.txt|llms-full\.txt)$ {
        # 强制下发无快照索引头
        add_header X-Robots-Tag "noindex, nofollow, noarchive, nosnippet" always;
        add_header Content-Type "text/markdown; charset=utf-8";

        # 针对 AI 爬虫限流,防止被恶意并发刷爆机器
        limit_req zone=ai_pool burst=2 nodelay;

        try_files $uri =404;
    }

    # 4. 文章独立 Markdown 出口:路径解耦,严禁共用同一 HTML 缓存键
    location ~* ^/articles/(.+)\.md$ {
        # 声明独立 Vary 头,隔离边缘缓存
        add_header Vary "User-Agent" always;
        add_header Content-Type "text/markdown; charset=utf-8";

        # 将请求代理给后端的 Markdown 转换微服务
        proxy_pass http://markdown_service/$1;
    }

    # 5. 主站 HTML 渲染通道:强化 Canonical 与常规索引安全
    location / {
        # 如果是 AI 爬虫访问常规动态页面,强制走限流保护
        if ($bot_category = "ai_crawler") {
            limit_req zone=ai_pool burst=5 nodelay;
        }

        proxy_pass http://html_service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 必须显式透传 Vary,防止 CDN 边缘乱序缓存
        add_header Vary "Accept-Encoding" always;
    }
}

改造上线两周后,监控大盘逐渐恢复平稳:CDN 命中率稳定在 94% 以上,百度的抓取耗时从平时的几秒缩减到了正常的 75ms,504 错误彻底归零。丢失的收录量和长尾词排名在三次快照轮巡更新后陆续找了回来。

做技术演进追逐新技术标准没有错,但面对像搜索引擎收录这种长期依赖严格 HTTP 状态码、DOM 规范和缓存一致性的基础业务,任何一个网关标头的改动,背后都连着复杂的爬虫调度体系。把隔离边界筑牢,新技术的落地才不会变成拆东墙补西墙的灾难。

评论一下?

OωO
取消