蜘蛛第一次請求一個 URL 时,拿到的通常是服務器直接返回的原始 HTML,而不是你在浏览器里看到的那個完整頁面。如果頁面的主要内容是靠 JavaScript 在客戶端拼出来的,這段内容就不在這份 HTML 里,它得等下一轮渲染處理,才有机會被看到。理解這一点,能解释很多“頁面明明存在,却像没被抓過”的現象。
第一步取 HTML,第二步才是渲染
抓取大致分两段:先是用普通請求把 URL 的响應体取回来,這一步快、成本低、能覆盖的數量大;之後才會把一部分頁面放進渲染队列,用接近浏览器的方式执行脚本,拿到渲染後的 DOM。只有走到第二步,JS 生成的内容才可能進入後續判断。
- 取 HTML 阶段:看的是服務器返回的字节,連結、正文、meta 都在這里被识別。
- 渲染阶段:执行 JS、加载异步資料,再重新提取内容與連結。
- 两個阶段的队列是分開的,渲染明顯更慢,能處理的數量也少得多。
所以頁面的抓取表現,取决于内容出現在哪一段。服務端就輸出的内容,在第一段就能被讀到;只存在于渲染後的内容,要先排队。
渲染队列不是無限的
渲染要占用計算资源,因此它更像是一種配给:站点權重、頁面重要程度、歷史抓取表現都會影响一個 URL 能不能進队列、多久轮到一次。排队時間長、單頁渲染有超时限制,脚本太慢或者依赖外部接口迟迟不返回,都可能让渲染在半途結束,只留下一份不完整的 DOM。
常见的後果是:連結能發現,但正文抓不到;正文能抓到,但分頁連結、图片、價格這些動態部分缺失。它們不是被拒绝,而是没等到渲染完成。
哪些寫法最容易在這一步掉队
- 内容懒加载:图片和正文都要滚動或進入视口才請求,渲染时不一定會触發。
- 点击後才插入:标簽頁、折叠面板里的文字,預設狀態是空的。
- 無限滚動:後續内容没有獨立 URL,也没有可点击的下一頁連結。
- 依赖登入態或本地存储:未登入訪問时脚本直接中断。
- 關键 JS 被 robots.txt 屏蔽:脚本取不到,頁面自然拼不出来。
- 第三方脚本超时:渲染在等待外部請求时被卡住。
這些問题有一個共同点:把“能不能看到内容”交给了一個不确定的执行环境。
把關键内容放回 HTML
不必把整站改成纯静態,但影响抓取判断的部分最好不依赖渲染。可以按下面的顺序處理:
- 正文、标题、主要連結直接由服務端輸出,脚本只负责增强交互。
- 列表頁的分頁入口用真實的 a 标簽寫在後端 HTML 里,不要只用 JS 绑定点击。
- 條件允许时用服務端渲染或预渲染,让首屏 HTML 就带上内容。
- 動態資料接口尽量同源、响應快,避免渲染时長時間空等。
- 確認 JS、CSS 资源本身可以被抓取,不要誤屏蔽。
- 為無限滚動的列表补上可獨立訪問的分頁 URL。
渲染只是让内容有机會被看到,抓取和渲染都不等于收錄,更不等于排名。把内容輸出得更稳,减少的是不确定性,不是结果本身。
怎么確認自己有没有這類問题
- 關掉 JS 抓取一次頁面,對比和浏览器里看到的内容差多少。
- 用工具的“查看渲染後 HTML”功能,看正文是否真的出現在 DOM 里。
- 翻服務器日誌,观察同一 URL 是否出現两類請求:一類只取 HTML,一類會连带請求脚本和接口。
- 挑几個重要模板頁做渲染前後對比,重点看正文首段和列表連結。
如果某個模板在關閉 JS 後几乎空白,那么它的抓取表現就會明顯受渲染队列的节奏影响。先把這部分内容搬回 HTML,往往比反复提交 URL 更有效。