content-security-policy 会不会挡住 Google 渲染器:怎么判断、怎么查、什么时候停手
content-security-policy 是你自己加的,但执行它的是 Google 渲染器里那个 headless Chromium。站是前端渲染的、页面在自己浏览器里看着正常,不等于渲染器拿得到正文。三步把这个成因排除掉,附一条能粘的命令和一张指令判读表。

站是前端渲染的,上线时按安全清单加了一条 content-security-policy,你自己在浏览器里从头翻到尾都正常——这三件事同时成立,页面在 Google 的渲染器里照样可能是空的。因为渲染器是一个 headless Chromium,而 Chromium 会执行 CSP。你的浏览器和它拿到的是同一份策略,但你的浏览器有缓存、有已登录状态、有你没打开的控制台。这一章讲怎么把这个成因排除掉,以及查到哪一步就该收手。
自己看没问题,证明不了渲染器拿得到
被 CSP 挡掉的资源不会弹窗,不会改状态码,页面也不会变红。它只在控制台里留一行违规记录,而多数人上线检查的时候不开控制台。更常见的是,被挡掉的不是主包,是某个二级 chunk 或者某个内容接口——首屏照样出来了,你翻到的那几页恰好不依赖它,于是你判定「没问题」。
还有一层:CSP 经常是在边缘节点或者反向代理上加的,只加在生产环境。你本地跑的那份根本没有这个头,预发环境也没有。你在这两个地方怎么点都复现不出来,而搜索引擎抓的偏偏是生产那个网址。
为什么你的 content-security-policy 对 Google 的渲染器同样生效
Google 自己把渲染这条链路写得很直白。Understand the JavaScript SEO basics(2026-09-01 取)里写着 All pages with a 200 HTTP status code are sent to the rendering queue, no matter whether JavaScript is present on the page.(凡是返回 200 的页面都会进渲染队列,不管页面上有没有 JavaScript),以及 Once Google's resources allow, a headless Chromium renders the page and executes the JavaScript.(等资源腾得出来,一个 headless Chromium 会渲染页面并执行 JavaScript)。同一页开头还有一句 While Google Search runs JavaScript with an evergreen version of Chromium(Google 搜索用的是常青版 Chromium 来跑 JavaScript)。同一台机器被点了两次名:一个当代 Chromium,跑你的脚本。
Chromium 会执行 CSP,这本来就是这个响应头存在的理由。MDN 的 Content-Security-Policy 参考页(2026-09-01 取)把它定义成让站点管理员 control resources the user agent is allowed to load for a given page(控制某个页面上用户代理被允许加载哪些资源)——用户代理,不管那个用户代理是谁。两句话摆在一起,就得到本章成立的那个判断:一个脚本在访客浏览器里被你的策略拒了,在渲染器里同样会被拒。
第二步是我们的推论,不是官方原文。Google 没有专门为 CSP 出过文档,我们也没有做过对照实验——两张一模一样的页面,一张挂上会拦截的策略,都提交上去再隔一段时间比结果。所以请把它当成一个值得排除的成因,而不是一个已经被证实的机制。谁跟你说「Google 说了 CSP 会影响收录」,那句话是不存在的。
不执行脚本的爬虫根本不会被你的 CSP 挡住;会被它挡住的那一个客户端,恰好是决定你的正文存不存在的那一个。
什么时候值得查,什么时候到此为止
只有一种页面值得花这五分钟:读者看到的那些字是文档到达之后由 JavaScript 生成的。用 curl 取一次自己的页面,看回来的 HTML 里有没有正文。有,就到此为止——CSP 管的是浏览器允许加载什么,对服务器已经发出去的文字它没有任何权限。服务端渲染的站在这一行就可以关掉这一章。
另一头的边界同样清楚。那些只读 HTML、压根不执行脚本的 AI 爬虫,完全不在你这条策略的管辖范围内,那是另一个问题,在AI 爬虫会执行 JavaScript 吗那一章。所以这个检查天生是窄的:它只能告诉你 CSP 是不是一个说得通的成因,不能告诉你它就是成因。如果脚本全都加载成功、页面渲染出来还是空的,那问题在别处,本章帮不上忙。
三步查完自己的站
每一步都有一句「你现在应该能说出口的话」。说不出来就把这一步重做一遍,别往下走。
- 先拿到服务器真正发出去的那份策略。请求页面,看响应头,不是看 HTML。看完响应头再回头看一遍文档本身,因为用
<meta http-equiv>声明的策略根本不会出现在响应头里。这一步结束的标志是:你能把完整的策略字符串粘到别的地方,或者你能确定这个网址上没有策略——后一种情况你已经查完了,成因在别处。 - 把放行名单和页面实际拉取的域名对一遍。从取回来的 HTML 里把所有脚本域名抠出来,跟
script-src里写的源逐个比对;如果没写script-src,就比对default-src——MDN 写明它是所有其它 fetch 指令的兜底。前端要调的内容接口用同样的办法对connect-src。这一步结束时你手上应该有两列:一列域名,一列放行还是没放行。任何一个承载正文的域名落在「没放行」那列,答案就出来了。 - 换一个会报违规的客户端确认。打开控制台再刷新一次,被挡掉的资源会打出一行违规,并且指名是哪条指令拦的。然后去 Search Console 的网址检查工具看同一个网址,那读的是 Google 自己那次渲染。Google 的 Fix search-related JavaScript problems(2026-09-01 取)对这两个工具的说法是 You can see loaded resources, JavaScript console output and exceptions, rendered DOM, and more information.(你能看到已加载的资源、JavaScript 控制台输出与异常、渲染后的 DOM 等信息)。看到渲染后的 DOM 里到底有没有你的正文、缺的是哪个脚本,这一步就完了。
一条能粘的命令,和一张指令判读表
拿自己的一个网址粘进去跑。它一次打完三样:响应头里的策略、文档里声明的策略、页面引用的全部脚本域名——也就是上面第一步和第二步。
URL="https://example.com/"
echo "--- 响应头里发出去的策略"
curl -sSI "$URL" | grep -i '^content-security-policy'
echo "--- 文档里声明的策略"
curl -sS "$URL" | grep -io '<meta[^>]*content-security-policy[^>]*>'
echo "--- 页面实际引用的脚本域名"
curl -sS "$URL" \
| grep -Eo 'src="https?://[^/"]+' \
| sed 's/src="//' | sort -u
如果你的边缘节点会按客户端发不同的响应头,就带上爬虫的 User-Agent 再跑一次第一条命令,两份结果对照着看。拿到策略之后按下面这张表读。表里前四条决定渲染出来的页面里有没有字,后两条是大家写得最多、但跟这件事最没关系的两条。
| 指令 | 它管什么 | 写窄了会挡住 | 只读爬虫 |
|---|---|---|---|
script-src | JavaScript 与 WebAssembly 资源的合法来源 | 生成正文的那个包加载不出来 | 不受影响,它们不执行脚本 |
default-src | 所有其它 fetch 指令的兜底 | 你忘了写进去的东西一次性全被拒 | 不受影响 |
connect-src | 脚本接口能请求的网址 | 脚本跑起来了,内容接口被拒,页面渲染出空状态 | 不受影响 |
style-src | 样式表的合法来源 | 版式散架,但文字通常还在 | 不受影响 |
frame-ancestors | 允许哪些父页面嵌入本页 | 渲染器需要的东西一样都不挡 | 不受影响 |
upgrade-insecure-requests | 把 http 地址当成 https 来取 | 什么都不挡,它是改写不是拒绝 | 不受影响 |
指令语义摘自上面那份 MDN 参考页,2026-09-01 取。这张表最有用的是最后两行:真实世界里的策略,大半内容跟「渲染出来的页面有没有字」毫无关系。
三种常见的做错
三种都是用错了证据,然后得出一个很笃定的结论。
- 把
<meta>里的策略当成整份文档都受管。CSP Level 3 规范(2026-09-01 取自 w3c.github.io/webappsec-csp,同日 www.w3.org 的 TR 版本返回 403)写着 Authors are strongly encouraged to place meta elements as early in the document as possible, because policies in meta elements are not applied to content which precedes them.(强烈建议把 meta 元素放在文档尽量靠前的位置,因为 meta 里的策略不作用于它前面的内容),并且点明排在这条 meta 之前的 script 元素 will not be blocked(不会被拦)。也就是说,你在源码里读到的那条策略,未必是当时作用在你正在追查的那个脚本上的策略。MDN 另外写了两句:响应头形式 should be set on all responses to all requests, not just the main document(该给所有请求的所有响应都设上,不只是主文档),而 meta 形式 does not support all CSP features(并不支持全部 CSP 特性)。 - 把 report-only 当成已经生效的策略。MDN 的 CSP 指南(2026-09-01 取)说得很死:report-only 模式下 the policy is not enforced, but any violations are sent to the reporting endpoint specified in the policy(策略不执行,只是把违规发到策略里指定的上报端点)。一个站可以同时挂两个头,只有不带后缀的那个才真的拦东西。grep 的时候只匹配头名、不看后缀,你会为一条从来没拦过任何请求的策略搭进去一个下午。
- 在一个根本没有这条策略的环境里测。这条前面提过一次,值得在这里再写死:只在生产加了 CSP,本地和预发都没有,于是你怎么点都复现不出来。这个检查的每一步都要打生产那个公开网址——就是你会拿去提交收录的那一个。
27 个首页里有多少个真的发了这个头
给一点量级,只当背景,不当结论。2026-09-01 我们从日本大阪出口,对 27 个可比首页各请求一次,不执行 JavaScript:17 个发了 content-security-policy 响应头,1 个用 <meta http-equiv> 声明,9 个三种都没有。最长的一条 7,702 字节,在 www.notion.com 上;最短的 22 字节,在 railway.com 上,全部内容只有 frame-ancestors 'self'。那 17 条里还有 14 条允许 'unsafe-inline'。这些数字只描述那 27 个站在那一天的样子,预测不了你的站——它们能提示的只有一件事:紧到足以搞坏一次渲染的策略,比 CSP 这个话题的写作量让人以为的要少见。
常见问题
加了 CSP 之后 Google 还能正常收录吗
能,除非某条指令挡住了「生成正文」那条路上的脚本,而且你的页面本来就靠脚本才有字。策略严格本身不扣分。服务端渲染的站把策略收得再紧,在搜索这边也不付任何代价。
report-only 算不算已经生效
不算,它什么都不拦。它的用途是在真正执行之前先看看新策略会打坏什么,只有这一个用途。把「站上有这个头」读成「站上有策略在生效」,是这件事里最常见的一次误读。
写在响应头里和写在 meta 里有什么区别
第一,meta 形式在 curl -I 里看不见,你得去读文档。第二,它只作用于源码里排在它后面的内容,所以能推出的结论也更窄。第三,report-only 没法用 meta 下发。手上有得选的话,先把策略挪到响应头再开始排查。
我的站是服务端渲染的,还需要查这个吗
不需要。取一次 HTML,正文已经在里面,CSP 就拿它没办法。真要查,去查上一层:看看 AI 爬虫从你的网址拿到了什么,再拿结果对照AI 爬虫从前端渲染的网站上拿到了什么里的字符数。裸取就空的页面,改多少条策略都救不回来。
本文属于 QueryWin 实操手册 · 第 2 阶


