404页面有多贵?114个大站说一句没有就要66KB

404页面有多贵?114个大站说一句没有就要66KB
张文保 更新 24 分钟阅读 1,720 阅读
本文目录
  1. 一个不存在的地址,站点要花多少字节来回答?
  2. 先证明这把尺子本身是准的
  3. 状态码这一关,是不是其实大家都过了?
  4. 那几百KB里装的到底是什么?
  5. 八个站,说“没有”比说“有”还贵
  6. 响应快不快
  7. 压缩之后真正传出去的是多少?
  8. 同一个站,为什么 .css的“没有”比网页的“没有”便宜两百倍?
  9. 还有一件更别扭的事:类型也对不上
  10. 说“有”的那十一个站,麻烦在哪?
  11. 它们的noindex和canonical都写了什么
  12. 平台默认值替你做完了这道选择题
  13. 缓存头也是默认值的产物
  14. 把问的节奏放慢,答案会变吗?
  15. 三个数先量出来,再谈改
  16. 错误页该长什么样
  17. 删了商品之后到底该回什么
  18. 把它挂进哪个流程
  19. 常见问题解答
  20. 返回404的页面还需要加noindex吗?
  21. 错误页几百KB,真的会影响SEO吗?
  22. 怎么判断我的站是不是软404?
  23. 单页应用没法在服务端判断地址存不存在,怎么办?
  24. 不存在的图片和CSS返回整张HTML,除了浪费流量还有别的害处吗?
  25. 把错误页缓存起来会不会导致真实页面上线后还显示404?
  26. 410用起来有什么风险?
  27. 权威参考资料
摘要:对131个国际电商站各要了14个地址,其中11个是编出来的、这世上根本不存在。首页能进的114个站里,103个老老实实回了404或410,只有11个嘴上说“有”。状态码这一关几乎人人及格,可代价藏在别处:一句“没有”,压缩后中位数还要传66.1KB,解压开是363KB,最大的那个站解压后5.73MB。这个数字是首页的0.83倍——服务器说“我没有这个东西”,花掉的带宽跟它把整个首页端出来差不多。

事情的起因很土。上个月帮一个做宠物用品的独立站看抓取日志,发现Googlebot有相当一部分请求打在一批早就删掉的商品地址上。这不稀奇,改版之后总有这种尾巴。稀奇的是把那些请求的响应大小拉出来一看,每一条都在两百多KB。

一个已经不存在的页面,凭什么还要传两百多KB?

于是就有了这次普查。131个国际电商站,每个站要14个地址:两个是真实存在的(首页和robots.txt,当对照组),十一个是随手编出来的、这世上不可能存在的路径,还有一个是把同一个不存在的地址再要一遍。想知道的事情很简单——当你朝一个站要一件它没有的东西,它是怎么回答你的,以及这句回答要花多少钱。

一个不存在的地址,站点要花多少字节来回答?

先说结论,再说怎么来的。首页能正常打开的有114个站,其中103个对不存在的地址给出了404或410。这103个站的错误页,压缩之后传输量中位数67722字节,也就是66.1KB;解压开是371962字节。

66KB听起来还行?那就换个参照物。同一批站的首页,用同样的方式量一遍,错误页跟首页的传输量之比中位数是0.83。有12个站的错误页比自己的首页还大。

口径中位数最大超过100KB的站
压缩后(真正走网线的字节)66.1KB521KB(rab.equipment)30个
解压后(浏览器要解析的字节)363KB5.73MB(misen.com)88个

解压后超过1MB的有16个站,超过512KB的有38个。排在最前面的那几个,misen.com 5729744字节、burrow.com 4649467字节、dreametech.com 3308733字节、eufy.com 2604079字节、casetify.com 2587949字节。这些数字后面跟着的都是知名品牌,不是什么野路子小站。

顺便说一句,2.6MB这个量级已经越过了Googlebot抓取体积的常见讨论线。一个用来告诉爬虫“这里什么都没有”的页面,体积够得上一次正经的正文抓取。

先证明这把尺子本身是准的

在往下走之前得先过一关。任何“A和B不一样”的结论,前提都是“A和A自己一样”。所以每个站上都多要了一次同一个不存在的地址,看两次回来的字节对不对得上。

结果:114个站里,两次字节完全相同的40个,相差不到2%的74个,一个不稳的都没有,抖动的中位数是0字节、最大216字节。另外还编了第二个完全不同的不存在路径,两个路径的状态码114比114全部一致。

更狠的一层对照是跨轮的。这次普查前后跑了两轮,中间隔了将近一个小时。两轮都返回404的65个站,字节相对差的中位数是0.0000,差得最多的philips.com也只有0.9%。同一个站隔一小时问两次,它说“没有”的方式一个字节都不带变的。

这一步不能省。之前量爬虫和用户看到的页面差异那次就吃过亏——不补对照组,量出来的74%差异有七十多个百分点是页面自己的抖动。这回先把零点钉死,后面所有的差异才站得住。

状态码这一关,是不是其实大家都过了?

是,而且过得比预想的漂亮。114个可判定的站里:

  • 返回404的102个
  • 返回410的1个(zwilling.com,唯一一个用410 Gone的)
  • 返回200的11个

诚实率90.4%。这跟很多人的印象不太一样——业内讲软404讲了十几年,好像遍地都是。至少在这批体量的电商站上,状态码这道题基本都做对了。

更有意思的是稳定性。六种不同形态的不存在路径都试了:根目录下的、带尾斜杠的、塞进/products/里的、塞进/collections/里的、四层深的、带.html后缀的。六种形态返回404的站数分别是102、101、103、101、103、102,上下浮动不超过2个站。站点对“不存在”的判断跟路径长什么样几乎没关系,这说明这个逻辑写在很靠前的地方,多半是平台或框架的默认行为,不是谁一条条配出来的。

这跟同一个页面十一种写法都返回200那次量到的结论是一枚硬币的两面:路径归一化跑在你写的所有规则之前,存在的东西被它放宽,不存在的东西也被它统一挡住。

所以真正值得聊的不是“有没有回404”,而是回这个404的时候顺手做了些什么。

那几百KB里装的到底是什么?

把错误页的HTML拆开数了数:103个站的404页面上,<a>标签数量中位数97个、最多1703个;<script>标签中位数39个、最多230个。

这就说清楚了。那几百KB不是错误信息,是整张站点外壳:头部导航、巨型下拉菜单、页脚的全部链接、语言切换、货币切换、Cookie横幅、客服挂件、埋点脚本、A/B测试脚本、推荐引擎、评价挂件。用户走错门,站点把整个商场的平面图连同全部导购一起塞给他。

体感上这挺贴心。可对爬虫来说,它拿到的是一份跟另外几千个页面高度雷同的模板,正文区域写着“页面不存在”。

八个站,说“没有”比说“有”还贵

按解压后的字节算,错误页跟首页之比超过1的有8个站;按压缩后的传输量算是12个。最极端的几个:

站点404页字节首页字节比值
sostrenegrene.com72263383141.89
dji.com3170791682171.88
casetify.com258794914640591.77
montbell.com139927884451.58

怎么会有这种事?首页通常是全站优化最狠的一个页面,图片懒加载、脚本延迟、首屏内联,能砍的都砍过。而404页面没人管,它继承的是那套没被优化过的原始模板,该加载的一样不少,该延迟的一个没延迟。优化过的页面和没人管的页面放在同一个站上,差距就是这么来的。

响应快不快

耗时这一栏反倒还行:404的响应时间中位数509毫秒,首页是815毫秒。最慢的几个是segway.com 2940毫秒、braun.com 2478毫秒、assos.com 2326毫秒。

比首页快是意料之中的,毕竟不用查数据库。但两三秒的错误页仍然值得警觉,那说明这个页面在渲染时还在跑一整套业务逻辑。慢查询和抓取预算那笔账在这里同样成立,只是账单开在了一个不存在的地址上。

压缩之后真正传出去的是多少?

这一节是自己给自己纠的偏,写出来是因为踩这个坑的不会只有保哥一个。

第一轮跑完,手上的数字是“错误页平均621745字节”。这个数听着骇人,六十多万字节。差点就这么写出去了。

问题在于,HTTP客户端默认会替你把响应解压,你拿到的len(body)是解压之后的长度。而抓取预算、带宽账单、CDN流量费,算的全是压缩之后真正走网线的那些字节。这两个口径在这批站上差了5.5倍。

所以又跑了一轮,这次不解压,直接数原始字节流:

  • 压缩后中位数67722字节,解压后中位数371962字节,压缩比中位数0.18
  • 用Brotli的75个站,用gzip的21个,用zstd的1个,一点不压的5个站
  • 压缩后超过100KB的30个站,超过500KB的只剩1个(rab.equipment,521KB)

纠偏之后结论没有反转,只是变得能站住脚了:66KB依然是一个不小的数,尤其考虑到它对应的信息量是“没有”这两个字。但5.73MB那种说法只能用在“浏览器要解析多少”这个语境里,不能拿去算带宽。

那5个完全不压缩的站要单独说一句。压缩类型的配置盲区在别处也常见,nginx出厂只压HTML这一种类型,其余的全额计费。放在错误页上,不压缩意味着它的传输量直接等于解压后的体积。

同一个站,为什么 .css的“没有”比网页的“没有”便宜两百倍?

这是这次普查里最硬的一组对照,因为它发生在同一个站、同一次会话、同一个CDN后面,唯一的差别只是要的东西后缀不一样。

同样是不存在的地址,请求/xxx和请求/xxx.css,中位数分别是:

请求形态字节中位数耗时中位数
网页路径(/xxx、/products/xxx等六种)371983 - 385587271 - 342毫秒
不存在的 .css1970256毫秒
不存在的 .js1708241毫秒
不存在的 .jpg338402468毫秒

中位数上 .css只要1970字节,跟网页路径差了将近两百倍。但平均数是280157字节——这个巨大的落差说明样本裂成了两半。逐站看确实如此:52个站的资源形态明显更省,51个站两者一样大,一个反过来的都没有。

省下来的那一半是怎么做到的?服务器在路由的最外层就按后缀判断了:这个请求要的是静态文件,静态文件目录里没有,直接吐一个几十字节的空响应,连应用框架都不惊动。brompton.com是个漂亮例子,不存在的 .css回21字节、.js回22字节、.jpg回一个78字节的透明GIF,而同一个站不存在的网页路径回215528字节。

另一半没这么做的站,请求走进了框架的兜底路由,然后框架尽职尽责地渲染了整张错误页——哪怕请求头上明明白白写着我要的是一个样式表。省得最多的几个站落差惊人(按.css、.js、.jpg三种形态的平均算):misen.com从5729744字节掉到1909828,ouraring.com从1226885掉到26743。后者才是这条路径该有的样子。

还有一件更别扭的事:类型也对不上

顺手统计了返回的Content-Type:

  • 请求 .css,回text/html的54个站,占52.4%
  • 请求 .js,回text/html的54个站,占52.4%
  • 请求 .jpg,text/html的98个站,占97.0%

图片这一项几乎全军覆没。101个站里只有一个(brompton.com)老实回了image/gif。也就是说,浏览器去要一张图,服务器回了一份几百KB的网页,还盖着“这是网页”的章。浏览器当然不会把它当图片渲染,最终效果是一个碎图图标,代价是几百KB的传输和一次完整的服务端渲染。

这个坑在nginx配置的静默副作用里属于同一族:配置本身不报错,reload每次都通过,只有在“请求一个不存在的东西”这条冷门路径上才现原形。爬虫报的死链和Googlebot实际抓的从来不是同一批,一部分原因也在这儿。

说“有”的那十一个站,麻烦在哪?

11个站对不存在的地址返回了200。名单:aliexpress.com、govee.com、iittala.com、innisfree.com、kotn.com、narwal.com、on.com、prose.com、shein.com、temu.com、zalando.com。

两个最大的跨境平台都在里面,这一点挺说明问题的。

它们的表现分三类。第一类是跳走:aliexpress.com跳了三次最后落到一个404页面上、govee.com和iittala.com各跳一次回首页。跳到首页这种做法,Google官方文档里点名说过,会被判成软404,因为地址变了、状态码却是200,搜索引擎只能自己猜。

第二类是原地返回一个壳:innisfree.com回了1802字节的空HTML,temu.com 2910字节,on.com 4348字节,narwal.com 4973字节。这些都是单页应用,路由在浏览器里,服务端根本不知道这个地址存不存在,先把壳发给你再说。前端框架站的渲染模式选择在这里直接决定了状态码能不能给对。

第三类最贵:govee.com回了3273316字节、shein.com回了1133975字节,都是200。一个不存在的地址,换来3.2MB的200响应。

它们的noindex和canonical都写了什么

这11个站里,页面上带noindex的只有kotn.com一个。canonical方面,govee.com指向https://us.govee.com、shein.com指向https://sg.shein.com/、prose.com指向/,其余的干脆没有canonical。

值得说明的是,没有一个站的canonical指回这个不存在的地址自己。所以这批200不至于变成无限的索引空间——canonical至少把它们归并到了首页那一边。但canonical是提示不是命令,Google会自己选规范页;人机验证屏被当成正文索引那个案子就是这么翻车的。真正该做的是把状态码给对,而不是指望后面几道闸兜住。

对照一下正常返回404的那103个站:带noindex的21个,带canonical的72个,两样都没有的21个。404页面本身不需要noindex,状态码已经把话说完了,这21个属于多做了一步也不亏。反过来,接口和feed这类没有head标签的地址就只能靠响应头说话,状态码在那里更是唯一的表达手段。

平台默认值替你做完了这道选择题

按建站平台分组,压缩后的传输量中位数是这样:

平台站数404传输量中位数说“有”的站
Shopify5981091字节2
Magento398115字节0
认不出平台1854383字节9
Salesforce Commerce1035062字节0
BigCommerce237488字节0
Next.js1025081字节0

Next.js那一组是最省的,中位数25KB,只有Shopify的三分之一。原因不神秘:Next.js有一个约定俗成的not-found页面,默认长什么样就是什么样,很多团队根本没动过它,于是它保持了一个极简的形态。而Shopify主题里的404模板挂在完整的layout下面,导航、页脚、全套挂件一个不少。

说“有”的11个站里,有9个落在“认不出平台”这一组。这一组基本是自研或者重度定制的站,恰恰是最容易漏掉这条路径的一类——用现成平台的时候,这道题平台替你答了;自己写的时候,就得自己记得答。

缓存头也是默认值的产物

顺手看了404响应的Cache-Control:写private, no-store的46个站,压根没这个头的14个,其余是各种no-cache组合。

这意味着绝大多数站的错误页是每次现做的,同一个不存在的地址被要一万次,服务端就渲染一万次。对一个内容永远不变的响应来说,这个默认值不太合理。多层缓存那套逻辑在这里可以反过来用:错误页恰恰是最适合被边缘节点缓存的东西。

把问的节奏放慢,答案会变吗?

会变,而且变得很不讲道理。这一节是方法论,但它同时是这次普查里最贵的一个教训。

第一轮用的是4个并发、每个请求之间隔0.35秒,跑完发现36个站返回429,也就是“你要得太快了”。这些站的数据全废了。

于是把节奏放慢到2个并发、间隔1.6秒,整批重跑。结果:

  • 429的站从36个降到6个
  • 37个站从被拒变成能进
  • 但有7个站反过来了:快的时候能进,慢下来反而被拒(allbirds.com、aloyoga.com、avocadogreenmattress.com、awaytravel.com、baseus.com、beistravel.com、swarovski.com)

如果只看前两条,很容易得出“放慢就能绕过限流”这个结论。第三条把它否掉了。限流不是一条你放慢就一定能压到线下的曲线,它按时间窗计数,你慢下来只是换了一个窗口落脚,运气不好照样撞上。

这件事对做SEO的人有直接的用处。限速规则拒掉的第4个请求要是robots.txt,全站抓取会停十几个小时抓取速率被调低的那几天,服务器日志里可能一条错误都没有。爬虫也是这样,在别人的限流窗口里碰运气。

还有一层:那17个始终没进去的站(403十个、418一个、429六个),它们只能算“判不了”,不能算“没有404页面”。把判不了的样本混进分母是一种很常见的作假,哪怕是无意的。

三个数先量出来,再谈改

这件事的排查成本极低,低到没有理由不做。三条命令,一分钟:

curl -s -o /dev/null -w "%{http_code} %{size_download}\n" \
  -H "Accept-Encoding: gzip, br" https://你的域名/zwb-not-exist-9x8

curl -s -o /dev/null -w "%{http_code} %{content_type} %{size_download}\n" \
  https://你的域名/zwb-not-exist-9x8.css

curl -s -o /dev/null -w "%{http_code} %{content_type} %{size_download}\n" \
  https://你的域名/zwb-not-exist-9x8.jpg

第一条看状态码和传输量,第二三条看后缀路径有没有被短路掉。注意size_download在带Accept-Encoding时给的是压缩后的字节,正是你要的那个口径。用curl -I查响应头有它自己的坑,这里必须用GET。

拿到三个数之后对号入座:

症状该改哪里大概能省多少
状态码是200先修状态码,其它都是后话决定性的
状态码对,传输量 > 100KB给错误页做一套精简模板,砍掉巨型导航和第三方挂件本批实测可降到25KB一档
.css/.jpg也返回整张HTML在服务器层给静态后缀加一条兜底规则,不进应用单次省掉99%以上
Content-Type与后缀对不上同上,顺带把类型给对省的是浏览器的困惑
Cache-Control是no-store允许边缘节点缓存错误页省的是服务端渲染次数

错误页该长什么样

不用做成极简的白底黑字,那样用户体验会掉。合理的做法是给404单独一套模板:保留品牌头部、一个搜索框、几条主要分类的链接,其余全砍。参照本批数据,25KB那一档是完全做得到的,而且视觉上并不寒酸。

要砍的重点是第三方挂件。中位数39个<script>里,真正跟“告诉用户走错了”有关的一个都没有。评价挂件、推荐引擎、A/B测试框架、客服机器人,在一个不存在的地址上全都无事可做,却照样被下载和执行。

删了商品之后到底该回什么

顺带说个常被问的:商品下架了,地址该回404还是410?404的意思是“我不知道这里有没有东西”,410的意思是“确实有过,我确认它没了”,两者的正式定义都写在RFC 9110的状态码那一章里。哪些死链必须修、哪些纯属白费功夫那篇讲过判据:有替代品就301到替代品,没有替代品且确定不回来就410,拿不准就404。

本批103个说“没有”的站里只有zwilling.com一个用了410。这不是说其他人做错了,只是说明这个区分在实践中基本没人做——而对搜索引擎来说,410确实能让它更快地把这个地址从索引里拿掉。

把它挂进哪个流程

这不是一次性的活。改版、换主题、上CDN、换框架版本,都可能把这条路径改回去。合适的做法是把上面三条curl写进上线后的冒烟检查,跟可索引性体检放在一起跑。死链检测查的是“该活的活没活”,这一项查的是“该死的死得干不干净”,两边合起来才是完整的。顺带把站点地图里那些已经坏掉的网址一起筛一遍,那批地址正是爬虫撞上错误页的主要来源。

保哥这些年见过最离谱的一次,是一个站把404模板挂在了带商品推荐的layout下面。推荐引擎每次都要查一遍数据库,于是每一个爬虫撞上的死链,都在数据库上开了一次全表扫描。分面导航生成的海量URL撞上这种配置,就是一场没有尽头的自我攻击。

常见问题解答

返回404的页面还需要加noindex吗?

不需要。状态码404本身已经告诉搜索引擎这个地址不该进索引,再加noindex是重复的。本批103个正常返回404的站里有21个加了,属于多做一步不亏,但没加的82个也不构成问题。真正需要noindex的是那些状态码给不对、只能返回200的场景,而那11个返回200的站里恰恰只有一个加了。

错误页几百KB,真的会影响SEO吗?

影响的是抓取预算,不是排名。Google关于大型网站抓取预算的说明里把软404单列成一项浪费,理由正是这个:爬虫每天在你站上的额度是有限的,花在错误页上的字节和请求数就是从正经页面那里挪走的。对几百个页面的小站这笔账可以忽略,但对商品动辄上万、改版频繁、死链存量大的电商站,这是一笔实打实的开销。判断标准很简单:去日志里看爬虫每天撞多少次404,乘以你刚量出来的那个传输量。

怎么判断我的站是不是软404?

用curl请求一个绝对不存在的地址,看返回的状态码。是200就是软404,无论页面上写着什么。要特别注意跳转到首页这种做法,最终状态码是200、地址也变了,这在搜索引擎那里同样算软404。Search Console的覆盖率报告里也有专门一栏,但它是抽样的、有延迟,自己curl一下更快。

单页应用没法在服务端判断地址存不存在,怎么办?

三条路。一是给路由做服务端渲染或预渲染,让服务器知道哪些地址是有效的;二是用框架自带的not-found约定,让构建期就生成正确的错误响应;三是退而求其次,在边缘节点上维护一份有效地址清单,命中不了的直接由CDN返回404。第三条最容易落地,代价是清单要跟着内容更新。

不存在的图片和CSS返回整张HTML,除了浪费流量还有别的害处吗?

有。浏览器拿到一份Content-Type是text/html的响应去当样式表用,会直接拒绝应用,控制台报一条MIME类型不匹配。如果这个样式表恰好是首屏关键的那一份,页面会以裸HTML的形态渲染一瞬间。更麻烦的是这类问题在本地开发环境几乎不出现,因为开发服务器的静态目录和线上不是一回事。

把错误页缓存起来会不会导致真实页面上线后还显示404?

会,所以缓存时间要短,几分钟到一小时是常见的取舍,同时上线流程里要有清缓存这一步。更稳的做法是只在CDN层缓存、不在浏览器层缓存,这样你随时可以主动刷掉。风险确实存在,但跟每次都回源渲染一整套模板相比,多数站点的天平是倒向缓存这一边的。

410用起来有什么风险?

410表示“确认没了、别再来了”,语义比404强。风险在于它不可撤销的味道太重——如果这个商品以后还会上架、或者这个地址只是暂时下线,用410会让搜索引擎更快地把它清出索引,回来的时候要重新爬一遍。判据是“这个地址还有没有回来的可能”,没有就用410,有就用404。

权威参考资料

分享到
标签
版权声明

本文标题:《404页面有多贵?114个大站说一句没有就要66KB》

本文链接:https://zhangwenbao.com/404-page-crawl-budget-byte-audit.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交