ads.txt 找不到或在 AdSense 里验证不通过

AdSense 报 "Earnings at risk — you need to fix some ads.txt file issues"。怎么把 ads.txt 部署到正确位置、用 curl 验证、清掉警告。

AdSense 控制台横幅写着 “Earnings at risk — you need to fix some ads.txt file issues to avoid severe impact to your revenue”。你加了文件,但警告不消。或者 https://yourdomain.com/ads.txt 返回 404,但你确定推送了。

最快修复: 从 AdSense 复制那行精确文本(首页有提示就点 Fix now,否则进 Sites → 你的站 → Ads.txt snippetCopy),放进框架的静态目录,让它在 https://yourdomain.com/ads.txtHTTP 200Content-Type: text/plain 返回,部署,再用一条 curl 确认。然后就是等——AdSense 按自己的节奏重爬,多数情况几天(低流量站可能要一个月,见 Google 官方文档)。所以「文件验证已正确、横幅却还挂着」通常只是爬取延迟,不是真问题。

ads.txt 规范本身很简单——根目录一个纯文本文件,列出授权 seller——但部署上有几个坑能套住大多数独立站。

你属于哪一类?

先跑这一条,结果会告诉你该看哪一节。

curl -sIL "https://yourdomain.com/ads.txt"
你看到最可能的原因看这里
404 / 403路径错,或框架没把文件 ship 出去原因 1、2
200Content-Type: text/html平台 / CDN 把 .txt 改写成 HTML原因 3
200text/plain,但内容不对publisher ID 错、BOM、智能引号原因 4
200、内容对,但只有加 ?nocache=... 才对CDN 缓存陈旧原因 5
301/302 跳到另一个域名重定向离开了根域名原因 6
全都对,横幅还在AdSense 还没重爬原因 7

-L 会跟随重定向,让你看到最终响应,而不是第一跳。

常见原因

按命中率从高到低。

1. 文件路径错

ads.txt 必须能在 https://yourdomain.com/ads.txt 解析到——不是 /public/ads.txt、不是 /assets/ads.txt、不是 /static/ads.txt。Google 的抓取「will start at the root domain, and the root domain needs to return from, or redirect to, the ads.txt file」(Google 文档)。它只命中这一个 URL。

怎么判断:

curl -sI "https://yourdomain.com/ads.txt"

要看到 HTTP/2 200。其它(404、跳错地方的 301、403)都说明根目录解析不到。

2. 框架没把文件 ship 出去

文件源放在仓库的静态目录里,build 会把它复制到 web 根目录。放进 src/ 就永远不会被部署。

  • Astro: public/ads.txt
  • Next.js(pages 或 app router): public/ads.txt
  • SvelteKit: static/ads.txt
  • Nuxt 3: public/ads.txt(Nuxt 2 旧版用 static/
  • Hugo / Jekyll: static/ads.txt / 仓库根
  • Vercel 静态部署: 项目根或 public/

怎么判断: 部署后 curl https://yourdomain.com/ads.txt。404 就是 build 没带它——检查文件是否已提交、是否在静态目录里、有没有被 gitignore 掉。

3. Content-Type 错,或返回 HTML「软 404」

某些配置(nginx 配错、CDN 的兜底改写规则、或者把未知路径都返回 index.html 的 SPA)会把 .txtContent-Type: text/html 返回,或者返回一个状态码是 200 的 HTML 404 页。AdSense 按 HTML 解析,找不到 seller 行,就当文件缺失。

怎么判断:

curl -sI "https://yourdomain.com/ads.txt" | grep -i content-type

应该是 Content-Type: text/plain(常见 text/plain; charset=utf-8)。如果是 text/html,说明平台在改写,或者 SPA 兜底吞掉了这个请求。

4. publisher ID 错或夹带不可见字符

这一行必须是:

google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0

四个逗号分隔的字段:广告系统域名(google.com)、你的 publisher ID、关系类型(DIRECT)、Google 的认证机构 ID(f08c47fec0942fa0,所有 AdSense 发布商都一样)。常见破坏因素:尾部空格、复制粘贴带来的智能引号、文件开头的 BOM、写错的 pub- 号、漏了逗号。

怎么判断:

curl -s "https://yourdomain.com/ads.txt" | od -c | head -3

留意意外的字节——UTF-8 BOM 会在文件最开头显示为 357 273 277。有的话,把文件重新存为不带 BOM 的 UTF-8。

5. Cloudflare / CDN 缓存还在返回旧版

你改了文件,但缓存里的旧副本仍然胜出。AdSense 爬到的还是旧的(缺失)状态。

怎么判断:

curl -s "https://yourdomain.com/ads.txt?nocache=$(date +%s)"

查询串能强制 cache miss。如果它和缓存 URL 的内容不一样,就是 CDN 缓存陈旧——清掉它(下一节原因 5 的修复)。

6. 重定向离开了根域名,或只有一个协议在返回

Google 明确允许重定向,但有规则。单次重定向可以指向根域名之外(比如 example1.com/ads.txtexample2.com/ads.txt)。多次重定向只有在每一跳都留在原根域名内(含 www 或其它子域名)时才行。另外,爬虫会同时尝试 http://https://;如果只有一个协议返回文件,要做重定向让另一个协议也能拿到。

怎么判断:

curl -sIL "http://yourdomain.com/ads.txt"   # 注意是 plain http
curl -sIL "https://yourdomain.com/ads.txt"

两条都应该最终落在一个返回你 ads.txt200 上。跳两次到别的域名、或 http:// 走不通,都会验证失败。

7. AdSense 还没重爬(最常见的虚惊)

AdSense 按自己的节奏重查 ads.txt,不按需。按 Google 说法,改动需要几天反映,对广告请求很少的站可能要一个月。如果之前爬过的文件短暂返回 5xx 错误,Google 会保留上一次成功的条目最多 5 天,所以临时故障不会立刻重新触发警告。

怎么判断: curl 确认文件正确、又是最近才修的,那就是在等。别反复重发。

最短修复路径

第 1 步:从 AdSense 拿到精确文本

如果 AdSense 首页有提示,点 Fix now。否则进 Sites → 点你的站 → Ads.txt snippetCopy。复制那一行,别手敲。

应该像:

google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0

第 2 步:放到正确路径

框架文件放哪URL
Astropublic/ads.txt/ads.txt
Next.js(pages 或 app)public/ads.txt/ads.txt
SvelteKitstatic/ads.txt/ads.txt
Nuxt 3public/ads.txt/ads.txt
Hugostatic/ads.txt/ads.txt
纯 HTMLwebroot 的 ads.txt/ads.txt

部署,再验证。

第 3 步:用 curl 验证

curl -sIL "https://yourdomain.com/ads.txt"
# 期望:最终 HTTP/2 200, Content-Type: text/plain

curl -sL "https://yourdomain.com/ads.txt"
# 期望:google.com, pub-XXXXX, DIRECT, f08c47fec0942fa0

Content-Typetext/html 或其它,说明平台或 CDN 在改写。加显式规则:

Vercelvercel.json

{
  "headers": [
    { "source": "/ads.txt", "headers": [{ "key": "Content-Type", "value": "text/plain" }] }
  ]
}

Netlify_headers

/ads.txt
  Content-Type: text/plain

Cloudflare Pages — 项目根目录的 _headers

/ads.txt
  Content-Type: text/plain

第 4 步:确认 robots.txt 没拦住爬虫

Google、Mediapartners-GoogleGoogle-Display-Ads-Bot 这几个爬虫都遵守 robots.txt。如果某条规则 disallow 了 /ads.txt 路径或其中某个 user agent,文件会被悄悄忽略。检查一下:

curl -s "https://yourdomain.com/robots.txt"

如果有 Disallow 规则可能命中 /ads.txt,显式加一条 allow:

User-agent: *
Allow: /ads.txt

第 5 步:清 CDN 缓存

Cloudflare → CachingConfigurationPurge CacheCustom Purge → 输入 https://yourdomain.com/ads.txt。Vercel 和 Netlify 通常每次部署自动清。

第 6 步:等 AdSense 重新核对

横幅不会瞬间消。只要 curl 返回的是正确的纯文本,AdSense 会在下一次抓取时识别——通常几天,低流量站可能要一个月。忍住别反复重发,那并不能加快抓取。

第 7 步:CI 加校验,别让它再悄悄回归

# .github/workflows/verify-ads-txt.yml 或 postdeploy 脚本
curl -sfL "https://yourdomain.com/ads.txt" | grep -q "pub-$ADSENSE_PUB_ID" || \
  { echo "ads.txt missing or wrong"; exit 1; }

每次部署后跑一次。文件一旦消失或被改写,部署的那一刻就能发现。

怎么确认修好了

以下全部成立才算修好:

  1. curl -sIL https://yourdomain.com/ads.txt 最终落在 HTTP/2 200
  2. Content-Typetext/plain
  3. curl -sL https://yourdomain.com/ads.txt 打印出你那行精确的 pub- 文本,没有 BOM 或多余字符。
  4. http://https:// 都能拿到文件。

这些都通过后,AdSense 横幅纯粹是抓取延迟的残留,下一次抓取就会清掉。

容易误判的情况

ads.txt 放错目录(或被框架 404 掉)是静态站最常见的原因。紧随其后的是 robots.txt 里一条规则悄悄拦住了路径。用 curl 验证,别只盯着代码库——AdSense 爬的是部署出来的实际结果。

预防建议

  • ads.txt 永远放在静态 / public 目录,每次部署自动带上。
  • CI 里加校验:curl -sfL yourdomain.com/ads.txt | grep -q pub-$PUB_ID
  • 平台配置里强制 Content-Type: text/plain
  • robots.txt 里保持 /ads.txt 可访问。
  • 改 CDN 或 DNS 后,重新验证 ads.txt 在两个协议下都正确返回。
  • 接第二家广告网络(Mediavine、Ezoic、Raptive),把它们的行也加上——每个授权 seller 一行。

FAQ

  • 修好后 “Earnings at risk” 横幅多久消失?Google 说法,多数情况几天,广告请求很少的站可能要一个月。没有手动「立即重爬」的按钮。
  • 只用 AdSense 也要 ads.txt 吗? 要。ads.txt 里没有你的 publisher ID,AdSense 会限制对你库存的需求竞价——这正是警告所指的问题。
  • ads.txt 能多行吗? 能,每个授权 seller 一行。你合作的每家广告网络各占一行。
  • 文件在根域名上,子域名却还报警告,为什么? 只有当子域名的授权 seller 或 publisher ID 与根域名不同时,才需要在子域名上单独放 ads.txt,并从根 ads.txt 引用它;否则根域名的文件就覆盖了它。
  • 301 重定向到我的 ads.txt 行吗? 单次重定向可以离开根域名;多次重定向则要求每一跳都留在同一根域名内。跳到外部主机的多跳链会失败。
  • AdSense 说「文件缺失」,但 curl 能看到,怎么办? 检查 robots.txt 没拦住路径,确认 http://https:// 都能返回,然后等下一次抓取。文件正确而横幅还挂着,就是抓取延迟。

相关阅读

标签: #AdSense #变现 #排查 #ads.txt