快速提示:为大语言模型采用“伪装”技术的理由

via https://ipullrank.com/cloaking-for-llms

在开始之前,我们先挑明一个显而易见的事实。伪装是指向普通用户展示的内容与向搜索引擎展示的内容不同,其目的是操纵搜索引擎的排名表现。这种做法违反 Google 的指南。

如果你想继续留在 Google 的索引中,就不应该对 Google 做伪装。

话虽如此,ChatGPT 并没有类似的指南。而且,ChatGPT 的“爬虫”(或称“代理”)并不渲染内容。Google 看重的是“信息增益”,而 ChatGPT 同时也成了你的竞争对手快速窃取你内容并重新发布的机制。

既然如此,为什么要让他们更容易得手呢?

什么时候对大语言模型做伪装是合理的?

让我感到困惑的是,生成式 AI 公司在训练模型和推理上投入了数十亿美元的算力,却在渲染 JavaScript 和缓存网页上止步不前。然而,这正是我们的机会。

这方面的数据差异非常明显。Vercel 和 MERJ 分析了超过 5 亿次 GPTBot 抓取记录,没有发现任何执行 JavaScript 的证据。ClaudeBot 大约在 24% 的情况下会抓取 JS 文件,GPTBot 约为 11.5%——但两者都不会实际运行它们。另一项针对 23 个主流 AI 爬虫的分析发现,其中 69% 根本无法执行 JavaScript。Googlebot 会渲染,Bingbot 会渲染。其他所有爬虫——GPTBot、ChatGPT-User、OAI-SearchBot、ClaudeBot、PerplexityBot、Bytespider、CCBot、Meta-ExternalAgent——只会获取原始的初始 HTML,然后就离开了。

假设你有一个网页,上面发布了你独有的丰富数据:比如一项专有的基准研究、一个你花了六个月收集的数据集、一项针对 2000 名从业者的内部调查,或者一个你根据多年客户工作整理出的独特框架。这类内容需要真正的时间和金钱才能产出,而且别人没有。

我可以轻松地运行这样的提示词:{阅读这个页面,提取所有数据,然后为[某个网站]重写一个新版本}。现在,你那精彩、独有的内容就变成我的了,你的“信息增益”优势也就消失了。

针对大语言模型的 JavaScript 伪装

这里的思路并不是说我们要向 ChatGPT 展示用户看不到的东西(虽然你也可以这么做,比如白底白字,或者根据用户代理添加文本,这些都是可行的手段)。

我提出的是:从页面中移除某些内容,从而保持你的优势

实际上,我们要做的是:用特定的 class 标记内容,并且仅通过客户端渲染这些区块。然后,我们只需为所有大语言模型的用户代理屏蔽掉我们所用的那个 JS 文件。

Google 和 Bing 不受影响,而 ChatGPT、Perplexity 等则永远看不到这些内容。

这种机制之所以有效,是因为整个 AI 爬虫生态系统(Gemini 除外,它跑在 Google 的基础设施上)仍然停留在 2010 年的工作方式:发送 HTTP 请求,获取初始 HTML 响应,然后离开。如果你的“差异化”内容存在于一个 <div data-llm-protected> 中,并且这个 div 的内容是由一个你已针对那些用户代理屏蔽了的 JS 文件填充的,那么爬虫就根本无法看到它。对它们来说,页面的那个部分根本不存在。与此同时,真实用户在浏览器中,以及会渲染页面的 Googlebot,看到的则是完整体验。

这恰恰是我们过去 20 年里所有关于伪装讨论的反向操作。过去的问题是:“我如何向搜索引擎展示我向用户隐藏的内容?” 而现在的问题是:“我如何向用户展示我向一个不配得到它的爬虫所隐藏的内容?”


以 Markdown 格式提供页面可以提升速度

当一个页面加载缓慢时,某些 AI 爬虫会返回 499 状态码,导致该页面无法被纳入答案或引用。以 Markdown 格式提供页面可以获得更快的首字节时间(TTFB),并提高被引用的可能性。

Cloudflare 自己的数据很有启发性:他们自己一篇公告的 HTML 版本消耗了 16,180 个 token,而 Markdown 版本只有 3,150 个。这相当于将爬虫需要处理的内容减少了 80%。下载更少、解析更少、TTFB 更快,你真正越过超时窗口并被引用的几率就大得多。

Cloudflare 现在已经把这个做成了一项功能(“面向代理的 Markdown”),但你不需要使用他们的托管产品也能实现。你只需要一个 Worker:当用户代理是 LLM 时,响应 Markdown;其他时候响应 HTML。我们下面就会具体实现。

保持你的优势,为大型语言模型做伪装

总结一下:AI 爬虫生态系统不执行 JavaScript,不遵循 Google 的指南(因为它们不是 Google),而且被你的竞争对手积极用来抓取和改写你的作品。没有任何政策理由让你把自己最好的内容喂给它们;而且,当你确实要喂给它们一些内容时,以 Markdown 格式提供还能带来显著的速度和被引用方面的好处。如果你花了真金白银产出了原创数据、框架或研究,那么默认做法——“让每个 AI 爬虫免费、完整地拿走,而我的竞争对手下周二之前就能在他们的域名上重新发布”——这不是一个策略,而是一个疏忽。

下面是使用 Cloudflare Workers 解决这个问题的方法。


步骤 1:在域上设置一个 Worker

如果你已经在使用 Cloudflare,这部分很简单。安装 Wrangler,认证,然后创建一个新的 Worker:

bash

npm install -g wrangler
wrangler login
wrangler init llm-cloaker
cd llm-cloaker

在 wrangler.jsonc 中,设置指向你域名的路由(如果你只想将其应用于特定路径,也可以设置路径模式):

json

{
  "name": "llm-cloaker",
  "main": "src/index.ts",
  "compatibility_date": "2025-03-07",
  "routes": [
    { "pattern": "yoursite.com/*", "zone_name": "yoursite.com" }
  ]
}

步骤 2:检测大语言模型的用户代理

Workers 的 fetch 处理程序可以直接访问请求头。构建一个你想视为 LLM 流量的用户代理列表。当前值得覆盖的列表如下:

typescript

const LLM_USER_AGENTS = [
  "GPTBot",            // OpenAI 训练
  "ChatGPT-User",      // OpenAI 用户触发的抓取
  "OAI-SearchBot",     // OpenAI 搜索/引用
  "ClaudeBot",         // Anthropic
  "anthropic-ai",      // Anthropic(旧版)
  "PerplexityBot",     // Perplexity
  "Perplexity-User",   // Perplexity 用户触发
  "CCBot",             // Common Crawl(许多模型的训练数据)
  "Bytespider",        // ByteDance
  "Meta-ExternalAgent",// Meta
  "Amazonbot",         // Amazon
  "Applebot-Extended", // Apple AI 训练(注意:不是普通的 Applebot)
  "Google-Extended",   // Google AI 训练(注意:不是 Googlebot)
  "Diffbot",
  "cohere-ai",
];

function isLLMAgent(ua: string): boolean {
  if (!ua) return false;
  return LLM_USER_AGENTS.some(bot => ua.includes(bot));
}

关于两个重要的排除项:不要把 Googlebot 或 Bingbot 放进这个列表。Google-Extended 和 Applebot-Extended 是 AI 训练代理。它们与常规的搜索爬虫是分开的,而这些才是你想当作 LLM 处理的代理。

步骤 3:针对 LLM 代理屏蔽受保护的 JS 文件

这就是伪装机制的核心。选择一个文件名,用于填充你受保护内容的 JS 文件,比如 /js/protected-content.js。当请求方是 LLM 时,Worker 对该文件返回 404(或一个空的 200);对其他所有请求则正常放行:

typescript

export default {
  async fetch(request: Request): Promise<Response> {
    const ua = request.headers.get("User-Agent") || "";
    const url = new URL(request.url);
    const isLLM = isLLMAgent(ua);

    // 对 LLM 爬虫屏蔽受保护的 JS 文件
    if (isLLM && url.pathname === "/js/protected-content.js") {
      return new Response("", {
        status: 404,
        headers: { "Cache-Control": "no-store" }
      });
    }

    // ... 继续步骤 4
    return fetch(request);
  }
};

在页面本身上,将你的受保护内容包裹在一个容器中,该容器在源 HTML 中是空的,然后由 protected-content.js 在客户端填充。真实用户执行 JS 并看到内容。Googlebot 执行 JS(它会渲染)并看到内容。每个 LLM 爬虫获取该 JS 文件时得到 404,因此永远不会执行它,只会看到一个空容器。

步骤 4:对内容 URL 上的 LLM 代理提供 Markdown

同一个 Worker,第二个职责。当 LLM 代理请求一个 HTML 页面时,获取源站响应并在返回之前将其转换为 Markdown。有几种方法可以做到这一点——最干净的方法是预先为你的重要页面生成对应的 .md 版本并提供它们,因为运行时将 HTML 转换为 Markdown 可能会比较粗糙,还会增加延迟(这反而违背了提速的初衷):

typescript

// 预生成的方法:如果你有 /post/foo.html,同时也发布 /post/foo.md
async function serveMarkdownIfAvailable(
  request: Request,
  url: URL
): Promise<Response | null> {
  const mdUrl = new URL(url.toString());
  // 将 /any/path 或 /any/path/ 映射到 /any/path.md
  mdUrl.pathname = mdUrl.pathname.replace(/\/$/, "") + ".md";

  const mdResponse = await fetch(mdUrl.toString(), {
    headers: request.headers
  });

  if (mdResponse.ok) {
    return new Response(mdResponse.body, {
      status: 200,
      headers: {
        "Content-Type": "text/markdown; charset=utf-8",
        "X-Robots-Tag": "noindex, nofollow",
        "Cache-Control": "public, max-age=3600"
      }
    });
  }
  return null;
}

将其接入主处理程序:

typescript

export default {
  async fetch(request: Request): Promise<Response> {
    const ua = request.headers.get("User-Agent") || "";
    const url = new URL(request.url);
    const isLLM = isLLMAgent(ua);

    // 1. 对 LLM 代理屏蔽受保护的 JS
    if (isLLM && url.pathname === "/js/protected-content.js") {
      return new Response("", { status: 404 });
    }

    // 2. 对于 HTML 页面,向 LLM 代理提供 Markdown
    const isHtmlRequest = !url.pathname.match(
      /\.(js|css|png|jpg|jpeg|gif|svg|webp|ico|woff2?|mp4|pdf)$/i
    );
    if (isLLM && isHtmlRequest) {
      const md = await serveMarkdownIfAvailable(request, url);
      if (md) return md;
    }

    // 3. 其他所有情况返回正常页面
    return fetch(request);
  }
};

如果你不想预生成 Markdown,也可以在边缘使用 HTMLRewriter 加上一个小型的 Turndown 风格转换器来进行 HTML 到 Markdown 的转换,或者直接启用 Cloudflare 的“面向代理的 Markdown”功能,让它为你做转换。预生成能产生更好的输出结果,运行时转换则上线更快。

步骤 5:部署并验证

bash

wrangler deploy

然后验证两种行为。你需要确认:一个 LLM 用户代理得到的是伪装后的行为,而普通浏览器得到的是完整页面。最快的方式是用 curl

bash

# 应该返回 404(或空内容)
curl -A "GPTBot/1.2" https://yoursite.com/js/protected-content.js -I

# 应该返回 Markdown
curl -A "GPTBot/1.2" https://yoursite.com/your-best-post -I

# 应该返回正常的 HTML 页面,并且 JS 文件可访问
curl -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
  https://yoursite.com/your-best-post -I

部署前的实操注意事项

在你不加区分地部署这种伪装技术之前,有几点需要考虑:

  • 不要在需要出现在 AI 概览或 ChatGPT 搜索中排名的内容上做伪装。
    这项技术是为你自己的护城河准备的:专有数据、原创研究、你投入最高心力的框架。你的漏斗顶部 SEO 内容仍然应该对 LLM 完全可见,因为对于那类内容,被引用的本身就是目标。伪装的是资产,而不是认知内容。
  • 存在欺骗者。 一个意志坚定的竞争对手会使用 Chrome 的用户代理(而不是 GPTBot)来抓取。基于用户代理的伪装能阻止掉 95% 的“偷懒型”自动提取(这已经包括了大部分,包括实际的训练和引用爬虫)。对于那 5% 的坚定分子,你需要机器人管理、速率限制和 Cloudflare 的已验证机器人信号。那将是另一篇文章的内容。
  • 测试 Googlebot 看到的内容。 部署后,通过 Google 的网址检查工具运行你的页面。受保护的内容应该出现在渲染后的 HTML 中。如果没有,那你就破坏了你的 SEO,这比 AI 爬虫吃掉你的数据要严重得多。
  • 每月更新你的用户代理列表。 新的 LLM 爬虫不断出现。上面的列表在撰写本文时是准确的。六个月后就不一定了。

以上就是全部技术方案。四十行 Worker 代码、一个 JS 文件的命名约定,以及一个可选的、在 HTML 旁生成 Markdown 的构建步骤。最终结果是:你花了真金白银产出的内容依然属于你,你的“信息增益”优势保持不变,而那些原本下周就要把它喂给你竞争对手的机器人,则得到了一个礼貌的空容器。

Leave a Reply

Your email address will not be published. Required fields are marked *

Leave a Reply

Your email address will not be published. Required fields are marked *