蜘蛛爬到一個入口頁时,最先讀到的並不是頁面里的文字,而是服務器返回的响應头。狀態碼决定這一趟值不值得繼續,响應头則决定下次還要不要来、以什么方式缓存、把這段字节当成什么来解析。很多入口頁在正文上花了不少功夫,响應头却全是服務器預設值,爬虫對這頁的更新节奏和内容類型只能靠猜,猜错的代價就是白跑一趟。
响應头是爬虫讀取的第二道信息
响應行之後,每一個字段都會被解析器按顺序讀走。不同字段的權重不一样,但有一條規律是共通的:字段缺失或前後矛盾时,爬虫倾向于走保守路径——少来、慢来、按舊内容處理。所以响應头不一定要寫得多花哨,但至少要自洽。
Content-Type:別把 HTML 發成別的類型
- 入口頁應当返回 text/html 並带上正确的 charset。charset 缺失时,爬虫要靠内容嗅探来猜编碼,猜错就是乱碼。
- 如果服務器把頁面返成 text/plain 或 application/octet-stream,解析器可能根本不当它是一個網頁来處理。
- 响應头里声明的编碼要和頁面 meta 里寫的一致。两處打架时,不同爬虫的取舍顺序並不统一。
Last-Modified 與 ETag:告诉爬虫内容變了没有
這两個字段的核心用途是让爬虫判断资源有没有變化,從而决定是重新拉取全文,還是只做一次轻量確認。
- Last-Modified 精度到秒。批量生成的入口頁如果所有頁面的時間戳完全一致,這個信号基本就废了;反過来,如果每次請求時間都在變但内容没變,爬虫會認為你在制造假更新。
- ETag 建议基于内容摘要生成,而不是随机數或進程 ID。基于内容生成时,内容不變 ETag 就不變,爬虫的比對才有意义。
- 两個字段同时存在且不冲突时,效果最好。只剩一個也能用,但不要出現 ETag 變了而内容没變的反复抖動。
Cache-Control 與 Expires:缓存時間不是越長越好
缓存字段在這里會经過两层:一层是爬虫自身的缓存策略,一层是 CDN 或反向代理的中間缓存。
- 更新频繁的入口頁,如果设了很長的 max-age,爬虫和中間层都可能拿到舊版本,你改了正文,外面的视图還是老的。
- 長期不變的入口頁可以设長一些,减少重复传輸。
- 需要每次確認的场景,用 no-cache 比 no-store 更合适——允许缓存,但要求回源校驗。no-store 會让任何一层都不留副本,抓取開销反而變大。
X-Robots-Tag 與其他容易被漏掉的字段
- X-Robots-Tag 的效果和頁面里的 meta 指令類似,但它藏在响應头里,改模板时最容易忘记同步。排查入口頁為什么没被繼續處理时,這個字段要優先看一眼。
- Content-Length 與實际字节數不一致,會让部分客戶端提前截断或等待超时。
- 3xx 场景下的 Location 應当指向真實可達的地址,避免鏈條過長。跳轉本身怎么做是另一個话题,但响應头里至少要保證目标稳定。
- Vary 字段如果用得随意,會让缓存命中率忽高忽低,間接影响爬虫拿到的版本。
一份可以照着過的检查清單
- 狀態碼與响應头是否自洽,正文長度和 Content-Length 能不能對上。
- Content-Type 是否為 text/html,charset 是否和頁面 meta 一致。
- Last-Modified 是否反映了真實的最近一次内容修改,而不是部署時間或進程啟動時間。
- ETag 是否随内容變化,且不會無故抖動。
- Cache-Control 是否與這頁的更新频率匹配。
- X-Robots-Tag 是否和模板里的 meta 指令保持一致,没有互相矛盾。
- 批量抽查几台机器、几條线路,確認响應头是同一套,而不是被某层代理改寫過。
响應头能让爬虫更准确地理解入口頁的類型和變化,但它只解决“看得懂”的問题,不解决“會不會收錄”的問题。把响應头理顺是基本功,不是捷径。