打開開發者工具,頁面上是一排排可以点的卡片和分頁按钮;查看網頁源代碼,這些連結却一個都找不到。對用戶来说没差別,對蜘蛛来说,這是两條不同的路。
蜘蛛先看源碼,再决定要不要渲染
抓取大体分两步:第一步下载 HTML 並解析,第二步在需要时执行脚本、渲染頁面。第一步便宜且稳定,第二步更贵,也更不确定。
- 寫在 a 标簽 href 属性里的地址,第一步就能進入待抓取队列。
- 只在脚本执行後才出現的連結,要等渲染完成才可能被發現,而渲染並不是每次都會發生。
所以問题往往不是“蜘蛛不抓 JS 頁面”,而是連結從源碼里消失了,URL 發現少了一條稳定入口。
連結“消失”的几種常见寫法
- 用脚本跳轉代替連結:元素上只绑了点击事件,跳轉由脚本里的地址赋值完成。没有 href,第一步就看不到目标地址。
- 前端路由:單頁應用通過 history 接口改寫地址栏,源碼里的列表項没有 href。
- 資料来自接口:列表由接口返回的 JSON 渲染,目标地址也寫在 JSON 里。
- 外面套了容器:内容放在 iframe 里,源碼中看不到内部連結。
- 只在交互後加载:点击“展開更多”才把下一批連結插入頁面。
這些寫法本身没有错,問题在于它們把 URL 發現完全押在渲染這一步上。
让連結回到源碼里:几個层級的做法
首選:服務端渲染或静態生成
列表頁、詳情頁、分頁導航在服務端就把 a 标簽和 href 輸出到 HTML 里,蜘蛛第一步就能拿到。分頁用真實的下一頁連結,而不是一個按钮。這样即使後續脚本出错,抓取路径依然完整。
次選:预渲染與動態渲染
對已知的抓取 UA 返回渲染後的 HTML,是可行但需要持續维護的方案。要留意几点:
- 渲染後的 HTML 與普通用戶看到的應保持一致,不要返回明顯不同的内容。
- 中間若有缓存层,缓存键要能区分不同版本,避免把空壳頁缓存给蜘蛛,或把渲染版塞给普通用戶。
- 渲染服務要纳入监控,長時間超时或报错會让蜘蛛拿到不完整的頁面。
兜底:Sitemap 與静態内鏈
如果短期内改不了前端,至少把 URL 清單交给 Sitemap,並在頁脚、主導航、面包屑這類静態輸出的位置补上通往重要頁面的連結。這样即便渲染环节失敗,蜘蛛仍有別的入口到達這些地址。
渲染服務不稳时會發生什么
渲染依赖服務端资源,出問题时常见两種表現:
- 返回 200,但頁面是空壳。蜘蛛拿到一個几乎没有正文和連結的 200 頁面,容易影响它對這批 URL 的後續抓取意愿。
- 超时或返回 5xx。蜘蛛會稍後重试,短期内這片路径的發現能力下降。
因此,渲染失敗率、平均耗时、空内容比例都值得進监控,而不是只盯着首頁能不能打開。
抓取路径断掉时,站点通常不會报错,只是新頁面迟迟不出現。检查源碼,比反复提交 Sitemap 更直接。
怎么自查
- 用命令行請求頁面,看返回的 HTML 里有没有目标連結。
- 浏览器禁用脚本後再看一遍,確認主導航和列表仍能走通。
- 在服務器日誌里筛蜘蛛的請求路径,看它是否只停在首頁和少數入口頁。
- 對比源碼版本與渲染版本,確認連結是否只在渲染後才出現。
小结
蜘蛛走的是它拿到的那份 HTML。把關键連結寫回源碼,是让抓取路径稳定的省事做法;前端渲染並非不能用,但要清楚它把 URL 發現的责任從 HTML 轉移到了渲染服務上,而這一步更容易出错。