HEAD请求实测124个站:九成的站和GET给的不是同一套头
本文目录
- 同一个地址,换一种问法,答案会不会变?
- 为什么值得花时间量这个
- 先交代一个负面结果
- 换成OPTIONS,124个站为什么给出9种答案?
- 这些答案是随手给的,不是设计出来的
- 规范要求的那个Allow头,有几个站给了?
- 问它支持哪些方法,它为什么把整页发了过来?
- 这件事花的是谁的钱
- 同一个页面,HEAD为什么说它0字节?
- 为什么会是0,我去挖了一层
- 反方向那14个站
- 两种问法拿到的响应头,是同一套吗?
- 差异不是均匀分布的
- 我原本担心的那件事没发生
- ETag那一格,我差点写出一个错结论
- 那两个真的方法差异长什么样
- 那32个每次都变的,其实是更大的问题
- 同一个站的首页和内页,答案为什么不一样?
- 第三方脚本那一层也一样
- robots.txt用HEAD问,会得到什么?
- weber.com的robots.txt,两种问法连压缩算法都不一样
- 这一轮,我的尺子错了五次
- 这一轮没测到的
- 顺带说说那7个真进不去的
- 自己的站该怎么测?
- 第一步,三种问法各来一次
- 第二步,把ETag连问两次
- 第三步,别忘了robots.txt和sitemap
- 第四步,看看你的前面站着谁
- 常见问题解答
- Googlebot会用HEAD请求抓我的站吗?
- OPTIONS返回404,需要修吗?
- HEAD把Content-Length报成0,会不会影响SEO?
- ETag每次都变,具体怎么修?
- 我的站robots.txt用HEAD也是404,怎么办?
- 这套测法值得做成定期检查吗?
- 权威参考资料
摘要:对131个英文大牌站点的同一个首页地址,分别用GET、HEAD、OPTIONS问了一遍。能问通的124个站里,112个站两种问法拿到的响应头不是同一套,占90.3%;OPTIONS这个问题收到了9种不同答案;50个站在HEAD上把内容长度报成0,而GET说有12万字节;还有一个站的robots.txt,用GET拿是200,用HEAD拿是404,复验三次都一样。
做站的人测页面,测的几乎永远是GET。浏览器发的是GET,你自己打开页面发的是GET,性能工具跑的也是GET。
但每天来敲门的机器不止用这一种问法。链接检查工具为了省流量优先发HEAD,CDN预热和缓存刷新用HEAD探活,跨域请求前浏览器会先发一个OPTIONS问一句你允许我干什么,监控脚本更是清一色HEAD。
这些通道你没测过。上一轮量证书那本公开账本时有个感受:一个系统总有几面是别人在看、你自己没看过的。这一轮把这句话挪到HTTP这一层,做法很朴素——同一个地址,换三种问法,看答案会不会变。
同一个地址,换一种问法,答案会不会变?
先把测法说清楚。131个站的首页,依次发GET、HEAD、OPTIONS三种请求,记录状态码、全部响应头、正文字节数和耗时。之后再往下测一层内页,以及robots.txt。
三种方法在标准里的分工是明确的。按RFC 9110对HTTP语义的定义,HEAD的语义是与GET完全相同、只是不返回正文,服务器发的响应头应当和GET一致;OPTIONS用来询问某个资源支持哪些方法,服务器应当在Allow头里给出答案。
| 方法 | 标准里的分工 | 谁在用它 |
|---|---|---|
| GET | 取回完整内容 | 浏览器、搜索引擎抓取、你自己 |
| HEAD | 只要元信息,不要正文 | 链接检查器、CDN预热、可用性监控、部分AI爬虫 |
| OPTIONS | 问这个资源支持哪些方法 | 浏览器的跨域预检、接口调试工具 |
说白了:HEAD应该是GET摘掉正文,OPTIONS应该是一张能力清单。标准写得很清楚,实现起来是另一回事。
为什么值得花时间量这个
因为用HEAD的那批客户端,恰恰是决定你在别人系统里长什么样的那批。外链监控用HEAD判断你的页面还在不在,判错了就是一条外链被标成失效;社交平台抓预览卡片时先探一下类型和大小;死链批量排查工具更是几乎全靠HEAD,否则扫一遍全站的流量成本没法接受。
MDN对HEAD方法的说明里有一句实现建议:HEAD响应里的头应当和同一个请求用GET时一致,否则会误导缓存与工具。这句话本轮成了很好的对照基线——它说的正是那90.3%没做到的事。
先交代一个负面结果
我原本最怀疑的是状态码:会不会有站GET给200、HEAD给别的?
没有。124个能问通的站,首页的HEAD状态码全部是200,一个例外都没有。这一格干干净净。
负面结果也是结果,而且它把范围收窄了:这件事的差异不在状态码上,在状态码后面那一堆东西上。往下每一节都是从这里开始的。
换成OPTIONS,124个站为什么给出9种答案?
同一个问题——这个地址支持哪些方法——124个站给了9种不同的回答:
| OPTIONS返回 | 站数 | 占比 | 字面意思 |
|---|---|---|---|
| 404 | 62 | 50.0% | 这个地址不存在 |
| 200 | 27 | 21.8% | 成功 |
| 405 | 16 | 12.9% | 方法不允许 |
| 400 | 5 | 4.0% | 你的请求有问题 |
| 500 | 4 | 3.2% | 服务器出错了 |
| 403 | 4 | 3.2% | 拒绝 |
| 204 | 4 | 3.2% | 成功,没有内容 |
| 501 | 1 | 0.8% | 没实现这个方法 |
| 410 | 1 | 0.8% | 已永久删除 |
这九种回答里,语义上说得过去的只有三种:405说我不支持这个方法,501说我没实现这个方法,200或204带着Allow头给出清单。
占了一半的那个404是什么意思?它说的是这个地址不存在。可同一个地址,你用GET问它,它返回的是一个完整的首页。
500更离谱,服务器自己崩了。anker.com和soundcore.com就是这样,OPTIONS一发过去先来个500,倒是很讲礼貌地在500的响应里附上了Allow: GET, HEAD——出错了还不忘告诉你它支持什么,大概是本轮最有礼貌的一次崩溃。
410的意思是这个资源永久没了。一个每天在卖货的首页,被问到支持哪些方法时,回答说自己已经永久删除。
那4个返回204的站是唯一一批把这件事做对的:204的意思是我收到了、处理完了、没有正文要给你,正好对应OPTIONS这个问题的性质。它们的响应通常很小,几百字节,带着允许的方法与头部就结束了。整批131个站里,只有这几个站的这条路径像是有人专门写过。
这些答案是随手给的,不是设计出来的
看到这个分布,第一反应可能是各家技术选型不同。往下拆一层会发现不是这么回事:回答的形态和平台高度相关,跟品牌无关。大量Shopify站给的是404,一批Next.js站给的是405或者500,Salesforce Commerce那批给的是200。
换句话说,这九种答案里绝大多数不是任何人做过决定的结果,是各家框架对一个没人处理的方法给出的兜底行为。这和响应头里那248个死字段是同一类现象:不是写错了,是根本没人写过,默认值替你回答了。
规范要求的那个Allow头,有几个站给了?
RFC 9110写得很明确:服务器返回405的时候,必须生成一个Allow头,列出这个资源支持的方法。这是必须,不是建议。
本轮OPTIONS返回405或501的站一共17个。带了Allow头的,3个。
- canyon.com:
GET, POST, HEAD - eufy.com:
GET, HEAD - vuoriclothing.com:
GET, HEAD
剩下14个站告诉你这个方法不允许,却不肯说哪些方法才允许。这就像敲门问能不能进,里面回一句不行,然后没了下文。
反过来,有5个站在不必给的地方给了:assos.com、braun.com、uniqlo.com在200的响应里带了Allow,anker.com和soundcore.com在500里带了。该给的时候14个站不给,不必给的时候5个站给了,这一格的实现基本处于随机状态。
这条规则的实际影响不大,因为几乎没有客户端真的依赖Allow头做决策。但它是一个很好的指示器:一个连硬性要求都没实现的代码路径,说明这条路径从来没被人认真看过。后面几节的问题都长在同一条路径上。
问它支持哪些方法,它为什么把整页发了过来?
OPTIONS是问问题,不是要内容。但124个站里,有40个站的OPTIONS响应带回了超过1KB的正文,占32.3%。其中22个站带回的字节数与GET几乎一模一样——它把整个首页又发了一遍。
| 站点 | OPTIONS状态 | OPTIONS正文 | GET正文 |
|---|---|---|---|
| casetify.com | 200 | 1461241字节 | 1461238字节 |
| shein.com | 200 | 1137694 | 1137734 |
| mejuri.com | 200 | 928469 | 928116 |
| cybex-online.com | 200 | 791454 | 791454 |
| philips.com | 200 | 638650 | 638650 |
| on.com | 200 | 610431 | 610431 |
| arcteryx.com | 200 | 509224 | 509224 |
| charleskeith.com | 404 | 512752 | 571774 |
casetify那一行值得停一下:1.46MB。一个只是想知道你支持什么方法的客户端,收到了一份完整的商品首页。为了确认这不是偶发,我对其中五个站各测了两次,两次的字节数差在个位数以内,差值来自页面上的动态内容——这个行为完全稳定。
charleskeith那一行更有意思:状态码说404找不到,正文里却塞了512KB的HTML。这是典型的软404,只不过发生在一个很少有人看的方法上。Nginx配置每次reload都通过、Googlebot却撞在301上那一轮讲的是同一类事:配置本身没报错,行为在另一条路径上跑偏了。
这件事花的是谁的钱
浏览器发跨域预检时用的就是OPTIONS。MDN对OPTIONS的说明里给的标准回法是返回204、带上允许的方法与头部,不带正文。
一个站如果对每个OPTIONS都回一整页,那么页面上每一个触发预检的跨域字体、接口、埋点调用,都会在真正的请求之前先拖回来一份首页。
算一笔账:casetify的首页1.46MB,如果页面上有三个跨域资源触发预检,光预检就多出4.4MB的回源流量,而这些字节客户端一个都用不上,收下就扔。对TTFB和多层缓存那本账来说,这属于纯粹的浪费——不影响正确性,只影响账单和延迟。
还有个更隐蔽的代价:预检响应本身是可以被缓存的,但前提是服务端给出相应的头。回一整页HTML的那些站,多半也没给预检缓存时间,于是每一次跨域调用之前都要重来一遍。
同一个页面,HEAD为什么说它0字节?
这是本轮最整齐的一组数字。124个可测的站里,把GET和HEAD的Content-Length摆在一起:
| 对账结果 | 站数 |
|---|---|
| 两边一致 | 60 |
| HEAD报0,GET报真实长度 | 50 |
| GET不给(分块传输),HEAD反而给了 | 14 |
| 两边都给但数值不同 | 0 |
那50个站的形态完全一致:GET说这个页面压缩后8万到34万字节不等,HEAD说0。casper.com的GET报99951、HEAD报0;brooklinen.com的GET报122889、HEAD报0;dreametech.com的GET报342956,HEAD照样是0。
这50个站里,49个是Shopify,剩下那1个weber.com是Salesforce Commerce。又是一个平台级的行为,不是50个团队各自的疏忽。
为什么会是0,我去挖了一层
数字太整齐,得知道机制才敢写。做了一组对照实验:同一个地址,分别带上和不带压缩协商各发一次。
| 请求头 | GET的Content-Length | HEAD的Content-Length |
|---|---|---|
| accept-encoding: gzip, deflate, br | 99951(声明br压缩) | 0(同样声明br) |
| accept-encoding: identity | 不给 | 不给 |
答案出来了:这个0只在压缩协商成功时出现。服务端的压缩环节算的是它实际写出去的字节数,而HEAD响应的正文被丢弃了,压缩器写出0字节,于是Content-Length如实写了0——同时还留着一个content-encoding: br,声明说我压缩过了,长度是0。
逻辑上自洽,语义上是错的。RFC 9110说HEAD响应里的Content-Length应当指示GET会返回多大,而不是这一次实际发出去了多少。
后果落在谁身上?落在所有拿HEAD探大小的工具身上:链接检查器会认为这个页面是空的,CDN的预热逻辑可能因此跳过它,任何按体积做阈值判断的监控都会读到0。如果你的工具链里有一条“正文为空则告警”的规则,这50个站会集体误报,而它们其实好好的。
还有一层值得想想:这50个站占了可测样本的四成。任何一份声称覆盖了主流独立站的自动化报告,只要它某一步用了HEAD探体积,就有四成的样本读到的是0。这个错误不会报错、不会超时、不会留下任何异常痕迹,它只是安静地把一个正常页面标成空的。最难查的问题从来不是崩溃,是一个语法正确、语义错误、还每次都稳定复现的答案。
反方向那14个站
另一头也有反常的:14个站的GET用分块传输、不给Content-Length,HEAD反而给了一个具体数字。
| 站点 | GET的Content-Length | HEAD的Content-Length |
|---|---|---|
| kotn.com | 不给(分块) | 1527015 |
| traeger.com | 不给(分块) | 789228 |
| article.com | 不给(分块) | 655021 |
| anker.com | 不给(分块) | 316119 |
| shein.com / swarovski.com等6个 | 不给(分块) | 0 |
前面几个给的数字看着像页面的真实大小,可GET那一次并没有承诺过任何长度。后面6个更绕:GET拒绝回答,HEAD回答了一个错的。
同一个资源,两种问法,一个说不知道,一个说是零。两句话都不对,而它们出自同一台服务器。
两种问法拿到的响应头,是同一套吗?
不是。这是本轮覆盖面最广的一个结论:124个站里,112个站的GET和HEAD响应头条数不相同,占90.3%。
| 站点 | GET响应头条数 | HEAD响应头条数 |
|---|---|---|
| farfetch.com | 49 | 36 |
| bellroy.com | 36 | 27 |
| quince.com | 36 | 28 |
| weber.com | 34 | 24 |
| uniqlo.com | 25 | 16 |
把具体丢了哪些字段拆开看:
| 字段 | GET上有的站数 | HEAD上丢了 |
|---|---|---|
| set-cookie | 97 | 11 |
| content-encoding | 123 | 10 |
| vary | 113 | 6 |
| cache-control | 62 | 1 |
| link | 62 | 1 |
| last-modified | 11 | 1 |
| content-type | 124 | 0 |
| etag | 66 | 0 |
最值得注意的是vary那一行。6个站在HEAD响应里没有vary,GET上有。vary决定了缓存该按哪些请求头分片存储,缺了它,中间层就会把本该分开存的几个版本混成一个。决定收录哪一版页面的往往不是你的配置那一轮讲的正是这件事,只不过那次的分岔来自请求头,这次的分岔来自请求方法。
set-cookie丢了11个站,这个反倒可能是好事——HEAD探测本来就不该拿到会话Cookie。但它同样说明一件事:这两条响应路径在服务端是分开走的,不是同一份头减掉正文。
content-encoding丢了10个站也属于同一类:GET声明我压缩了,HEAD什么也不说。对客户端来说,这两次收到的是关于同一个资源的两份互相矛盾的描述。
为什么会分开走?常见的原因有三种:一是应用框架对HEAD做了短路处理,在生成完整响应之前就提前返回;二是反向代理或CDN在边缘层拦下了HEAD,用一份简化的头直接回;三是某些中间件只在有正文时才追加自己那几条头。三种情况的共同点是,写这段逻辑的人当时想的是省资源,没想过有人会拿这两份头做对比。
差异不是均匀分布的
把差值排一下会发现,条数差得最多的站,差的往往是同一批东西:CDN加的缓存状态头、安全相关的自定义头、以及一整组追踪用的内部标识。响应头自相矛盾那一轮量的是同一批头里前后打架的情况,这一轮是同一批头在两种方法上有和无的差别。
换个角度看,这也解释了为什么这类问题长期没人发现:缺的那些头,绝大多数不影响页面显示。它们只影响缓存、监控和自动化工具,而这三样东西出问题时,通常不会有人来提工单。
我原本担心的那件事没发生
动手之前我最担心的是X-Robots-Tag:假如一个页面靠响应头声明noindex,而这条头只在GET上出现,那么用HEAD做审计的工具就会漏判,页面被判成可索引,实际上不是。
实测结果是这个风险在这批站上不成立——124个站里只有1个站在首页发了X-Robots-Tag,而那一个GET和HEAD都发了。样本太小,不足以下任何结论。写在这里是因为它值得你在自己站上验一遍:如果你的noindex是靠响应头下发的,那这条头在HEAD上必须也在。
ETag那一格,我差点写出一个错结论
这是本轮最该记下来的一次翻车,因为它差一点就变成一个很吸引人、但完全错误的标题。
首轮数据是这样的:66个站的GET给了ETag,其中41个站的HEAD给的ETag和GET不一样。62%的不一致率,看起来是个大发现——同一个地址两种问法给出两个不同的版本凭据,条件请求当场失效。
写之前我留了个心眼,做了一组对照:如果连发两次GET,ETag会不会也变?
会。而且是大面积地变。把41个站逐个复测,每站各发两次GET、两次HEAD:
| 复测结论 | 站数 |
|---|---|
| 每次请求都在变,跟用什么方法无关 | 32 |
| 本次复测两边一致,首轮那次是抖动 | 7 |
| GET自身稳定、HEAD给的确实不同(真的方法差异) | 2 |
41变成了2。原来那个62%的不一致率,量到的根本不是方法差异,是这批站的ETag本身每次请求都在换。
那两个真的方法差异长什么样
kotn.com是最干净的一个样本,它的ETag在两种方法下都稳定,但形态不同:
GET → W/"174ce7-wIoHQXl4rJjaJ5AS5byKwUi/PGU"
HEAD → "174ce7-wIoHQXl4rJjaJ5AS5byKwUi/PGU"引号里的值一个字符都不差,区别是GET那个前面多了个W/。这个前缀在标准里有确切含义:带它的叫弱验证器,只保证语义等价;不带的叫强验证器,保证字节完全相同。同一份内容,用GET问它说大概是这个,用HEAD问它说就是这个。
强弱之分不是文字游戏,它决定了这个凭据能不能用于范围请求这类要求字节精确的场景。一个资源在两种方法下给出不同强度的承诺,等于把判断责任推给了客户端。
那32个每次都变的,其实是更大的问题
把错误结论修掉之后,剩下的事实反而更糟糕:32个站的ETag每次请求都在变。这意味着任何客户端拿着上一次的ETag来问“还是这个版本吗”,答案永远是否定的,条件请求在这些站上等于不存在。
之前量过142个站里只有52个能回出304,当时的结论停在回不出。这一轮补上了其中一类原因:不是它不肯回304,是它每次给的凭据都不一样,客户端拿什么来问都对不上。
对抓取预算的影响是实打实的,每一次重访都得整页重下,抓取预算优化里省下来的那点额度,在这一格上全漏光了。要修的话,第一步是定位那个每次都变的因子——多数情况下是页面缓存键里掺进了时间戳或者分流标记,具体的排查口径见浏览器缓存头怎么配那一篇。
同一个站的首页和内页,答案为什么不一样?
前面所有数字都来自首页。往下测一层内页之后,出现了本轮最反直觉的一组数据。
能同时测到首页和内页的站有109个,其中68个站的首页和内页在OPTIONS上给出了不同的答案,占62.4%。而且方向高度集中:
| 首页 → 内页 | 站数 |
|---|---|
| 404 → 200 | 41 |
| 200 → 404 | 4 |
| 200 → 405 | 3 |
| 405 → 200 | 3 |
| 其余组合 | 17 |
41个站在首页说这个地址不存在,在内页说完全支持。同一个域名,同一套代码,隔着一个路径的差别,能力清单就反过来了。
原因不复杂:根路径和商品路径在框架里往往由不同的路由规则接管,一个有兜底、一个没有。但它带来的结论很硬:用首页测出来的HTTP行为,不能代表这个站。任何一份只测首页的审计报告,在这一格上有62%的概率给出与内页相反的描述。
这条经验对做站点审计的人特别值钱:抽样的时候,首页从来不是一个有代表性的样本,它是全站最特殊的那一页——模板不同、缓存策略不同、路由优先级也不同。
为什么404到200这个方向占了绝对多数?一个合理的解释是路由的匹配顺序:根路径通常由一条最宽泛的规则兜底,而这条兜底规则往往只声明了GET;商品页、分类页那些带参数的路径,反而走的是更完整的处理链,方法限制没那么死。越是重要的入口,越可能落在最粗糙的那条规则上——这个反差在很多站的配置里都成立,不只是HTTP方法这一格。
第三方脚本那一层也一样
顺手测了每个站首页上引用的第一个静态资源。79个能取到的资源里,有两个站命中了同一个第三方地址:cdn.cookielaw.org上的同意管理脚本,用GET取是200,用HEAD取是405。
这是一个装在无数站点上的合规脚本。它自己不允许HEAD,意味着任何一个想检查这个脚本还在不在的自动化流程,都会拿到一个方法不允许的答案。第三方脚本会悄悄拉进一串你看不见的域名,而这些域名的行为完全不由你决定,这里又多了一个例子。
robots.txt用HEAD问,会得到什么?
最后一层测的是robots.txt。选它是因为它的读者几乎全是机器,而机器里有一部分习惯先用HEAD探一下。
这一层能拿到200的站有62个,比首页少,原因下一节说。62个站里61个的HEAD也是200,只有一个例外:
sostrenegrene.com的robots.txt,用GET拿是200、312字节的纯文本;用HEAD拿是404。
为了确认不是抖动,交替测了三个来回,六次请求:GET三次全是200,HEAD三次全是404。完全稳定。
这意味着什么,取决于谁来问。一个先用HEAD确认文件存不存在、存在再GET取内容的爬虫,在这个站上会得到没有robots.txt这个结论。而没有robots.txt的默认含义是全站可抓。这个站robots.txt里写了什么,对这类客户端来说完全不生效——不是被忽略,是压根没被发现。
之前量过robots.txt里35.7%的指令根本没有接收方,也量过分组不继承导致的规则落空。那两轮讲的是写了没人读、写了读错组。这一次是第三种:写了,文件也在,但换个问法就查无此文。
weber.com的robots.txt,两种问法连压缩算法都不一样
还有一个稳定复现的:
| 方法 | Content-Length | Content-Encoding | 实收字节 |
|---|---|---|---|
| GET | 770 | br | 2289 |
| HEAD | 0 | gzip | 0 |
长度报0已经见过了,这次连声明的压缩算法都换了一个——GET说用的是br,HEAD说是gzip。两次复测结果完全一样。这说明两条路径上接手压缩的根本不是同一个环节。
robots.txt这一层还有个顺带的数字:61个站里,有41个的GET和HEAD响应头条数不同,占67.2%。连这么一个纯文本小文件,两种问法拿到的元信息都不是同一套。另外还有4个站的robots.txt直接返回404——这个数字本身不算问题,没有robots.txt是合法状态,但它提醒一件事:这个文件的存在与否,值得纳入常规巡检。
这一轮,我的尺子错了五次
上面每个数字背后都有一次判据修正。五条里有三条改变了结论方向,如果不查,这篇文章会有三个错误的核心数字。
| 错在哪 | 差点写出的结论 | 改法 |
|---|---|---|
| ETag只比了一次,没验证它自身稳不稳 | 41个站HEAD与GET的ETag不同,62%的站条件请求失效 | 每站补发GET两次,41个里32个是每次都变、7个是抖动,真方法差异只有2个 |
| 把一次抽样当成站点属性 | 47个站拒绝机器访问,可测样本只有84个 | 打乱顺序多轮重试,第一轮就救回39个,最终只剩7个真进不去,分母从84变成124 |
| 内页选择器没排除字体和JS | 6个站内页GET通、HEAD返回404 | 逐条看URL,其中4条抽到的是woff2、ttf、js文件,不是内页。归到资源层重新算 |
| 拿解压后的字节数去比Content-Length | 几乎每个站的长度都对不上,全站都在乱报 | HTTP客户端会自动解压,Content-Length说的是压缩后的长度。只能头对头比 |
| 没验证HEAD报0的成因就下判断 | 这50个站的HEAD实现是坏的 | 用identity再测一遍,发现两边都不给长度。这个0是压缩路径的产物,不是随手写的 |
第三条那次没有改变结论,但改变了归类。首轮统计说6个站内页GET通、HEAD返回404,看着像是内页层的问题;逐条打开URL才发现,其中4条抓到的根本不是内页,是页面上引用的woff2字体、ttf字体和一个js文件——我的内页选择器排除了css和js的常见后缀,却漏掉了字体格式,还有一个兜底分支根本没做过滤。把这4条归回资源层之后,内页层真正的问题只剩下访问被拦这一种。判据写错不一定会让数字变大或变小,它可能只是把问题挂到了错误的墙上。
第一条最值得说。一个62%的不一致率,看起来像个很有冲击力的发现,实际上量的是另一件事。判断的方法其实很朴素:任何一个“A和B不一样”的结论,都要先问一句“A和A自己一样吗”。这个对照只花了几分钟,省下的是一篇建立在错误前提上的文章。
第二条也一样。第一轮采集时47个站返回429或403,我差点写成这些站拒绝机器访问;换个时间、打乱顺序重试一轮,39个立刻就通了。被拦不是站点的属性,是那一次请求的结果。这条经验对所有做批量采集的人都成立:单次失败率不能当成覆盖率来报。
这一轮没测到的
把局限说在前面,免得这些数字被当成比它本身更大的结论。每个站只测了首页、一个内页和一个静态资源,抽样很薄;内页是从首页HTML里按规则挑的第一个符合条件的链接,不是按模板均匀抽的;所有请求都从同一个出口发出,没有换过地区,而不少站的边缘规则是按地区分的。
另外,POST、PUT、DELETE这几个方法一个都没测——不是忘了,是不该测。对别人的生产站发这类请求可能真的改动数据,这条线不能碰。所以本文所有结论都只覆盖只读方法,一个站在写方法上是什么表现,本轮不知道,也不打算知道。
顺带说说那7个真进不去的
三轮重试之后,稳定进不去的还有7个:aboutyou、fjallraven、pandora、thefarmersdog、warbyparker返回403,govee返回400,underarmour返回418。拦截方几乎全是Cloudflare。
其中429这个状态码值得单独提一句。它的字面意思是请求太多、稍后再试,可我这边第一个请求就收到了它。Google对抓取相关状态码的说明里,429被归为要求放慢的信号,抓取端收到之后会降低频率。用一个表示慢一点的状态码来表达不欢迎你,这两件事对机器来说完全不同:收到429的客户端会退避、会重试,而收到403的会记下来不再来。
之前量过robots.txt说允许、GPTBot仍然进不去,讲的是同一件事的另一面——身份识别这一层的决定,往往比robots.txt里写了什么更能决定谁能进来。
自己的站该怎么测?
不需要工具,curl就够。要点是每一条都测两个地址:首页和一个真实内页。
第一步,三种问法各来一次
curl -sI https://example.com/
curl -s -o /dev/null -D - https://example.com/
curl -s -X OPTIONS -D - -o /dev/null -w 'body=%{size_download}\n' https://example.com/把三份输出并排放着看,重点盯四样:状态码是否一致、Content-Length是否一致、vary和cache-control在不在、OPTIONS有没有回正文。最后一项最容易被忽略,因为curl默认不显示正文大小,上面那个size_download就是为它加的。
第二步,把ETag连问两次
for i in 1 2 3; do curl -sI https://example.com/ | grep -i '^etag'; done三次的输出如果不一样,那么你的条件请求是失效的,跟方法无关。这一格上如果发现问题,优先级要排在最前面——它直接影响回访抓取的成本,比本文其他任何一条都实在。
第三步,别忘了robots.txt和sitemap
curl -sI https://example.com/robots.txt
curl -sI https://example.com/sitemap.xml这两个文件的读者全是机器。HEAD返回的状态码必须和GET一致,长度也不该是0。sitemap里那个lastmod值不值得信是另一个话题,但前提是这份文件得先能被发现。
第四步,看看你的前面站着谁
如果你的站在CDN或者WAF后面,上面这些行为有一部分不是你的应用决定的。测的时候顺手记下server头和CDN的缓存状态头,出现异常时先判断是哪一层的行为。本文里那50个报0长度的站,没有一个是自己写的代码造成的。
| 症状 | 大概率在哪一层 | 要不要管 |
|---|---|---|
| HEAD的Content-Length是0 | 平台或压缩环节 | 自己改不了,但要知道监控会读到0 |
| OPTIONS返回整页 | 应用路由缺兜底 | 该管,白花流量 |
| 405不带Allow | 应用 | 影响小,顺手补 |
| ETag每次都变 | 应用或页面缓存键 | 优先处理 |
| robots.txt的HEAD是404 | 路由或边缘规则 | 该管,影响抓取 |
| 首页与内页答案不同 | 路由规则不一致 | 知道就行,别用首页代表全站 |
这一轮跑下来,保哥最想留下的不是某个具体数字,而是一个测法上的习惯:你以为你在测一个站,其实你只是在测它对你那一种问法的反应。换个方法、换个路径、换个时间再问一次,成本很低,而它揭示的东西往往和第一次完全不同。
常见问题解答
Googlebot会用HEAD请求抓我的站吗?
Google的抓取以GET为主,官方文档讨论抓取行为时也是按GET来描述的,所以本文这些差异对自然搜索的直接影响有限。真正会踩到的是另一批客户端:链接检查工具、CDN预热、外链监控、社交平台的预览抓取,以及相当一部分AI爬虫和第三方审计工具。它们用HEAD是为了省流量,代价是可能拿到一份和GET不一样的答案。
OPTIONS返回404,需要修吗?
如果你的页面上没有任何跨域请求,影响接近于零,可以放着。有跨域资源就值得看一眼:浏览器发预检时用的就是OPTIONS,返回一个带Allow的204是标准做法,返回整页是纯浪费。判断依据不是状态码好不好看,是它有没有把正文一起发出来。
HEAD把Content-Length报成0,会不会影响SEO?
对搜索引擎的收录判断没有直接影响,因为它们取内容用的是GET。会受影响的是你自己那套工具链:按体积做阈值告警的监控会读到0,链接检查器可能把页面判成空,CDN的某些预热策略会跳过零长度资源。如果这些工具在你的流程里有决策权,就得知道这个数字不可信。
ETag每次都变,具体怎么修?
先定位它是谁生成的。如果是应用层按响应内容算哈希,要检查内容里是不是掺了每次都不同的东西,比如时间戳、随机的追踪标识、A/B分流标记。如果是平台或CDN生成的,就看缓存键的构成——它把某个每次都变的因素算了进去。用SaaS建站平台的话,这一格多半改不了,但至少要知道自己的回访抓取成本是按整页重下算的。
我的站robots.txt用HEAD也是404,怎么办?
先确认是哪一层返回的:直接请求源站再测一次,如果源站正常,问题在CDN或边缘规则上,通常是某条路由规则只匹配了GET。如果源站也是404,那是应用路由的问题。这一条建议排在前面处理,因为robots.txt是少数几个纯粹面向机器、又直接影响抓取范围的文件。
这套测法值得做成定期检查吗?
值得,但频率不用高。这类行为在平台升级、CDN换配置、路由规则调整之后才会变,平时相当稳定。建议是在每次基础设施变更之后跑一遍,十几条curl命令的事。真正需要长期盯的只有ETag那一格,因为它和抓取成本直接挂钩。
权威参考资料
本文标题:《HEAD请求实测124个站:九成的站和GET给的不是同一套头》
本文链接:https://zhangwenbao.com/head-options-method-response-divergence-audit.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0
← 上一篇
证书透明日志实测131个站:CAA记录只有18个是自己写的下一篇 →
没有了