不少站点把列表、詳情連結和正文都交给 JavaScript 生成,然後在日誌里發現蜘蛛只訪問了首頁和几個入口。問题通常不是蜘蛛“不愿意抓”,而是它第一次拿到的 HTML 里根本没有那些 URL。把抓取和渲染分成两件事来看,排查思路會清楚很多。
抓取和渲染是两個阶段
蜘蛛請求一個地址,最先拿到的是服務器返回的原始 HTML。這一阶段做的事情很朴素:從响應里提取連結和文本,把新 URL 放進队列。如果原始 HTML 里没有某個連結,這個地址往往连被發現的机會都没有。
渲染是後面的事情。蜘蛛會把其中一部分頁面送進渲染队列,执行頁面上的 JS,等 DOM 相對稳定後再提取一次連結和内容。這一步有排队、有资源上限,也有失敗概率,和第一次抓取不是同一條流水线。
哪些内容必须依赖渲染才能出現
- 列表由前端框架請求接口後拼出来的分頁和條目連結
- 詳情頁地址由 JS 拼接字符串生成,HTML 里只有一個空容器
- 正文完全来自接口返回,服務端没有任何兜底輸出
- 跳轉寫成点击事件或按钮,而不是带 href 的 a 标簽
- 需要登入、需要選擇地区、需要点击“展開”才顯示的内容
渲染這一环的限制在哪里
- 排队:渲染不是同步發生的,從抓取到渲染完成可能有明顯延迟,甚至長時間不渲染。
- 超时與失敗:脚本执行過久、依赖被阻塞、請求报错,都會让這次渲染被放弃,且不一定立刻重试。
- 视口:渲染通常在固定视口下進行,位于视口之外、需要滚動才顯示的内容不一定被加载。
- 交互:滚動、点击、“加载更多”這類用戶行為一般不會自動执行。
- 资源可訪問性:被 robots.txt 或權限拦住的 JS、CSS 會影响解析结果,渲染出来的结构可能和你在浏览器里看到的完全不同。
让 URL 出現在原始 HTML 里的几種做法
- 列表頁做服務端渲染或预渲染,至少让條目連結以 a href 的形式直接輸出。
- 分頁使用可点击連結,给出真實可訪問的網址,而不是纯 JS 按钮。
- 图片和模块懒加载尽量用原生属性,並在 noscript 中留一份連結或文本兜底。
- 無限滚動配一套分頁地址,让滚動到底後存在一個可被抓取的等價 URL。
- 让 Sitemap 與站内連結互為备份,重要地址至少在其中一處出現。
- 移動端與桌面端模板差异較大时,確認两套模板輸出的連結集合不要相差太多。
渲染不是免費的通行證
即使頁面能被渲染,代價也比静態 HTML 高:排队更久、失敗概率更大、重抓間隔通常也更長,而且渲染结果一旦出错,你很难立刻從日誌里看出来。能直接輸出的内容,尽量直接輸出。
不要假设蜘蛛會滚動、点击或等待你的“加载更多”按钮。任何需要用戶動作才出現的 URL,都按“預設不存在”来设計兜底方案。
自查的顺序
- 先看源代碼而不是审查元素,確認連結是否出現在原始 HTML 中。
- 用抓取工具對比渲染前後的連結差异,找出只在渲染後出現的 URL。
- 检查 JS 與接口請求是否被 robots.txt 或權限規則挡住。
- 用站長平台的 URL 检查工具查看渲染快照,確認渲染出来的頁面是否符合预期。
- 把 Sitemap 中的 URL 與抓取日誌做差集,差集里長期不被訪問的部分,優先补内鏈或改成静態輸出。
把站点的發現路径做成“不依赖 JS 也能走通”的结构之後,渲染問题就從阻塞項變成了優化項。剩下的工作才是提升渲染成功率、缩短渲染與重抓之間的間隔。