做SEO量出74%的页面对爬虫和用户不一样,补上一组对照之后只剩2.2%
本文目录
- 你说的这个页面变了,有多大可能它一直在变?
- 为什么字节数一样,也不能说明内容一样?
- 三种在字节数上完全隐形的变化
- 抖动到底抖在哪一段字节上?
- 两个极端的样本
- 把一眼就是噪声的串剥掉之后,还剩多少?
- 连抓两次和连抓三次,结论会差多少?
- 一个安全用的随机数,能让抓取预算多花多少?
- 弱ETag为什么没能救场
- 那份爬虫版和用户版不一样的报告,有多少是这40.9%?
- 这组对照该怎么设计
- 怎么给自己的站量一条基线?
- 第一步:选URL,别只测首页
- 第二步:三次请求,而且要拉开间隔
- 第三步:算两枚指纹
- 第四步:不一致的时候,做行级差异,别看百分比
- 第五步:把这个数字写进你所有对比结论的前面
- 哪些抖动该修,哪些不必修
- 常见问题解答
- 页面抖动会直接影响排名吗?
- 40.9%这个数字能不能直接套到我的站上?
- 用无头浏览器渲染后再比对,是不是就能绕过这个问题?
- 为什么剥掉随机串之后还有34.3%在变?
- 只发两次请求真的会漏掉很多吗?
- 抖动和cloaking是一回事吗?
- ETag改成弱验证器能不能解决这个问题?
- 权威参考资料
摘要:我原本想量一件很简单的事:带不带Cookie,服务器给的页面会不会不一样。第一轮跑完,74.4%的站两次响应字节对不上,我差点就把这个数字写进结论。补了一组对照——同样的请求头、同样的会话、什么都不改,再发一次——才发现绝大部分差异跟Cookie毫无关系,是页面自己在抖。230个真实URL连抓三遍,40.9%三次指纹不全同;这些抖动里56.4%三次字节数分毫不差,靠Content-Length根本看不出来;剥掉nonce、请求ID、时间戳这类一眼就是噪声的串之后,仍有34.3%在变。而真正由Cookie造成的分岔,在稳定样本上只有2.2%。这篇讲怎么先把噪声量出来,再谈差异。
做技术SEO的人手里都有那么几件对比工具:抓一份Googlebot看到的HTML,再抓一份浏览器看到的,摆在一起看差多少;或者今天存一份快照,明天再存一份,看页面有没有被人偷偷改过。这类工具的输出通常很干脆——两版不一致,扣分,红灯。
问题是,这套逻辑默认了一件从来没人验证过的事:同一个地址,在什么都不变的情况下,会稳定地返回同一份内容。
这篇文章要做的事就一件:把这个默认前提拿去实测。230个真实站点URL,每个连抓三遍,同一套请求头、同一个出口、间隔不到一秒,然后看三份字节对不对得上。
你说的这个页面变了,有多大可能它一直在变?
先交代口径。样本来自124个首页加106个深层页,共230个URL,全部在三次基线请求里都返回200。请求头完全固定:同一个Chrome桌面UA、同一个Accept-Language: en-US,en;q=0.9、同一个出口IP,三次之间不做任何改动。指纹用响应体原始字节的SHA-1,不做任何清洗。
结果是这样的:
| 样本 | URL数 | 三次指纹不全同 | 占比 |
|---|---|---|---|
| 全部 | 230 | 94 | 40.9% |
| 首页 | 124 | 55 | 44.4% |
| 深层页 | 106 | 39 | 36.8% |
换句话说,三次完全一致的URL只有136个,占59.1%。剩下那四成,你在什么都没改的情况下,就已经拿到了两份或三份不同的HTML。
首页比深层页更爱抖,这个方向符合直觉:首页上挂的推荐位、榜单、促销条、埋点脚本最多。但36.8%这个深层页的数字仍然不低——那些通常是文章页和商品页,也就是大家平时最认真做SEO的那一批。
这个40.9%意味着什么?意味着任何一份“两版不一致”的报告,都自带一个四成的假阳性地板。你的对比工具抓两份HTML发现它们不同,第一嫌疑人不是Googlebot被区别对待了,而是这个页面本来就不会给你两份一样的东西。
为什么这么基础的一个前提,很少有人真的去验证?我猜有两个原因。
一个是这件事在十几年前确实成立。那时候一个PHP页面渲染出来就是那一份HTML,同一个模板同一批数据,字节级稳定是常态,抖动才是异常。这套直觉一直沿用到今天,可今天的页面上挂着三四个埋点脚本、两套实验框架、一个推荐引擎,中间还隔着CDN——稳定从常态变成了少数派,直觉却没跟着更新。
另一个原因更朴素:验证这件事需要多发一次请求,而多发的那一次不产出任何新信息。你抓两份就能出一张对比图,抓三份只能得到一句“这个页面本身就在抖”,看起来像白干。做数据的人都懂对照组的价值,可轮到自己写巡检脚本的时候,那一步几乎总是第一个被省掉的。
为什么字节数一样,也不能说明内容一样?
很多自建的巡检脚本图省事,用Content-Length或者curl -w '%{size_download}'来判断“页面有没有变”。这个办法有一个非常大的漏洞。
在那94个抖动的URL里,53个三次的字节数完全相同,占56.4%。它们的SHA-1三次全不一样,字节数却一个数都没差。
为什么会这样?我在实验台上造了一个最小的例子。一台干净的nginx后面挂一个PHP页面,页面里输出六个列表项,唯一的处理是每次响应前把顺序打乱:
| 指纹 | 字节 | 列表顺序 |
|---|---|---|
| 3693324aec | 282 | foxtrot alpha delta bravo echo charlie |
| aa67125512 | 282 | foxtrot bravo alpha echo delta charlie |
| 0be277628d | 282 | charlie foxtrot delta bravo alpha echo |
| 2feee55ee9 | 282 | bravo delta alpha echo charlie foxtrot |
| e5f9e616c0 | 282 | charlie delta alpha foxtrot bravo echo |
| 43d62c19f4 | 282 | charlie delta foxtrot bravo alpha echo |
| bf22c0b7da | 282 | alpha echo charlie delta bravo foxtrot |
| 077ccbb24c | 282 | echo alpha foxtrot charlie bravo delta |
八次请求,八个不同的指纹,八个一模一样的282。凡是“重排”而不是“增删”的变化,在字节数这个维度上是完全隐形的。
现实里这类变化多得很:商品列表按个性化权重重排、推荐位轮播、相关文章随机取N条、多个CDN域名轮询、A/B分桶决定组件顺序。它们改变的是“哪个在前面”,不改变“一共有多少”。
顺带说个数:这94个抖动URL里,三次字节差的中位数是0,p90是536字节,最大的一个是zapier.com首页的42604字节。中位数为0这件事本身就是结论——抖动的典型形态不是页面重写了一半,而是几个字符悄悄换了个位置。
三种在字节数上完全隐形的变化
把这类变化分个类,排查的时候能快很多。
第一种是重排。上面那个实验就是。列表顺序、推荐位顺序、CDN域名轮询、多个脚本标签的先后,全都属于这一类。它们的共同点是元素集合没变,只是走了个位置。
第二种是等长替换。一个32位的请求ID换成另一个32位的请求ID、一个UUID换成另一个UUID、一个毫秒时间戳换成下一个毫秒时间戳——长度天然固定,内容每次都新。这类是所有抖动里最普遍的。
第三种最阴,是等长的内容替换。比如价格从¥129变成¥139、库存从12件变成13件、评分从4.6变成4.7。这些是真实的内容变化,会影响结构化数据,也会影响搜索结果里展示的富媒体信息,可它们在字节数上一动不动。
第三种正是为什么“只看size就行了”这个想法特别危险:它对噪声不敏感,同时对真正该报警的那类变化也不敏感。一个既报不出假警、也报不出真警的指标,其实就是没有指标。
抖动到底抖在哪一段字节上?
知道“在抖”还不够,得知道“抖在哪”。我把那94个URL全部重抓两份完整HTML,逐行做差异比对,再给每一段差异归类。
第一个意外的发现:重抓时只有86个还在抖,另外8个这次三次一致了。也就是说抖动本身也是间歇的——一个页面今天没抖,不等于它稳定,只等于这次没赶上。
在这86个里:
- 变化行数中位数4行,p90是20行,最多98行;
- 变化行占全文行数的比例,中位数0.743%,p90是25.2%;
- 变化块数中位2块;
- 第一处差异落在
</head>之前的,占55.8%。
最后那条最要命。超过一半的抖动,第一处就发生在head里——而head正是title、meta description、canonical、hreflang、结构化数据的地盘。你的差异报告有一半概率会把手指头点在SEO最敏感的那一段上。
把差异段落做关键词归类之后,成因分布是这样:
| 抖动来源 | 命中URL数 | 占比 |
|---|---|---|
| 广告与追踪脚本 | 51 | 59.3% |
| 实验与A/B分桶 | 46 | 53.5% |
| 推荐与排序 | 43 | 50.0% |
| 时间戳与日期 | 37 | 43.0% |
| 库存、价格与计数 | 31 | 36.0% |
| 构建ID与资源版本号 | 25 | 29.1% |
| 会话ID | 19 | 22.1% |
| CSRF令牌 | 19 | 22.1% |
| 请求ID与链路追踪 | 19 | 22.1% |
| CSP的nonce | 13 | 15.1% |
| 一类都没命中 | 13 | 15.1% |
一个URL可以同时命中好几类,所以合计超过100%。这张表里最值得盯的是最后一行:有15.1%的抖动,用这十条规则一条都识别不出来。那些是页面自己业务逻辑里的东西——库存数字、评论条数、“3人正在看”这类实时片段,没有任何通用特征可以把它们摘出去。
几条真实的差异行,能让人对这件事有个具体的感觉:
zfc.push(['initialize', '1785433803088', (new Date).getTime(), ...(zappos.com,两次请求相差不到一秒,这个毫秒级时间戳就换了一个数)
'__TGT_DATA__': { configurable: false, enumerable: true, value: deepFreeze(JSON.parse("{ \"formFactor\": \"desktop\",\"assetPrefix...(target.com,整个页面状态被序列化进一个巨大的JSON,里面任何一个字段变了,这一行就变了)
最干净的几个也值得记一下:salesforce.com和mailchimp.com每次只变2行、占比0.019%,moz.com只变2行且十条规则一条都不命中。它们不是不抖,只是抖得极小——小到任何一个按“相似度百分比”打分的工具都会判它们通过。
两个极端的样本
抖动幅度最大的是zapier.com首页,三次分别是577728、535124、535124字节,头一次比后两次多出42604字节。这个量级不是埋点脚本能解释的,更像是首屏内容整块换了一版——可能是实验分桶,也可能是缓存里恰好躺着一份不同的副本。
另一头是paypal.com的深层页,变化行数92行,占全文的27.9%,同时命中了nonce、CSRF令牌、请求ID、会话ID、时间戳五类。金融类站点在安全上下的功夫,全都以抖动的形式写进了HTML。这没什么可指摘的,只是意味着对这类站做内容对比,噪声地板高得离谱。
把这两个极端和中位数的0字节差放在一起看,能得出一个对排查有用的判断:抖动的分布是长尾的,绝大多数是几行的小变动,极少数是整块内容的替换。所以看“变化行数”这个绝对量,比看“相似度百分比”这个相对量更有信息量——后者在一个几千行的页面上,永远是99.9%。
把一眼就是噪声的串剥掉之后,还剩多少?
到这里很自然会想到一个办法:既然大部分抖动是nonce、请求ID、时间戳这种明显的随机串,那就在算指纹之前先把它们统一替换掉,剩下的差异才算数。
这个思路是对的,我也这么做了。做法是在算第二枚指纹之前,用一组正则把UUID、32到64位的十六进制串、10到13位的纯数字、nonce="..."、各类token赋值、ISO 8601时间串,全部替换成固定的占位符,然后对处理后的文本再算一次SHA-1。于是每个响应有两枚指纹:原始指纹和归一化指纹。
结果比我预期的糟:
| 口径 | 三次不全同 | 占比 |
|---|---|---|
| 原始字节指纹 | 94 | 40.9% |
| 归一化指纹(剥掉高熵串) | 79 | 34.3% |
也就是说,40.9个百分点的抖动里,只有6.5个百分点是纯token噪声,剩下34.3个百分点在剥掉所有随机串之后依然在变。
这个结果推翻了我动手前的假设。我原本以为抖动主要是安全和链路追踪那套东西造成的,清洗一遍就该干净了。实际上不是——大头是内容本身在变:推荐位换了一批、榜单重排了、库存数字动了、A/B分桶给了另一个变体。这些东西你没法用正则摘掉,因为它们长得就跟正常内容一样。
保哥的看法是,这条其实是好消息里裹着坏消息。好消息是“清洗一下就能对比”这个念头可以早点放弃,省得在正则上耗一周;坏消息是只要页面上有一块内容是活的,这个页面就没有一个稳定的“正确版本”可以拿来当基准。
连抓两次和连抓三次,结论会差多少?
几乎所有对比脚本都是抓两份。我这次特意抓了三份,中间还留了一点间隔,就是想看看第三份能多告诉我什么。
第一个数字:头两次指纹一样、第三次才变的URL有5个,占2.2%。比例不高,但方向很明确——抓两次判“稳定”,会有一小撮漏网。
第二个数字更值得注意,就是上一节说的8.5%重抓时不抖了。合起来看,这两个数字说的是同一件事:“这个页面稳不稳定”本身就是一个概率问题,不是一个是非题。你测一次得到的是一个样本,不是一个结论。
第三个数字来自实验台,是我做这批实验时最喜欢的一个小机关。我造了一个只注入时间戳的页面,然后连着请求:
| 时机 | 指纹 | 是否与第一次相同 |
|---|---|---|
| 第一次 | 6d181df64ebb | — |
| 紧接着第二次 | 6d181df64ebb | 相同 |
| 隔2秒第三次 | 75295c2096bc | 不同 |
因为这个页面注入的是秒级时间戳,在同一秒里连发两次,它看起来完美稳定。而现实里43.0%的抖动跟时间戳有关,这意味着你的采样间隔本身就是一个变量:抓得越快,页面看起来越稳。
这大概是整件事里最容易让人栽跟头的地方。为了“控制变量”,大家往往把两次请求发得越紧越好,结果恰恰是把一整类抖动屏蔽掉了。想抓时间戳类抖动,你得故意等一会儿。
一个安全用的随机数,能让抓取预算多花多少?
前面讲的都是“你怎么被误导”,这一节讲抖动实打实的代价,而且这笔代价直接记在SEO账上。
Google的爬虫支持条件请求。它会把上次抓取拿到的ETag放进If-None-Match发过来,服务端如果发现内容没变,回一个304、零字节的响应体就完事了。这套机制能省下的是真金白银的抓取预算,具体机制我在条件请求与304怎么省抓取预算里拆过一遍。
现在问题来了:如果页面每次响应的字节都不一样,应用层算出来的强ETag也就每次都不一样,条件请求会发生什么?
我在实验台上做了个标准的框架式实现——对最终HTML算一次MD5当ETag,收到If-None-Match就比对,一致返304。然后拿同一个页面的几个变体各跑100次条件请求:
| 页面里注入的东西 | 100次条件请求的304命中 | 累计回传字节 |
|---|---|---|
| 什么都不注入 | 100 / 100 | 0 |
| 一个CSP用的nonce | 0 / 100 | 26200 |
| 列表顺序随机 | 0 / 100 | 21200 |
一个12字节的随机数,把条件请求的收益从100%清零到0%。不是打了折,是整个机制彻底失效。
让这件事变得棘手的是:那个nonce并不是谁写错了,它是按规范做对了的结果。MDN在讲CSP的script-src时写得很直白:
"It is important to note, this nonce value needs to be dynamically generated as it has to be unique for each HTTP request"
规范要求每次请求都换一个新的nonce,否则这道防线就没意义了。于是你面对的是一个真实的取舍:要么这个页面的内联脚本有CSP保护,要么这个页面能享受条件请求的抓取预算折扣,两个不能同时要——除非你把ETag的算法改成只对“内容部分”取哈希,而不是对整份HTML。
现网的情况怎么样?在230个URL里:
- 带
ETag响应头的81个,占35.2%;带Last-Modified的66个,占28.7%; - 带ETag的那批里,弱ETag(
W/开头)占46.9%; - 三次拿到的ETag值本身就各不相同的有15个,占带ETag的18.5%——这些页面上的条件请求,必然一次都命中不了。
把这三个数连起来读:三分之一的页面根本没给爬虫任何验证器;给了的里面又有接近五分之一,给的是一个每次都变的值。后者比前者更浪费——不发ETag只是没省下,发一个每次都变的ETag,是让爬虫每次多发一个请求头、还是全量重传。
弱ETag为什么没能救场
看到46.9%是弱ETag的时候,我一度以为这是个好消息。弱验证器(W/前缀)在规范里表达的是“语义等价”而不是“字节完全相同”,理论上它就是为这种场景设计的:埋点时间戳变了,但这个页面对读者来说还是同一个页面,那就该返回同一个弱ETag。
实际上没有。这批带弱ETag的页面里,仍有相当比例三次拿到的值各不相同。原因不难猜——绝大多数框架和中间件的“弱ETag”实现,只是在原来的哈希前面加了两个字符W/,哈希本身照旧对整份响应体算。前缀改了,语义没改。
要真正用好弱验证器,应用得自己回答一个问题:“这次响应和上次比,算不算实质变化?”这个判断没法通用化,只能由业务来做:把内容主体单独取哈希、把埋点和令牌排除在外,或者干脆用内容表里的modified字段生成验证器。后一条路最省事,也顺带解决了更新时间戳该怎么诚实地维护那个老问题。
那份爬虫版和用户版不一样的报告,有多少是这40.9%?
现在回到开头那件事。我一开始想量的是Cookie:站点在首访就下发Cookie,爬虫从不回传,那么“带Cookie”和“不带Cookie”拿到的页面差多少?
第一轮我用最直接的办法测:建一个会话,请求一次,把服务器下发的Cookie收下,隔一秒用同一个会话再请求一次,比对两次的字节。40个站里39个可用,29个两次响应不同,占74.4%。
这个数字很漂亮,也很有故事性——“四分之三的站给爬虫和用户的不是同一个页面”。我当时确实准备把它写进结论。
补对照组花了不到二十分钟:同样的URL,再开两个全新的、不带任何Cookie的会话各请求一次。如果这两次之间也不同,那第一轮那个74.4%就跟Cookie没关系。
对照组的结果是68.2%。
换句话说,我原以为测到的是Cookie效应,实际上测到的绝大部分是页面自己在抖。等到用230个URL重做一遍、并且只在三次基线完全一致的136个稳定URL上计算之后,真实的数字是这样:
| 改变的东西 | 稳定样本数 | 响应不同 | 占比 |
|---|---|---|---|
| 第二次请求带上服务器下发的Cookie | 136 | 3 | 2.2% |
| Accept-Language换成zh-CN | 136 | 15 | 11.0% |
| Accept-Language换成de-DE | 136 | 14 | 10.3% |
| 完全不发Accept-Language | 136 | 7 | 5.1% |
| 换成Googlebot的User-Agent | 124 | 14 | 11.3% |
74.4%和2.2%,差了33倍。中间隔的就是一组花二十分钟补上的对照。
这组对照该怎么设计
把这次的教训抽象一下,对照组的设计其实只有一条规则:让对照组和实验组之间,只差你想测的那一个变量。听起来像废话,可具体到HTTP请求上,“只差一个变量”比想象中难做到。
我第一轮的实验组是“同一个会话的第二次请求”,对照组本该是“另一个新会话的第一次请求”。但这两者之间差的不只是Cookie——还差了时间(第二次晚了一秒)、差了连接复用状态、差了服务端可能已经把这个页面预热进缓存。任何一个都足以造成字节差异。
所以正确的做法是把对照组也做成一对:两个全新会话、都不带Cookie、间隔和实验组一样。这一对之间的差异率就是噪声地板,实验组的差异率减去它,才是Cookie的净效应。这跟SEO实验设计里的单因素隔离是同一套东西,只不过大家做A/B测试时都记得,做技术巡检时都忘了。
还有一个更省事的版本,也是我最后采用的:先把所有基线不稳定的URL整个剔除,只在稳定样本上做实验。这样噪声地板直接归零,代价是样本量从230掉到136。样本够的时候这个办法最干净。
这件事对市面上的对比类工具意味着什么,我觉得得说清楚,因为它不是“工具不行”这么简单。渲染对比器这类工具做的事——抓Googlebot版和浏览器版,逐维度比对,按差异扣分——方法本身没有问题,它查出来的cloaking也确实是真的。问题出在它默认每一维只需要各取一个样本。在一个40.9%的页面会自己跟自己对不上的世界里,单样本对比的结论,误差比信号大。
同样的道理也适用于任何一个“快照告警”系统:SEO监控告警体系里最常见的一条规则就是“页面内容变化超过X%就报警”,而这个X如果是拍脑袋定的,你要么天天收到广告脚本换了个时间戳的告警,要么把阈值调到高得连真事故都漏掉。这跟慢查询日志里783MB只有32条是真问题是同一类毛病:不先量噪声,就定不出阈值。
顺便说一句,这个坑不只在HTTP层。AI可见度排名同一个问题查多次名次都不一样、关键词排名天天上下跳,讲的都是同一件事的不同层:先确认你要测的量有没有一个稳定的值,再谈它变了多少。
怎么给自己的站量一条基线?
方法不复杂,成本也低。保哥把它压成了五步,跑一次十分钟,结论能用很久。
第一步:选URL,别只测首页
至少三类各取几个:首页、一个典型的列表或分类页、一个典型的详情页(文章或商品)。我这批数据里首页44.4%、深层页36.8%,两者差了将近8个百分点,只测首页会高估自己的问题。
第二步:三次请求,而且要拉开间隔
同一套请求头连发两次,再隔至少3到5秒发第三次。前两次抓的是“每次都变”的那类(nonce、请求ID),第三次抓的是“每秒都变”的那类(时间戳、缓存刷新)。只发两次、且发得很紧,会同时漏掉后一类。
第三步:算两枚指纹
一枚是原始字节的SHA-1,一枚是清洗后的。清洗的正则不用写得多讲究,覆盖这几类就够了:UUID格式、连续32位以上的十六进制、10到13位纯数字、nonce=后面的引号串、token/csrf/requestId这类赋值、ISO 8601时间串。哈希指纹的算法选择在这里不重要,SHA-1够用,你只是在比对,不是在防篡改。
第四步:不一致的时候,做行级差异,别看百分比
两枚指纹只告诉你“变没变”,逐行逐词的差异比对才告诉你“变在哪”。重点看第一处差异落在</head>之前还是之后——落在之前,说明抖动碰到了SEO标签所在的区域,值得深挖;落在之后而且只有两三行,大概率是个埋点脚本。
第五步:把这个数字写进你所有对比结论的前面
这一步没有技术含量,但它是全部五步里最有价值的一步。以后任何一份差异报告,抬头先写一句“本站基线抖动率X%,本次差异Y%”。Y要显著大于X,这份报告才配叫报告。
我拿自己的站跑了一遍这套流程,结果是这样:
| URL | 三次指纹 | 是否一致 | ETag | Set-Cookie | Vary |
|---|---|---|---|---|---|
| 首页 | b9711033b7b9 ×3 | 一致 | 无 | 无 | Accept-Encoding |
| 术语表页 | 71a2f1a07840 ×3 | 一致 | 无 | 无 | Accept-Encoding |
| 工具站页 | ac92ffddc219 ×3 | 一致 | 无 | 无 | Accept-Encoding |
三个页面各三次全部一致,换Accept-Language为zh-CN、de-DE,以及换成Googlebot的UA,拿到的仍然是同一枚指纹。这不是我优化出来的,是这个站根本没有广告脚本、没有推荐位、没有A/B分桶的自然结果。一个不做个性化的站在这件事上是天然干净的,代价是它也没有那些东西带来的收益——这笔账每个站自己算。
顺带一提,这三个页面都没有ETag。按上一节的账,这是个可以捡的便宜:内容稳定却不发验证器,等于把条件请求这项白送的优惠券扔了。这条已经进了保哥的待办。
哪些抖动该修,哪些不必修
把抖动全部消灭既不可能也不划算。真正要判断的是:这段抖动有没有落在会被搜索引擎当成“内容”的字节上。按这个标准分三档:
| 档位 | 典型来源 | 处理建议 |
|---|---|---|
| 该修 | head里的title、description、canonical、hreflang、结构化数据随请求变化 | 必须查。这类抖动会让规范网址判定和富媒体结果不稳定,属于真事故 |
| 该修 | 正文主体内容按分桶给出不同版本,且没有任何标注 | 按爬虫与用户看到的页面对比那套口径自查,接近cloaking的红线 |
| 值得改 | ETag按整份HTML算,被nonce、请求ID带得每次都变 | 改成只对内容部分取哈希,或者干脆改用Last-Modified,把条件请求的收益捡回来 |
| 值得改 | 推荐位、相关文章每次刷新都换一批 | 加个短TTL让它在几分钟内稳定,对用户体验没损失,对缓存和验证器友好得多 |
| 不必修 | CSP的nonce | 规范要求它每次都变。别为了指纹稳定去牺牲安全,去改ETag的算法 |
| 不必修 | 埋点脚本里的毫秒时间戳、请求ID | 它们不进索引,也不影响内容判定,记住它们的存在、在对比时剥掉就行 |
| 不必修 | 库存数字、评论条数、“N人正在看” | 这就是内容本身在变,是对的。只要别让它出现在title里 |
最后回到这篇文章的起点。做技术SEO这些年,保哥见过太多的排查是从一份“两版不一致”的截图开始的,然后一路查到CDN、查到WAF、查到UA判别,最后发现两版差的是一个埋点脚本里的时间戳。同一个页面有11种URL写法都返回200那篇里我数过一个页面有几个地址,这篇数的是同一个地址下有几份内容。两个问题的答案都是同一句话:你以为的“一个”,实际上从来都是“一批”。
差别只在于,地址那件事你至少能一条条列出来,内容这件事你连“正确版本”是哪一份都指不出来。所以顺序只能是先量噪声、再谈差异——反过来做,你得到的每一个结论都建立在一次运气上。
常见问题解答
页面抖动会直接影响排名吗?
抖动本身不是排名因素,Google没有任何一条规则是针对“响应字节不稳定”的。它的影响是间接的,走两条路:一是让强ETag每次都变,条件请求全部落空,白白多花抓取预算;二是如果抖动落在head里,title、canonical、结构化数据在不同抓取批次拿到不同的值,规范网址判定和富媒体结果就会不稳定。正文里几个数字或者推荐位换一批,基本没有影响。
40.9%这个数字能不能直接套到我的站上?
不能,这是一个参考量级不是一个基准值。样本是230个电商、SaaS和开发者站的URL,这类站上广告脚本、A/B分桶、个性化推荐特别密集。一个纯内容站的抖动率通常低得多——我自己这个站三类页面各测三次全部一致。这个数字的用法是提醒你自己去测一遍,而不是拿来当自己的数。
用无头浏览器渲染后再比对,是不是就能绕过这个问题?
不能,反而更糟。渲染会把JavaScript执行的结果也算进来,广告位、推荐接口、实验分桶的返回全都进入DOM,抖动幅度只会更大。渲染后对比适合查“内容有没有被JS填进来”,不适合查“两版一不一样”。要查一致性,用原始HTML加两级指纹更可控。
为什么剥掉随机串之后还有34.3%在变?
因为大头不是随机串,是内容本身。推荐位换了一批商品、榜单重新排了序、库存数字动了、A/B实验给了另一个变体,这些东西长得跟正常内容一模一样,任何正则都摘不出来。这也是为什么“清洗一遍再对比”这条路走不通——真正在变的那部分,恰恰是你想拿来对比的那部分。
只发两次请求真的会漏掉很多吗?
漏掉的比例不算大但方向很明确。头两次一样、第三次才不同的占2.2%;另有8.5%的URL重抓时干脆不抖了。更关键的是间隔:秒级时间戳类的抖动在同一秒内连发两次完全看不出来,而这类抖动占了43.0%。所以真正的建议不是“多发一次”,是“第三次要故意等几秒”。
抖动和cloaking是一回事吗?
不是,而且必须分清楚。cloaking是有意针对爬虫身份返回不同内容,是违反Google垃圾内容政策的作弊行为。抖动是页面对所有人都不稳定,跟你是谁没关系。判别方法很简单:先测三次基线,如果同一个身份连测三次就已经不一致,那么“爬虫版和用户版不同”这个观察不构成任何证据;只有在基线稳定的页面上,身份造成的差异才是真的。
ETag改成弱验证器能不能解决这个问题?
能解决一部分。弱ETag(W/开头)表达的是“语义等价”而不是“字节相同”,理论上允许你在只有埋点时间戳变了的情况下仍然返回同一个值。但这要求应用自己有能力判断“这次的变化算不算实质变化”,多数框架的实现只是简单加个W/前缀、哈希照旧算全文,那就没有任何区别。实测230个URL里带ETag的有46.9%是弱ETag,但其中仍有相当比例三次值各不相同。
权威参考资料
本文标题:《做SEO量出74%的页面对爬虫和用户不一样,补上一组对照之后只剩2.2%》
本文链接:https://zhangwenbao.com/page-content-jitter-baseline-seo-diff-false-positive.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0