KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

03 · 渲染:爬虫看到的是不是你看到的 — keel 龙骨

本章目标:讲清「渲染(Rendering)」环节——从拿到的 HTML 到最终参与索引的文本,中间发生了什么。 验收标准:关掉 JavaScript 后,页面正文仍然完整可见。

本章目标:讲清「渲染(Rendering)」环节——从拿到的 HTML 到最终参与索引的文本,中间发生了什么。
验收标准:关掉 JavaScript 后,页面正文仍然完整可见。

为什么渲染是一个独立环节:抓取只保证"HTML 到手"。如果正文是由 JavaScript 在浏览器里生成的,那么爬虫拿到的很可能只是一个空壳。抓取成功 ≠ 拿到内容,两者之间隔着的就是渲染。


一、四种渲染方式

方式 谁执行渲染 爬虫拿到 适用
HTML 直出 / SSG / 预渲染 构建时生成静态 HTML 完整正文 内容型站点(首选)
SSR 服务端每请求渲染 完整正文 动态/个性化
ISR 服务端增量再生成 完整正文(可能有延迟) 大量内容的混合站
CSR(客户端渲染) 浏览器里的 JS 空壳(除非再渲染) 登录后的应用

一个典型的 CSR 页面源码:

<div id="root"></div>
<script src="/assets/index-abc123.js"></script>

爬虫第一次拿到的就是这个。正文在 index-abc123.js 运行之后才出现。


二、两波索引:Google 的二次渲染

Google 具备"执行 JavaScript 再渲染一次"的能力,这个过程叫渲染队列。因此 Google 的索引实际分两波:

  1. 第一波(立刻):索引原始 HTML 里能直接读到的内容。
  2. 第二波(延迟):把页面排进渲染队列,用无头浏览器执行 JS,拿到最终 DOM 再更新索引。

关键事实:

依赖 JS 渲染 = 把收录机会交给一个延迟的、不保证的队列。

三、JS 站点的真实风险

风险 说明 后果
内容在 JS 里 正文由 fetch 回来再渲染 除 Google 外全部抓不到
链接是 JS 事件 onclick 而非 <a href> 链接图断裂,发现失败
分页/无限滚动 靠滚动触发加载 深层内容不可达
动态路由 History API 切换,无真实 URL 每个状态无独立 URL
渲染前后不一致 JS 改了标题/加了内容 两波索引内容打架
渲染超时 JS 太重,渲染队列放弃 第二波永远不完成

判定一个站点是否有此风险,只看一件事:关掉 JS 后,正文在不在。


四、自证内容可见(最直接的判据)

# 1) 用 curl 拿源码,检查是否包含正文关键词
curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ | grep -c '本章目标'
# 返回 0 → 正文不在 HTML 里,爬虫抓到的是空壳

# 2) 对比:带 JS 与不带 JS 的可见文本差异(需 headless 环境)
#    关 JS 的渲染结果 ≈ 爬虫第一波拿到的内容

# 3) 检查链接是不是真的 <a href>
curl -s https://www.xxxxxx.cn/learning/ \
  | grep -oE '<a [^>]*href="[^"]+"' | wc -l
# 数量远少于你在浏览器里看到的导航项 → 链接由 JS 生成

三条判据:

检查项 通过标准
正文在初始 HTML curl 源码中含正文文本
链接是 <a href> curl 能提取出站内链接
关键元数据直出 title/description/canonical 在源码中

五、解决方案与代价

方案 做法 代价
预渲染 / SSG(推荐) 构建时为每个路由生成含正文的 HTML 路由需可枚举;构建变慢
SSR 服务端渲染 需要常驻服务;成本高于静态
同构框架 Next/Nuxt 等 架构改造
动态渲染(Dynamic Rendering) 给爬虫返回预渲染版本 已被 Google 标记为过渡方案,维护两套
混合渲染 关键内容直出,交互部分水合 复杂度上升,但最实用

本站采用route shell 预渲染:构建脚本按内容清单为每个课程/章节生成 HTML 外壳,正文直接内联。收益是三重的——爬虫拿到完整文本、首屏直出、LCP 同时受益。

实现要点:预渲染产物的 URL 必须与前端路由逐字符一致,否则会出现两份内容相同、URL 不同的页面,直接制造重复内容(回到第 02 章的 canonical)。


六、混合渲染的实用边界

完全静态有时做不到(比如需要登录态、个性化推荐)。可用的折中:

# 渲染前后一致性自检:源码中的 title 与渲染后的 title 是否一致
curl -s https://www.xxxxxx.cn/learning/ | grep -oiE '<title>[^<]*</title>'

七、故障速查

现象 渲染层原因 处理
curl 源码里没有正文 CSR,内容靠 JS 改预渲染/SSR
Google 收录了但百度没有 JS 渲染,百度不执行 直出正文
首页能抓、内页抓不到 内页链接是 JS 生成 改 <a href>
收录内容与页面不符 两波索引内容不一致 保证渲染前后一致
内容全丢 渲染队列超时 减小 JS 体积,关键内容直出
分页第 N 页不收录 无限滚动无独立 URL 给出可抓取的分页 URL

八、本章验收

# 一条命令的渲染总检
curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ \
  | grep -c -e '本章目标' -e '<title>'

拿到正文之后,它能不能进索引,还取决于下一章索引环节的准入判据。

进入 keel 阅读