不少站点把列表、详情链接和正文都交给 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 也能走通”的结构之后,渲染问题就从阻塞项变成了优化项。剩下的工作才是提升渲染成功率、缩短渲染与重抓之间的间隔。