百度网站所有权验证:HTML文件、Meta标签与CNAME三种方式

接手一项百度相关的任务,第一道拦路的往往就是所有权验证页面。在百度确认这个域名归您所有之前,它不会接收站点地图,不会开放收录报告,也不允许您推送哪怕一条URL。证明的方式有三种,各自的行为并不相同,而且出错的方式与您在Google Search Console上熟悉的那一套也不一样。
下面是每种方式的具体实现,以及验证通常失败的原因和从终端逐项排查的方法。
验证在整个流程中的位置
百度面向站长的产品是百度搜索资源平台,地址是 ziyuan.baidu.com,很多人仍习惯称它为百度站长平台。添加站点、选择验证方式之后,百度会从您的域名抓取一样东西来确认控制权。只有通过之后,链接提交、站点地图上传和抓取诊断才会解锁。
在选择方式之前,站点的定义方式有两点最容易让人栽跟头:
- 协议和主机名都属于站点身份的一部分。 在百度看来,
https://example.com和https://www.example.com是两个不同的站点,http://版本同样是另一个。请添加canonical URL实际使用的那个origin。如果验证错了对象,之后提交的一切都指向一个没有任何流量的站点。 - 抓取来自中国大陆。 用来证明所有权的东西必须能从中国大陆经公网访问到,前面不能挡着一层验证页面。本文末尾列出的大部分失败原因,都能由这一点解释。
在这一切之前,还有账号这一关。要进入验证页面就必须先有百度账号,而注册需要通过中国大陆手机号的短信验证。对身处欧洲或英国的公司来说,这一条值得在花一个下午配置DNS之前先弄清楚。文末会再谈到它。
方式一:HTML验证文件
最常用的方式,只要能部署静态文件就优先选它。
百度会为您的站点生成一个文件供下载,文件名形如:
baidu_verify_codeva-XXXXXXXXXX.html
较早添加的站点拿到的可能是 baidu_verify_XXXXXXXXXX.html 这种形式,有些面板显示的前缀是 code- 而不是 codeva-。不要照着博客文章去拼文件名或token:请直接下载百度给出的文件,或者从面板里逐字符复制那串字符。文件内容很短,通常只有token本身。
它必须从您所注册的那个origin的根目录下提供:
https://example.com/baidu_verify_codeva-XXXXXXXXXX.html
不能放在子目录里,不能放在语言前缀之下,也不能放在另一台主机上。
各类技术栈的存放位置
| 技术栈 | 存放位置 |
| --- | --- |
| Vite、Create React App、SvelteKit(static)、Nuxt | 项目根目录下的 public/ 或 static/ |
| Next.js(两种路由均适用) | public/ |
| Hugo、Astro | 分别是 static/ 和 public/ |
| Laravel | public/ |
| Django | 由 collectstatic 收集的 static/ 目录,或由nginx直接提供 |
| ASP.NET Core | wwwroot/ |
| WordPress | 与 wp-config.php 同级的网站根目录,通过SFTP上传 |
| 原生nginx或Apache | 文档根目录 |
用百度的方式自查
curl -sSI https://example.com/baidu_verify_codeva-XXXXXXXXXX.html
curl -sS https://example.com/baidu_verify_codeva-XXXXXXXXXX.html
您要看到的是 HTTP/2 200、值为 text/html 的 content-type、没有 location 头,并且响应体恰好就是那串token。出现下面三种结果,说明还没完成:
- 返回301或302。 有东西在规范化路径,最常见的是末尾斜杠规则,或者把所有未匹配路径都送到
/en/...的语言重定向。请在catch-all之前加一条例外。 - 返回200,但内容是应用外壳。 看着像通过、实际没通过的情况里,这一种最常见。带catch-all路由的单页应用会用
index.html和200状态码回应任何路径,于是文件看起来存在,响应体却是您的React标签。百度读取响应体,找不到token,于是拒绝。请让静态文件的优先级高于SPA的兜底路由。 - 部署之后仍然404。 通常是CDN缓存了那次否定响应。请精确清除这个路径的缓存,然后用
curl -H 'Cache-Control: no-cache'重新检查。
验证通过之后,请把文件留在代码库里。百度会定期复查所有权,丢失验证文件的站点会悄无声息地从面板里掉出去。
方式二:HTML meta标签
当您能改模板但没法往网站根目录扔文件时用这一种,托管型CMS或平台化主机上通常就是这种情况。
百度会给出这样一行标签,放进首页的 <head> 里:
<meta name="baidu-site-verification" content="codeva-XXXXXXXXXX" />
name 属性永远是 baidu-site-verification,连字符在内一字不差。content 的值是面板里的token。
有三条规则决定它能不能生效:
- 必须位于
<head>内。 解析器如果在<body>里发现它,head早已被当作结束。任何在第一个非head元素之后注入的内容都太迟了。 - 必须出现在服务器返回的HTML里。 用
view-source:打开,更好的做法是运行curl -sS https://example.com | grep baidu-site-verification。如果这行标签只在DevTools的元素面板里看得到,那它是hydration之后由JavaScript加上去的,不算数。客户端渲染的React和Vue应用就是栽在这里,这也是为什么SPA上用文件方式更省事。 - 必须扛得住框架。 head管理库会做去重和净化。Next.js希望它写在metadata导出里,而不是某个组件里孤零零的一个
<meta>。React Helmet会丢掉在其provider之外渲染的标签。有些CMS安全插件会剥掉名称不认识的meta标签。标签管理工具放不了它,因为标签管理工具运行在客户端。
各框架的写法:
// Next.js App Router,app/layout.js
export const metadata = {
other: { 'baidu-site-verification': 'codeva-XXXXXXXXXX' },
}
// WordPress,子主题的 functions.php
add_action('wp_head', function () {
echo '<meta name="baidu-site-verification" content="codeva-XXXXXXXXXX" />' . "\n";
}, 1);
服务器端渲染的站点,把它写进基础模板、放在charset和viewport旁边,之后就不用再管了。Hugo是 layouts/_default/baseof.html,Django则是所有页面继承的那个基础模板。
方式三:CNAME记录
DNS方式适合这几种情况:根本没法部署、站点在一台您管不到的设备后面,或者您希望这次验证能扛过以后每一次重新部署。它证明的是整个域名的控制权,而不是单个文件。
百度会给出一个随机的主机标签,让您在域名下创建,指向百度:
abcdef123456.example.com. 3600 IN CNAME ziyuan.baidu.com.
大多数DNS面板里只需要填左边的标签,因为所在的zone是默认补上的:
| 字段 | 值 |
| --- | --- |
| 类型 | CNAME |
| 名称/主机记录 | abcdef123456 |
| 值/目标 | ziyuan.baidu.com |
| TTL | 3600,测试期间也可以用面板允许的最小值 |
这里反复出错的有四件事:
- 域名被拼了两遍。 在会自动补zone的面板里填
abcdef123456.example.com,结果会变成abcdef123456.example.com.example.com。如果面板在保存后会显示完整域名,请回头读一遍。 - 目标末尾的那个点。 在原始zone文件里,
ziyuan.baidu.com少了最后那个点,就会被当作相对于zone的名称,变成ziyuan.baidu.com.example.com。面板会替您处理,named配置和Terraform文件不会。 - 记录前面挂了代理。 在Cloudflare上,开启代理(橙色云朵)的记录从外部看就不再解析为CNAME了,权威应答会变成Cloudflare的A记录。请把验证用的记录设为仅DNS。
- 等待时间短于TTL。 如果某个解析器已经缓存了这个名称的否定应答,生效的是旧的TTL。请检查外界实际看到的结果,而不是面板上写的。
dig +short abcdef123456.example.com CNAME
# ziyuan.baidu.com.
dig +short @8.8.8.8 abcdef123456.example.com CNAME
请把这条记录一直留着。验证通过后就删掉它,往往会在日后的复查时失败。
配置看起来没问题,验证却过不了
文件curl过了,返回200,百度还是说不行。照着这份清单往下排查:
- 机器人挑战或WAF规则。 Cloudflare Bot Fight Mode、AWS WAF的托管规则,以及大多数受攻击模式的设置,都会对不执行脚本的客户端返回一个带403或503的JavaScript中间页。百度的抓取程序不执行脚本。请把验证路径或爬虫本身加入白名单。
- 地域封锁。 不少欧洲站点为了减少被采集而屏蔽或限速来自中国大陆的流量,而验证抓取正是从这个范围过来的。请确认边缘层没有把它丢掉。
- User-Agent被过滤。 爬虫的标识是
Baiduspider。有些安全产品会把不认识的爬虫标识当成采集器。如果想在放行之前确认请求是真实的,可以对来源IP做反向解析,结果应为*.baidu.com或*.baidu.jp,并且正向解析必须能对上。 robots.txt挡住了。 通配User-Agent下的Disallow: /,或者别人留下的一段User-agent: Baiduspider屏蔽规则,会让抓取根本无从开始。- 浏览器替您掩盖了的TLS问题。 缺失的中间证书会被桌面浏览器悄悄补上,简单的抓取程序不会。请用外部TLS检测工具跑一遍您的域名,而不是相信那把绿色的锁。
- 验证错了origin。 文件在
www.example.com上,百度里的站点填的却是example.com。错误信息不会有任何地方提示您这一点。 - 首字节太慢。 在中国境内没有节点的欧洲服务器,响应来自北京的请求本来就慢,再叠加一次无服务器冷启动,就可能超过抓取的超时时间。换个时段重试确实有用。
该选哪一种
有构建流水线就部署静态文件:它最不容易被改坏,也最好测。如果您日常就在CMS里干活、页面是服务器端渲染的,那就用meta标签。如果完全动不了站点,或者希望在域名层面证明一次所有权之后再也不用回头看,就用CNAME。
不管选哪一种,都请永久保留,并且写进基础设施文档,和其他DNS记录放在一起。十八个月后删掉那个来历不明的文件的人,很可能就是您自己。
这三种方式解决不了的部分
这三种方式都默认您已经登录了百度账号。注册需要通过中国大陆手机号的短信验证,对部分站点,百度还会要求以中国身份证或中国营业执照完成实名认证。一家在法兰克福或曼彻斯特的公司,即使DNS配置得完美无缺,也仍然到不了验证页面。
我们的百度收录提交服务正是为此而设。您把域名交给我们,在您这边完成一次域名归属验证,剩下的提交流程由我们跑完,并向您说明百度实际收录了哪些页面,每个域名固定价格。您不需要中国大陆手机号、中国主体,也不需要自己的百度账号。
如果想先了解全貌,百度收录提交完整指南讲了站点地图、推送API以及百度的排名到底看重什么。如果更愿意直接聊,预约通话即可。