搜尋引擎處理一個 URL,通常不是「打開浏览器看一眼」這么简單,而是分成两步:先拉取服務器返回的原始 HTML,從里面提取連結、标题和正文;只有当頁面被判断為需要执行脚本时,才進入渲染队列。渲染要消耗額外的资源,排队時間往往比抓取本身長很多。因此,如果一條連結只存在于脚本执行之後的 DOM 里,它在第一步就是缺席的,被發現的時間會被明顯拉長。
連結在原始 HTML 里缺席的几種常见寫法
- 用 div 或 span 绑定点击事件:视觉上是按钮或列表項,但没有 a 标簽和 href,抓取阶段讀不到目标地址。
- 地址由脚本拼接:点击後执行跳轉,URL 藏在 JS 變量或接口返回的資料里,HTML 源碼中找不到。
- 列表靠接口异步填充:初始 HTML 只有一個空容器,几十條内容連結全部来自异步請求的返回值。
- 滚動触發加载:懒加载只處理了图片,列表項本身也依赖滚動事件才插入,首屏之外的内容預設不可见。
- hash 路由:地址栏變成带 #/detail/123 這類形式,真實路径不在初始 HTML 中出現,連結也不容易被当作獨立入口。
抓取與渲染是两個阶段,代價不同
抓取阶段相對便宜,一次請求加一次解析就能拿到連結;渲染阶段昂贵得多,要么在無头浏览器里跑完整套脚本,要么依赖预渲染服務。所以搜尋引擎會把渲染名額分给一部分頁面,而不是每個 URL 都渲染。分配逻辑通常與頁面重要性、更新频率、連結權重有關,外部很难知道具体阈值,但有一條稳定的規律:能直接從 HTML 讀到連結的頁面,被發現和再抓取的机會,遠大于必须渲染才暴露連結的頁面。
排查:先看源碼,再看 DOM
- 用命令行請求一次頁面,把返回的 HTML 存下来;或在浏览器中選擇「查看網頁源代碼」,注意不是開發者工具里的元素面板。
- 在源碼中搜尋目标連結的特征片段,例如路径關鍵詞、詳情頁 ID 前缀。
- 如果源碼里搜不到,再在渲染後的 DOM 中搜一次,確認差异确實来自脚本注入。
- 對照服務器日誌,看這些 URL 是否長期没有抓取记錄,把「没被抓」和「没被發現」区分開——两者的處理方式完全不同。
可落地的改進方向
關键入口做服務端渲染或预渲染
首頁、频道頁、列表頁、詳情頁的上下篇導航,這些承担分發职责的位置,優先让連結出現在原始 HTML 中。服務端渲染、静態化、预渲染都是常见路径,選哪種取决于現有架构和缓存策略,不必一步到位,先從流量最大的层級開始。
保留一份静態兜底
异步列表可以在 HTML 里先輸出一小批連結,比如首屏若干條,脚本加载成功後再替換或追加。這样即使渲染环节被延後,連結依然存在。分頁部分同理,除了「加载更多」按钮,最好保留可点击的頁碼連結,让翻頁序列在源碼里连續。
用 Sitemap 覆盖連結薄弱的頁面
有些頁面天然缺少内鏈入口,比如篩選组合頁、归档頁、专题頁,它們更容易被漏掉。可以把這類 URL 明确寫進 Sitemap,作為發現渠道的补充。注意只放返回 200 且内容正常的地址,把 404、空壳頁混進去,會稀释整份文件的可信度。
内鏈结构仍然是最稳的一條路
一個 URL 如果能從多個相關頁面通過正常的 a 标簽到達,被抓取的路径就多几條;如果它只在某次脚本执行後才出現,就只剩下渲染队列這一條窄路。梳理栏目层級、补充上下文内鏈、给孤岛頁面找归属,這些老办法在脚本站点上依然有效。
几個容易踩的坑
- 為了「让蜘蛛看到」堆砌隐藏連結或隐藏文字,風險大于收益。
- 把主導航完全交给脚本,虽有渲染兜底,但延迟和覆盖都不确定。
- 改版後只更新了前端路由,忘了同步 Sitemap 和舊連結的跳轉規則。
- 認為提交了 Sitemap 就一定會被立刻抓取,忽略了抓取調度本身的時間成本。
判断标准可以很简單:關掉 JavaScript,用文本浏览器打開頁面,還能不能顺着連結走到你想让搜尋蜘蛛到達的頁面。如果能,說明基础结构是通的;如果不能,至少要让關键层級在源碼中保留一條通路。