現在很多頁面的结构是脚本拼出来的:導航、列表、卡片、分頁,都可能在浏览器执行 JavaScript 之後才出現在 DOM 里。對用戶来说没差別,但對搜尋蜘蛛来说,同一個地址可能對應两份不同的東西——服務器直接返回的原始 HTML,和脚本跑完之後形成的頁面。URL 發現發生在哪一份里,结果差別很大。
蜘蛛先拿到的通常是原始 HTML
抓取往往從一次普通的 HTTP 請求開始。這一步如果返回的 HTML 里没有連結,蜘蛛就没有可跟的路径,只能停在原地。执行脚本、渲染出完整頁面一般發生在後面的阶段:资源要下载、脚本要跑完、還要在時間预算内完成。任何一环出問题,連結就可能没有被發現。
- 原始 HTML 里的 a 标簽最直接,尤其是带 href 的普通連結。
- 由 JS 插入的連結會被排到渲染环节,不保證和第一次抓取同时完成。
- 渲染本身要抓取脚本文件。如果關键 JS 被 robots.txt 拦掉,或者加载超时、体积過大,連結等于不存在。
- 渲染失敗时,蜘蛛拿到的仍是那份没有連結的 HTML,它不會反复重试到你满意為止。
哪些寫法容易让連結消失
問题通常不在“用了 JS”,而在于連結從来没有以 URL 的形式出現在任何一份文档里。
- 用 div 或 button 绑定 click 事件做跳轉,頁面上没有可解析的地址。
- 点击之後才通過 pushState 改寫地址,URL 始终没有出現在 HTML 中。
- 連結依赖滚動、hover、懒加载才出現,蜘蛛不一定触發這些交互。
- 内容随登入狀態、地域或 A/B 實驗變化,同一份 HTML 在不同环境下並不一致。
- 分頁只有“加载更多”按钮,没有可抓取的下一頁地址。
- 把導航渲染在客戶端组件里,而首屏 HTML 只留下一個空的容器节点。
把關键連結放回 HTML 里
渲染是弥补手段,不是唯一出路。需要被發現的层級,最好在原始响應里就存在。
- 主導航、栏目入口、列表頁的前若干條,尽量由服務端渲染或预渲染輸出成真實的 a 标簽。
- 分頁保留獨立 URL,上一頁、下一頁用連結表達,而不是纯按钮或無限滚動。
- 把 Sitemap 当作兜底通道,让不依赖内鏈也能被發現的地址有地方可查。
- 确實必须由 JS 生成的区块,保證脚本可抓取、依赖可控、不依赖用戶本地狀態。
- 重要入口避免只出現在彈窗、折叠面板或标簽頁的隐藏分支里。
怎么自己驗證一遍
最简單的办法是禁用 JavaScript 打開頁面,看看還剩多少可点的連結;再關掉图片和样式,確認 href 是否真實存在而不是執行时才补上。其次可以對照服務器日誌里“取 HTML”和“渲染頁面”两類請求,观察脚本文件有没有被拦、有没有出現大量超时,以及渲染請求是否集中在少數几個地址上。
也可以用一個不带脚本执行能力的抓取工具跑一遍入口頁,把结果和浏览器里的頁面结构對比。两邊差距越大,說明越多的 URL 發現押在了渲染上。
能被静態 HTML 表達的連結结构,稳定性永遠高于依赖脚本生成的同類结构。
邊界怎么定
並不是所有連結都要寫死在 HTML 里。评论区、推荐位、個性化模块交给 JS 問题不大,它們對 URL 發現的價值本来就有限。真正需要被發現的那一层——栏目、列表、分頁、詳情入口——最好在原始响應里就能看到。把這個邊界划清楚,抓取路径會更可预期,URL 發現也不會因為一次脚本报错而整体停摆。