Puppeteer可解决动态网页抓取难题,它启动真实Chromium实例,等待JS执行完毕后获取完整渲染DOM;而curl或“查看页面源代码”仅获初始HTML,无法获取SPA中JavaScript动态生成的内容。

用开发者工具 Elements 面板复制 outerHTML
直接右键“查看页面源代码”拿到的是原始 HTML,不含 JS 执行后插入、修改或删除的内容。真正渲染后的结构只存在于浏览器内存中,得从 Elements 面板取。
打开 DevTools(F12 或 Ctrl+Shift+I),切到 Elements 标签页,找到最外层的 <html> 标签,右键 → Copy → Copy outerHTML。粘贴进编辑器保存为 .html 文件即可。
- 注意:如果页面有懒加载或滚动触发动态内容,得先手动滚动到底部、触发所有 JS 加载逻辑,再复制
- 复制前可按
Ctrl+F搜关键词,确认目标内容已出现在Elements中,否则说明还没渲染出来 - 某些单页应用(SPA)会把路由状态写在
history.state里,这部分不会出现在 HTML 中,outerHTML也带不走
用 Puppeteer 无头浏览器自动抓取完整渲染结果
手动操作不可靠,尤其要批量保存、定时抓取或处理登录后页面时,必须用能执行 JS 的环境。Puppeteer 是目前最稳定的选择,它启动真实 Chromium 实例,等页面完全加载、JS 执行完毕后再提取 DOM。
示例脚本(需提前 npm install puppeteer):
立即学习“Java免费学习笔记(深入)”;
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const html = await page.content(); // 获取最终渲染后的完整 HTML 字符串
require('fs').writeFileSync('rendered.html', html, 'utf8');
await browser.close();
})();
-
waitUntil: 'networkidle2'表示等待最后两个网络请求结束,比domcontentloaded更稳妥 - 若页面需要登录,可用
page.login()或注入 cookies,但要注意 session 过期问题 - 默认不执行
eval或内联脚本中的document.write,如有这类逻辑,得额外加page.evaluate处理
为什么不能用 fetch 或 curl 直接替代
curl https://example.com > raw.html 拿到的只是服务器返回的初始 HTML,里面往往只有 <div id="root"></div> 和一个 <script src="bundle.js">。所有实际内容都由 JS 在浏览器里动态生成,curl 完全看不到。
- HTTP 工具只管发请求、收响应,不解析 HTML,更不执行 JS
- 即使页面用了 SSR(服务端渲染),也可能在客户端又被 JS 覆盖重写,最终 DOM ≠ 初始 HTML
- 遇到 CSP(Content-Security-Policy)限制内联脚本或 eval 的站点,Puppeteer 默认行为可能失败,需加
ignoreHTTPSErrors: true或调整 launch 参数
保存后离线打开可能样式错乱?检查这三点
用 outerHTML 或 Puppeteer 保存的 HTML 是“快照”,但不是完整项目——外部资源路径没变,本地打不开很正常。
- 打开保存的文件,按
F12看 Console 是否有大量404:说明href="/css/app.css"这类绝对路径在本地找不到 - 把相对路径改成绝对 URL(如
https://example.com/css/app.css)再下载对应文件,或干脆用 Puppeteer 的page.pdf()导出为 PDF 规避资源依赖 - 若 HTML 里有内联
<style>或<script>,它们会被完整保留;但外部 CSS/JS 文件不会自动下载,得单独处理
visibilitychange 或定时器,在你复制的瞬间才触发关键逻辑。真要 100% 可靠,得结合 page.waitForFunction 等待特定 DOM 节点出现,而不是盲目等时间或网络空闲。



















