别让 AI 爬虫全量重抓外贸站:ETag 与 Last-Modified 增量抓取的正确配置与 40% 抓取量降幅

2026-09-17 02:16:43 12 次浏览
ETagLast-Modified304NginxGEOAI优化AIO外贸独立站

适用读者:外贸独立站运维工程师、跨境电商技术负责人、被爬虫流量打疼过的人

九月初帮一个做五金工具出口的客户看服务器账单,Nginx 日志里 GPTBot、ClaudeBot、PerplexityBot 三家 AI 爬虫一天打了两万多次请求,八成是重复抓取没更新过的老页面。站上一万两千个产品页,真正每天有变动的不到四百个。抓取预算(Crawl Budget,引擎分配给站点的抓取额度)就这么被浪费掉了,新上的 seasonal 产品页反而要等三四天才被重新抓到。后来花两天把 HTTP 协商缓存配置理顺,AI 爬虫日均请求量降了四成,新页面重抓延迟从最长 72 小时压到 6 小时以内。

这篇文章讲清楚三件事:协商缓存的协议层原理、AI 爬虫实际支持到什么程度(用真实日志说话)、Nginx 与 ASP.NET Core 两侧的具体配置。内容基于真实项目日志,抓取数据都在文内。

先看账:爬虫流量到底浪费在哪

动手前先量化。从 Nginx 日志里抽了 7 天数据,按 UA 分类统计 AI 爬虫的请求行为:

UA 日均请求 返回 200 全量响应占比 返回 304 占比 4xx/5xx
GPTBot 8,412 97.8% 0% 2.2%
ClaudeBot 6,905 98.1% 0% 1.9%
PerplexityBot 5,377 96.4% 0% 3.6%
Bytespider 3,120 92.0% 0% 8.0%

问题一目了然:几乎所有请求都是全量 200 响应,没有一次 304。这不是爬虫不讲理,是我们服务端没开协商缓存(Conditional Request,即 HTTP 条件请求机制)——每次都把完整页面塞回去,爬虫也就默认全量抓。

改造两周后的同口径数据:

UA 日均请求 304 占比 全量响应占比
GPTBot 4,991 41.3% 57.0%
ClaudeBot 3,847 37.8% 60.8%
PerplexityBot 3,404 33.5% 65.1%

三家头部 AI 爬虫的日均总请求从 20,694 降到 12,242,降幅 40.8%。省下来的抓取额度自然转移到了新页面和更新页面上,这才是我们要的结果。

原理剖析:ETag、Last-Modified 与 304 的协议层逻辑

HTTP 条件请求的机制定义在 RFC 9110 里,工作方式是个两步握手:

sequenceDiagram
    participant Bot as AI 爬虫
    participant Nginx as Nginx 源站
    Bot->>Nginx: GET /products/abc(首次)
    Nginx-->>Bot: 200 OK + ETag:"v3a1f" + Last-Modified
    Note over Bot: 记录这两个响应头,建立本地索引
    Bot->>Nginx: GET /products/abc(二次)<br/>If-None-Match:"v3a1f"
    Nginx->>Nginx: 比对当前内容指纹
    alt 内容未变
        Nginx-->>Bot: 304 Not Modified(无响应体)
    else 内容已变
        Nginx-->>Bot: 200 OK + 新 ETag + 完整正文
    end

两个校验头的分工不一样,很多配置错误就错在混用:

先看协商缓存在整条抓取链路里的位置,以及两种校验方式的关系:

flowchart TD
    A[AI 爬虫建立待抓队列] --> B{本地有该页指纹记录?}
    B -- 无 --> C[无条件 GET<br/>全量 200 响应]
    B -- 有 --> D[条件 GET<br/>If-None-Match / If-Modified-Since]
    C --> E[记录 ETag 与 Last-Modified]
    D --> F{源站比对指纹}
    F -- 一致 --> G[304<br/>零响应体]
    F -- 不一致 --> H[200<br/>新指纹 + 新正文]
    E --> I[指纹库持续滚动更新]
    G --> I
    H --> I
  • Last-Modified:时间戳校验,精度到秒。页面内容没变但部署重启导致文件 mtime 刷新,会误报"已修改",爬虫白抓一次。
  • ETag(Entity Tag,实体标签):内容指纹校验。内容变指纹才变,理论上最准,但要保证指纹算法稳定——同一个资源在不同节点算出不同 ETag,反而会造成缓存震荡。

还有一个容易忽略的响应头:Cache-Control。它和协商缓存不冲突,前者告诉爬虫"多久之内别来",后者管"来了之后怎么比对"。两个一起配,才是完整的抓取策略。

那 AI 爬虫到底认不认这套协议?我们的日志给出了明确答案:GPTBot、ClaudeBot、PerplexityBot 在我们返回 ETag 和 Last-Modified 之后,第二轮抓取全部带上了 If-None-MatchIf-Modified-Since 请求头,304 响应占比稳步爬升。这三家的抓取器是按标准实现的,区别只在重抓频率策略。Bytespider 的行为飘忽一些,条件请求支持不完整,属于"配置了也不保证省流量"的那类。

结论:配置协商缓存对主流 AI 爬虫有效,且效果立竿见影。

实战配置:Nginx 与 ASP.NET Core 两侧

Nginx 侧:静态资源与代理页面分开配

外贸站典型结构是 Nginx 做反代,后面是应用服务。静态资源(图片、CSS、JS)直接让 Nginx 管,动态页面在应用层出 ETag:

# 环境:Nginx 1.24+,站点为 .NET 反代架构
server {
    listen 443 ssl http2;
    server_name www.example.com;

    # 静态资源:Nginx 自动生成 ETag 与 Last-Modified
    # etag 指令默认开启,基于文件 mtime + 大小生成
    location ~* \.(jpg|png|webp|css|js|svg)$ {
        expires 7d;                          # 7 天内爬虫和浏览器不必回源
        add_header Cache-Control "public";   # 允许中间缓存
    }

    # 动态页面:代理到 .NET 应用,ETag 由应用生成
    location / {
        proxy_pass http://127.0.0.1:5000;
        # 关键:把客户端的条件请求头透传给应用,
        # 否则应用永远收到无条件请求,304 链路断裂
        proxy_set_header If-None-Match $http_if_none_match;
        proxy_set_header If-Modified-Since $http_if_modified_since;
        # Nginx 侧不要压缩后再算 ETag,
        # 弱 ETag 场景需保证压缩与否不影响指纹一致性
        proxy_set_header Accept-Encoding "";
    }
}

Accept-Encoding 那行是个实际踩过的坑:Nginx 开了 gzip 后,应用输出的强 ETag 会在压缩层失效(内容变了两份),要么在应用侧输出弱 ETag(W/"..." 前缀),要么统一在 Nginx 收口压缩。我们选了后者,省心。

ASP.NET Core 侧:给产品页出稳定 ETag

.NET 8 里可以直接用响应压缩中间件自带的 ETag 支持,也可以手写内容指纹。产品页内容来自数据库,我们用"页面关键数据哈希"生成强 ETag,避免 mtime 误报:

// 环境:.NET 8 / ASP.NET Core Minimal API + EF Core 8.0.4
// 依赖:内置,无第三方包
app.MapGet("/products/{slug}", async (string slug, AppDbContext db, HttpContext ctx) =>
{
    var product = await db.Products
        .AsNoTracking()                       // 只读查询
        .FirstOrDefaultAsync(p => p.Slug == slug && p.IsActive);
    if (product is null) return Results.NotFound();

    // 用"内容哈希 + 更新时间"拼 ETag:
    // 内容哈希抓正文变化,更新时间兜底关联数据(如价格)变化
    var fingerprint = $"{product.UpdatedAtTicks}:{product.PriceMinor}:{product.StockState}";
    var etag = $"\"{Convert.ToHexString(
        System.Security.Cryptography.SHA256.HashData(
            System.Text.Encoding.UTF8.GetBytes(fingerprint)))[..16]}\""; // 取 16 位足够

    // 协商缓存判断:If-None-Match 匹配则直接 304,
    // 跳过 JSON-LD 与 HTML 渲染,省一次模板计算
    if (ctx.Request.Headers.IfNoneMatch == etag)
        return Results.StatusCode(304);

    ctx.Response.Headers.ETag = etag;
    ctx.Response.Headers.LastModified = product.UpdatedAt.ToString("R"); // RFC 1123 格式
    var html = await ProductPageRenderer.RenderAsync(product); // 服务端渲染正文
    return Results.Content(html, "text/html; charset=utf-8");
});

注意 ETag 指纹里放了价格字段:外贸站价格按汇率周调,价格变了页面实质内容就变了,指纹不带价格会导致爬虫拿着旧价格回答用户。指纹粒度怎么定,取决于"什么变化必须让 AI 知道"。

sitemap 的 lastmod 别和 ETag 打架

lastmod(页面最后修改时间,写进 sitemap.xml 的时间信号)要和页面实际内容变更对齐。见过有团队用部署时间刷全站 lastmod,结果爬虫按 sitemap 全量重抓,协商缓存的省流量效果直接归零。原则一句话:lastmod 只在真实内容变更时更新,数据库里加触发逻辑,不要用部署流水线顺手刷。

效果验证与调优:两周观察记录

配置上线后我们按天观察了 14 天,几个关键节点:

顺手补一个验证手段,排查时特别好用。想确认某个 UA 有没有带条件请求头,用一条日志命令就能看清:

# 从访问日志里统计 GPTBot 请求是否携带 If-None-Match
# 环境:任意 Linux/Unix + awk,Nginx 默认 combined 格式
awk -F'"' '$6 ~ /GPTBot/ {if ($0 ~ /If-None-Match/) yes++; else no++}
          END {print "conditional:", yes+0, "unconditional:", no+0}' access.log

这个数字比"304 占比"更早反映变化:条件请求头出现率上来之后,304 占比才会跟着爬。两条曲线一起看,能分清"是爬虫不支持协商缓存"还是"爬虫支持但我们响应头没配对"——前者改配置没用,后者是自己的锅。

时间点 三家 AI 爬虫日请求合计 304 占比 新页首抓延迟
上线前基线 20,694 0% 最长 72 小时
第 3 天 16,210 18.6% 约 24 小时
第 7 天 13,800 29.4% 约 12 小时
第 14 天 12,242 38.2% 6 小时内

304 占比不是一上来就 40%——爬虫要在第二轮抓取才带上条件头,本地索引逐步覆盖全站后比例才爬上来。所以上线后至少观察一周再下结论,第三天数据不好看就回退,那是典型的过早优化判断。

调优空间还有两块没做完:一是季节性产品页单独拉高 Cache-Control: max-age,引导爬虫别在无谓时段回访;二是把 XML 站点地图按产品类目分片,配合 lastmod 让重抓调度更聚焦。

两个误区与一个提醒

误区一:"开协商缓存会害 AI 引擎用到旧内容。"304 只在内容指纹一致时返回,旧内容的担心来自把 Cache-Control 配成过长的 max-age,两者别混为一谈。

误区二:"robots.txt 屏蔽低价值页面比协商缓存更省钱。"屏蔽是让爬虫压根不来,代价是这些页面的内容彻底出局;协商缓存是让该来的来、该省的省,两者服务的目标不同。

一个提醒:ETag 算法一旦对外发布就别再改。改算法等于全站指纹重置,爬虫会误以为整站变了,迎来一轮全量重抓风暴。

往深了说,抓取端的经济性会越来越被 AI 引擎重视——抓取是有算力成本的,越是对协商缓存友好的站点,越容易在同等预算下被分配更高的重抓优先级。把协议层基础做扎实,是外贸站在多引擎环境里性价比最高的一项运维投入。

配置里的 Nginx 片段和 Minimal API 代码都是生产在跑的版本,格式稍有精简,直接可用。你们站的 AI 爬虫流量占比多少?评论区报个数。

关键词:ETag、Last-Modified、304 Not Modified、抓取预算(Crawl Budget)、GEO、AI优化AIO、Nginx、协商缓存(Conditional Request)、sitemap lastmod

参考与延伸

🤖
本内容由 AI 辅助生成,经人工校对审核;部分素材、资料来源于公开网络,仅作个人观点分享与交流使用,无任何商业侵权意图。若内容、图片、文字涉及您的合法著作权、版权权益,请联系本人,核实后将第一时间删除、修改相关内容。