导读:本期聚焦于小师妹创作的《Spring Boot 整合 Puppeteer 实现动态渲染页面抓取的完整方案怎么做?》,敬请观看详情。传统的 HttpUtils 或 Jsoup 只能拿到静态 HTML,遇到前端框架渲染的单页应用就无能为力了。Puppeteer 通过控制 Headless Chrome 浏览器执行页面上的 JavaScript,可以拿到渲染完成后的完整 DOM。本文介绍如何在 Spring Boot 项目中通过 Java 版 Puppeteer 客户端驱动无头浏览器,涵盖环境准备、Maven 依赖引入、页面加载与等待策略、截图与 HTML 提取、并发控制与浏览器生命周期管理等核心内容,同时分析内存占用过高、进程残留等常见问题的解决办法,帮助你搭建一套稳定可用的动态页面抓取服务。

抓取静态页面很简单,用 HttpClient 发个请求、Jsoup 解析一下 HTML 就完事了。但现在越来越多的网站是 Vue、React 这类前端框架渲染的单页应用,接口返回的 HTML 只有一个空的 <div id="app"> 占位节点,真正的数据要等浏览器执行完 JavaScript 之后才会出现。这种页面想靠纯 HTTP 请求抓取,基本走不通。解决办法是让程序真正驱动一个浏览器去渲染页面,等 DOM 渲染完成后再提取内容,Puppeteer 就是干这件事的利器。

Spring Boot 整合 Puppeteer 实现动态渲染页面抓取的完整方案怎么做?

为什么选择 Puppeteer 而不是 Selenium

Puppeteer 是 Google 推出的 Node.js 库,直接基于 Chrome DevTools Protocol(CDP)与 Chrome 通信,省去了 WebDriver 这一中间层,启动速度快、API 简洁,对 Headless 模式的支持也是最原生的。相比之下,Selenium 需要单独维护 chromedriver,且驱动版本必须和浏览器版本严格匹配,版本一旦错位就会报 SessionNotCreatedException,运维成本明显更高。

在 Java 技术栈下使用 Puppeteer 有两条路:一是通过 Java 进程调用 Node.js 脚本,两个进程之间用消息队列或 HTTP 通信;二是直接使用 Java 版的 Puppeteer 客户端,比如社区维护的 puppeteer-java 项目,它实现了 CDP 协议的大部分能力,API 风格与官方 Node 版几乎一致。对于 Spring Boot 项目来说,第二种方式集成成本最低,不需要额外部署 Node 环境,本文以 puppeteer-java 为例讲解。

还有一点值得注意:Puppeteer 控制的是真实的 Chromium 内核,所以它抓到的页面和用户在浏览器里看到的一模一样,包括懒加载的图片、异步请求填充的数据,甚至可以绕过一部分基于 Cookie 或指纹的前置校验。这也是它在动态渲染抓取场景中流行的根本原因。

环境准备与依赖引入

首先确认服务器环境。Headless Chrome 对内存有要求,建议单实例至少分配 512MB,如果并发开 5 个页面,2GB 内存是起步配置。CentOS 或 Ubuntu 服务器需要安装 Chromium 的依赖库,最省事的方式是直接下载官方的 Chrome Headless Shell,解压即用,不依赖图形界面。

在 Spring Boot 项目的 pom.xml 中加入依赖:

<dependency>
    <groupId>com.ruiyun</groupId>
    <artifactId>jvppeteer</artifactId>
    <version>1.1.5</version>
</dependency>

jvppeteer 是 puppeteer-java 生态中维护较活跃的一个实现,支持页面导航、元素选择、截图、请求拦截、PDF 导出等核心能力。如果没有指定 executablePath,它会在首次启动时自动下载对应平台的 Chromium,生产环境建议提前下载好浏览器并显式指定路径,避免部署时联网下载失败。

浏览器启动参数也很关键。生产服务器通常没有沙箱权限,需要加上 --no-sandbox;为了降低资源占用,可以加上 --disable-gpu--disable-dev-shm-usage,后者能避免容器环境下因 /dev/shm 太小导致的浏览器崩溃。示例代码如下:

ArrayList<String> args = new ArrayList<>();
args.add("--no-sandbox");
args.add("--disable-gpu");
args.add("--disable-dev-shm-usage");
LaunchOptions options = new LaunchOptions.Builder()
        .args(args)
        .executablePath("/opt/chrome/chrome-headless-shell")
        .headless(true)
        .build();
Browser browser = Puppeteer.launch(options);

核心抓取逻辑的实现

浏览器启动后,抓取流程一般是:新建页面、设置视口与 UA、导航到目标 URL、等待渲染完成、提取 HTML 或特定元素文本。其中最容易被忽视的是等待策略。页面 load 事件触发不代表渲染完成,SPA 应用往往在 load 之后才发起数据请求。稳妥的做法是组合使用 waitUntil 选项和 waitForSelector,即等待某个必然出现的业务元素渲染出来后再提取内容。

Page page = browser.newPage();
page.setViewport(1280, 800);
page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");

PageNavigateOptions navOptions = new PageNavigateOptions.Builder()
        .setWaitUntil(Collections.singletonList("networkidle2"))
        .setTimeout(30000)
        .build();
page.goTo("https://ippipp.com/list", navOptions);

// 等待列表容器渲染完成,最多等 10 秒
page.waitForSelector(".data-list .item", 10000);

String html = page.content();
System.out.println("抓取到的 HTML 长度: " + html.length());

networkidle2 表示网络连接数降到 2 个以下且持续 500ms,比 load 更适合动态页面。如果目标数据依赖滚动触发,还需要在抓取前执行 page.evaluate 模拟滚动到底部,把懒加载内容逼出来。需要截图留证的场景,调用 page.screenshot 即可,支持全页截图和指定区域截图。

在 Spring Boot 中,建议把 Browser 实例封装成单例 Bean,由容器管理生命周期,应用关闭时在 @PreDestroy 中调用 browser.close(),避免浏览器进程残留。每次抓取任务复用 Browser 但新建 Page,用完立即 page.close(),这样既节省了启动浏览器的开销,又不会让页面上下文互相污染。

并发控制与常见坑

并发是这类服务最容易出问题的地方。一个 Browser 实例可以开多个 Page,但 Page 数量要严格限制,建议用信号量或线程池把并发页面数控制在 CPU 核数的 1 到 2 倍以内。超过限制后,Chrome 的渲染进程会互相争抢内存,表现为抓取耗时飙升甚至 OOM。更彻底的做法是部署多个独立的服务实例,每个实例跑一个 Browser,前面用负载均衡分发任务。

@Service
public class CrawlerService {

    private final Semaphore semaphore = new Semaphore(5); // 最多 5 个并发页面

    public String crawl(String url) throws InterruptedException {
        semaphore.acquire();
        Page page = null;
        try {
            page = BrowserHolder.getBrowser().newPage();
            page.goTo(url);
            page.waitForSelector(".content", 10000);
            return page.content();
        } catch (Exception e) {
            log.error("抓取失败: {}", url, e);
            return null;
        } finally {
            if (page != null) {
                page.close();
            }
            semaphore.release();
        }
    }
}

几个高频踩坑点总结一下。一是进程残留:浏览器进程异常退出后可能留下僵尸进程,定时执行 ps aux | grep chrome 检查,必要时用 pid 文件强杀;二是内存持续上涨:长时间运行的 Browser 会有内存泄漏倾向,可以设置定时任务每几小时重启一次浏览器实例;三是目标站点反爬:频繁请求会触发验证码,合理设置请求间隔、轮换 UA 和代理 IP 是基本操作,同时尽量拦截图片、字体等静态资源请求,能显著降低加载时间,Puppeteer 的请求拦截 API 通过 page.setRequestInterception 开启后可以按资源类型直接 abort 掉媒体请求。

整体来看,Spring Boot 整合 Puppeteer 的方案结构清晰:jvppeteer 负责驱动浏览器,Spring 容器管理 Browser 生命周期,信号量控制并发,再加上资源拦截和定期重启兜底稳定性。这套架构跑每天几万次的动态页面抓取任务没有压力,如果规模再往上走,可以考虑把抓取逻辑抽成独立的 Node.js 微服务,Java 侧只做任务调度,各取所长。

Spring BootPuppeteer动态渲染抓取修改时间:2026-09-07 20:40:42

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260907/52433.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。