现在很多页面的结构是脚本拼出来的:导航、列表、卡片、分页,都可能在浏览器执行 JavaScript 之后才出现在 DOM 里。对用户来说没差别,但对搜索蜘蛛来说,同一个地址可能对应两份不同的东西——服务器直接返回的原始 HTML,和脚本跑完之后形成的页面。URL 发现发生在哪一份里,结果差别很大。
蜘蛛先拿到的通常是原始 HTML
抓取往往从一次普通的 HTTP 请求开始。这一步如果返回的 HTML 里没有链接,蜘蛛就没有可跟的路径,只能停在原地。执行脚本、渲染出完整页面一般发生在后面的阶段:资源要下载、脚本要跑完、还要在时间预算内完成。任何一环出问题,链接就可能没有被发现。
- 原始 HTML 里的 a 标签最直接,尤其是带 href 的普通链接。
- 由 JS 插入的链接会被排到渲染环节,不保证和第一次抓取同时完成。
- 渲染本身要抓取脚本文件。如果关键 JS 被 robots.txt 拦掉,或者加载超时、体积过大,链接等于不存在。
- 渲染失败时,蜘蛛拿到的仍是那份没有链接的 HTML,它不会反复重试到你满意为止。
哪些写法容易让链接消失
问题通常不在“用了 JS”,而在于链接从来没有以 URL 的形式出现在任何一份文档里。
- 用 div 或 button 绑定 click 事件做跳转,页面上没有可解析的地址。
- 点击之后才通过 pushState 改写地址,URL 始终没有出现在 HTML 中。
- 链接依赖滚动、hover、懒加载才出现,蜘蛛不一定触发这些交互。
- 内容随登录状态、地域或 A/B 实验变化,同一份 HTML 在不同环境下并不一致。
- 分页只有“加载更多”按钮,没有可抓取的下一页地址。
- 把导航渲染在客户端组件里,而首屏 HTML 只留下一个空的容器节点。
把关键链接放回 HTML 里
渲染是弥补手段,不是唯一出路。需要被发现的层级,最好在原始响应里就存在。
- 主导航、栏目入口、列表页的前若干条,尽量由服务端渲染或预渲染输出成真实的 a 标签。
- 分页保留独立 URL,上一页、下一页用链接表达,而不是纯按钮或无限滚动。
- 把 Sitemap 当作兜底通道,让不依赖内链也能被发现的地址有地方可查。
- 确实必须由 JS 生成的区块,保证脚本可抓取、依赖可控、不依赖用户本地状态。
- 重要入口避免只出现在弹窗、折叠面板或标签页的隐藏分支里。
怎么自己验证一遍
最简单的办法是禁用 JavaScript 打开页面,看看还剩多少可点的链接;再关掉图片和样式,确认 href 是否真实存在而不是运行时才补上。其次可以对照服务器日志里“取 HTML”和“渲染页面”两类请求,观察脚本文件有没有被拦、有没有出现大量超时,以及渲染请求是否集中在少数几个地址上。
也可以用一个不带脚本执行能力的抓取工具跑一遍入口页,把结果和浏览器里的页面结构对比。两边差距越大,说明越多的 URL 发现押在了渲染上。
能被静态 HTML 表达的链接结构,稳定性永远高于依赖脚本生成的同类结构。
边界怎么定
并不是所有链接都要写死在 HTML 里。评论区、推荐位、个性化模块交给 JS 问题不大,它们对 URL 发现的价值本来就有限。真正需要被发现的那一层——栏目、列表、分页、详情入口——最好在原始响应里就能看到。把这个边界划清楚,抓取路径会更可预期,URL 发现也不会因为一次脚本报错而整体停摆。