打开开发者工具,页面上是一排排可以点的卡片和分页按钮;查看网页源代码,这些链接却一个都找不到。对用户来说没差别,对蜘蛛来说,这是两条不同的路。
蜘蛛先看源码,再决定要不要渲染
抓取大体分两步:第一步下载 HTML 并解析,第二步在需要时执行脚本、渲染页面。第一步便宜且稳定,第二步更贵,也更不确定。
- 写在 a 标签 href 属性里的地址,第一步就能进入待抓取队列。
- 只在脚本执行后才出现的链接,要等渲染完成才可能被发现,而渲染并不是每次都会发生。
所以问题往往不是“蜘蛛不抓 JS 页面”,而是链接从源码里消失了,URL 发现少了一条稳定入口。
链接“消失”的几种常见写法
- 用脚本跳转代替链接:元素上只绑了点击事件,跳转由脚本里的地址赋值完成。没有 href,第一步就看不到目标地址。
- 前端路由:单页应用通过 history 接口改写地址栏,源码里的列表项没有 href。
- 数据来自接口:列表由接口返回的 JSON 渲染,目标地址也写在 JSON 里。
- 外面套了容器:内容放在 iframe 里,源码中看不到内部链接。
- 只在交互后加载:点击“展开更多”才把下一批链接插入页面。
这些写法本身没有错,问题在于它们把 URL 发现完全押在渲染这一步上。
让链接回到源码里:几个层级的做法
首选:服务端渲染或静态生成
列表页、详情页、分页导航在服务端就把 a 标签和 href 输出到 HTML 里,蜘蛛第一步就能拿到。分页用真实的下一页链接,而不是一个按钮。这样即使后续脚本出错,抓取路径依然完整。
次选:预渲染与动态渲染
对已知的抓取 UA 返回渲染后的 HTML,是可行但需要持续维护的方案。要留意几点:
- 渲染后的 HTML 与普通用户看到的应保持一致,不要返回明显不同的内容。
- 中间若有缓存层,缓存键要能区分不同版本,避免把空壳页缓存给蜘蛛,或把渲染版塞给普通用户。
- 渲染服务要纳入监控,长时间超时或报错会让蜘蛛拿到不完整的页面。
兜底:Sitemap 与静态内链
如果短期内改不了前端,至少把 URL 清单交给 Sitemap,并在页脚、主导航、面包屑这类静态输出的位置补上通往重要页面的链接。这样即便渲染环节失败,蜘蛛仍有别的入口到达这些地址。
渲染服务不稳时会发生什么
渲染依赖服务端资源,出问题时常见两种表现:
- 返回 200,但页面是空壳。蜘蛛拿到一个几乎没有正文和链接的 200 页面,容易影响它对这批 URL 的后续抓取意愿。
- 超时或返回 5xx。蜘蛛会稍后重试,短期内这片路径的发现能力下降。
因此,渲染失败率、平均耗时、空内容比例都值得进监控,而不是只盯着首页能不能打开。
抓取路径断掉时,站点通常不会报错,只是新页面迟迟不出现。检查源码,比反复提交 Sitemap 更直接。
怎么自查
- 用命令行请求页面,看返回的 HTML 里有没有目标链接。
- 浏览器禁用脚本后再看一遍,确认主导航和列表仍能走通。
- 在服务器日志里筛蜘蛛的请求路径,看它是否只停在首页和少数入口页。
- 对比源码版本与渲染版本,确认链接是否只在渲染后才出现。
小结
蜘蛛走的是它拿到的那份 HTML。把关键链接写回源码,是让抓取路径稳定的省事做法;前端渲染并非不能用,但要清楚它把 URL 发现的责任从 HTML 转移到了渲染服务上,而这一步更容易出错。