做网站的人,多少都碰到过这种怪事:浏览器里打开明明完整无缺,标题、正文、配图、数据全渲染好了,可一看搜索引擎的抓取结果,只剩一个白底空壳。原因也不复杂——站点的HTML里只有框架,所有内容全靠JS动态加载,而爬虫在拿到原始HTML那一步就把你的页面“看完”了,根本不会等你的JS跑完。今天这篇,我就结合宝塔面板,聊一套在“HTML静态+JS动态加载”网站上实现24小时自动静态预渲染的完整方案。做完之后,爬虫拿到的是一份已经渲染好的完整HTML,用户访问速度也能顺带提升一个档次。
这套方案适合谁?自己用宝塔面板搭了个人站、内容站、导航站,页面数据靠JS从接口拉的朋友;想让文章页和列表页被搜索引擎更快收录的朋友;以及不想为了SEO去重写一套SSR、只想用最小成本解决问题的朋友。内容不涉及任何花里胡哨的框架改造,就是把预渲染脚本、定时任务、Nginx路由这三块拼起来,我尽量把每一个细节和坑都写清楚。
1. 为什么HTML+JS动态加载的网站在爬虫眼里是“空壳”
1.1 服务器吐出来的原始HTML,和你看到的根本不是同一份
很多人对“HTML静态网站”有个误解:以为文件是静态的,页面就一定是静态的。实际上,很多用原生JS或Vue、React这类框架写的页面,服务器上存放的HTML只是一个“壳子”。比如下面这份代码,就是很典型的动态加载结构:
html复制<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>我的资讯站</title>
</head>
<body>
<div id="app"></div>
<script src="/js/main.js"></script>
</body>
</html>
浏览器打开没问题,是因为浏览器把main.js完整执行了一遍:fetch接口、拼接DOM、绑定事件,最终把内容填进#app里。但搜索引擎爬虫拿到的是哪个版本?正是上面这份只有空div的骨架。爬虫在“抓取HTML”这一步就把页面内容定了性,后面你的JS跑得多欢快,它根本不在乎。
这就是“动态加载网站”做SEO最尴尬的地方:你提供的内容是真材实料的,可爬虫拿到的白纸一张。很多站点反馈“只收录了首页,内页一个也不进”,多半就是这个原因。
1.2 各家搜索引擎对JS渲染的真实态度
搜索引擎不是没有渲染JS的能力,但“能渲染”和“愿意为每个页面都花这个成本”是两码事。我根据自己的日常观察,把主流搜索引擎的JS处理能力整理成了一张表:
| 搜索引擎 | 对JS渲染的支持 | 现实中的表现 |
|---|---|---|
| 支持较好,但需要进入渲染队列 | 新页面经常要等几天甚至更久才收录,核心页面收录相对快 | |
| 百度 | 支持不稳定 | 很多站点只有首页被收录,内页URL长期不进索引 |
| Bing | 支持一般 | 依赖外链和站点地图,动态页面收录波动大 |
| 搜狗/360等 | 相对保守 | 对纯JS渲染的页面基本是“看到什么记录什么” |
你可能会说:Google不是早就支持JS渲染了吗?支持归支持,它给每个页面分配的渲染预算有限。我测试下来,一个完全靠JS渲染内容的新页面,从提交到被真正执行JS、再到索引,周期可能比纯静态HTML长一倍以上。对于小网站来说,这种延迟非常致命——内容明明发了,搜索引擎就是不收录。
1.3 预渲染、SSR、CSR到底有什么区别
要理解预渲染的定位,得把几种渲染方式放在一起看:
- CSR(客户端渲染):就是你现在网站的运行方式。服务器只给空壳,浏览器跑JS填充内容。优点是对服务器压力小、开发灵活;缺点是SEO不友好,首屏也受JS加载速度影响。
- SSR(服务端渲染):每次用户请求页面,服务器都实时执行一次渲染,再把完整HTML返回。SEO很好,但对服务器性能要求很高,而且需要改造项目结构,工作量大。
- 预渲染(Prerender):在构建阶段或定时任务阶段,提前把页面渲染成完整HTML,保存为静态文件。之后不管是爬虫还是用户,拿到的都是这份现成的HTML。它不需要改造站点代码,又解决了SEO问题,对“内容更新频率不高”的站点来说非常合适。
导航站、文章站、企业站、资源列表站,这些站的内容一天更新几次甚至几天更新一次,完全可以用“每半小时或每小时预渲染一遍”的方式来覆盖。既享受了静态HTML的秒开速度,又不丢SEO,还不需要动现有的JS代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方案选型:为什么Puppeteer绕不开,其他方案差在哪
2.1 为什么不用PhantomJS
早些年大家做预渲染,首选是PhantomJS。它当年确实是唯一一个能“在服务器上无头运行、拿到渲染后DOM”的工具。但问题也很明显:PhantomJS已经停止更新很多年了,内置的WebKit内核版本非常老,对ES6语法、async/await、Fetch API、新版CSS特性的支持都很差。你现在站点里的JS只要用到一个fetch或者箭头函数,扔进PhantomJS里可能就直接白屏。
Puppeteer刚出来的时候,我就把身边几个站点的预渲染脚本从PhantomJS迁了过去。怎么说呢,就像把一台十年的老爷车换成现在的自动挡——同一个操作,体验天差地别。
2.2 Puppeteer到底干了什么
Puppeteer是Chrome团队推出的Node.js库,它的本质是:在服务器上启动一个“无头Chrome”实例,然后通过Chrome DevTools Protocol协议,用代码控制这个浏览器。
拿预渲染来说,整个过程是:
puppeteer.launch()启动一个无头浏览器;browser.newPage()打开一个新标签页;page.goto(url)访问你的目标页面,这一步会真实执行页面里的所有JS;- 等到网络请求基本停止后,调用
page.content()拿到当前页面的完整HTML; - 把这个HTML写进静态文件。
因为执行这段渲染的就是真正的Chrome内核,所以它得到的DOM和你自己在浏览器里右键“查看源代码”几乎完全一致。这是PhantomJS那种老方案给不了的保真度。
2.3 在宝塔面板跑无头浏览器,需要什么基础条件
Puppeteer虽然好用,但它毕竟是启动了一个完整的Chrome,所以对服务器环境有要求,主要是两点:
- 需要有Node.js运行环境。在宝塔面板里,可以通过软件商店安装PM2管理器或Node.js版本管理器,也可以用命令行自行安装,后面我会讲到。
- 无头Chrome需要一些Linux系统依赖库。常见的是libnss3、libatk-1.0、libatk-bridge2.0、libcups、libdrm、libxkbcommon等。在CentOS和Ubuntu上,装依赖的方式略有不同,但绝大多数情况下,只要系统是干净的,缺哪个补哪个就行。
有一点要注意:无头Chrome默认在沙箱模式下
