做 Java 爬虫或自动化测试,定位元素是基本功。今天聊聊 jvppeteer 里那些好用到飞起的选择器 API。


做前端自动化或者写爬虫的朋友,对 Puppeteer 一定不陌生。但如果你的项目是 Java 技术栈,Node.js 那套用不上怎么办?

jvppeteer 就是答案——它是 Puppeteer 的 Java 移植版,API 风格几乎一模一样,Java 开发者无缝上手。

这篇文章,我们聚焦一个核心能力:如何通过选择器精准获取页面元素信息。


一、最基础的两兄弟:$$$

如果你用过 jQuery,这两个方法会让你倍感亲切。

获取单个元素:

ElementHandle el = page.$("#myId");
ElementHandle el = page.$(".my-class");
ElementHandle el = page.$("div.container > p:first-child");

获取多个元素:

List<ElementHandle> items = page.$$("li.item");
for (ElementHandle item : items) {
    // 逐个处理
}

💡 小贴士: $ 返回的是 ElementHandle,它是元素的"句柄",不是元素本身。要读取内容,还需要进一步操作。


二、拿到元素后,怎么读信息?

这才是重头戏。jvppeteer 提供了三种主流方式,各有适用场景。

方式一:getProperty + jsonValue

最"底层"的方式,直接取 DOM 属性:

ElementHandle el = page.$("h1.title");

String text = (String) el.getProperty("textContent").jsonValue();
String href = (String) el.getProperty("href").jsonValue();

适合场景: 你已经拿到了 ElementHandle,需要读取它的某个属性。


方式二:page.$eval — 一行搞定

最优雅的方式,在页面上下文里执行函数:

// 取文本
String text = (String) page.$eval("h1.title", "el => el.textContent");

// 取链接
String href = (String) page.$eval("a.link", "el => el.getAttribute('href')");

// 取输入框的值
String value = (String) page.$eval("#username", "el => el.value");

适合场景: 你只关心一个元素的某个值,不想多写一行代码。


方式三:page.$$eval — 批量收割

当你需要一次性拿到所有匹配元素的数据时:

// 获取所有标题文本
List<String> titles = (List<String>) page.$$eval(
    "li.item",
    "els => els.map(el => el.textContent)"
);

// 获取所有链接
List<String> links = (List<String>) page.$$eval(
    "a.nav-link",
    "els => els.map(el => el.getAttribute('href'))"
);

适合场景: 列表页、表格数据采集,批量操作效率拉满。


三、几个高频实战场景

判断元素是否存在

ElementHandle el = page.$(".maybe-exists");
if (el != null) {
    System.out.println("找到了");
} else {
    System.out.println("没有这个元素");
}

获取元素位置和尺寸

做截图标注、模拟点击时经常用到:

ElementHandle el = page.$("#target");
BoundingBox box = el.boundingBox();

double x = box.getX();
double y = box.getY();
double width = box.getWidth();
double height = box.getHeight();

等待懒加载元素出现

实际项目中,元素可能异步加载。直接获取会返回 null,必须先等:

page.waitForSelector("div.lazy-loaded", new WaitForSelectorOptions()
    .setVisible(true)
    .setTimeout(10000)  // 最多等 10 秒
);

String text = (String) page.$eval("div.lazy-loaded", "el => el.textContent");

⚠️ 踩坑提醒: 生产环境里,waitForSelector 基本是必选项,别省。


四、在 iframe 里操作元素

现代网页里 iframe 很常见(广告、嵌入内容等)。jvppeteer 处理起来也很简单:

// 切换到 iframe
Frame frame = page.frames().get(1);
// 或按 URL 等待
Frame frame = page.waitForFrame("https://example.com/iframe");

// 然后用同样的 API
String text = (String) frame.$eval(".inside-iframe", "el => el.textContent");

五、完整示例:抓取 Hacker News 热榜

把上面的知识串起来,看一个完整例子:

import com.ruiyun.jvppeteer.core.Puppeteer;
import com.ruiyun.jvppeteer.core.browser.Browser;
import com.ruiyun.jvppeteer.core.page.ElementHandle;
import com.ruiyun.jvppeteer.core.page.Page;
import com.ruiyun.jvppeteer.options.LaunchOptions;
import com.ruiyun.jvppeteer.options.LaunchOptionsBuilder;

import java.util.Arrays;
import java.util.List;

public class SelectorDemo {
    public static void main(String[] args) throws Exception {
        LaunchOptions options = new LaunchOptionsBuilder()
                .withArgs(Arrays.asList("--no-sandbox", "--disable-setuid-sandbox"))
                .withHeadless(true)
                .build();

        Browser browser = Puppeteer.launch(options);
        Page page = browser.newPage();
        page.goTo("https://news.ycombinator.com");

        // 获取所有标题
        List<String> titles = (List<String>) page.$$eval(
            ".titleline > a",
            "els => els.map(el => el.textContent)"
        );
        titles.forEach(System.out::println);

        // 获取第一个链接
        String firstLink = (String) page.$eval(
            ".titleline > a",
            "el => el.getAttribute('href')"
        );
        System.out.println("First link: " + firstLink);

        page.close();
        browser.close();
    }
}

速查表

收藏这张表,忘了随时翻:

你想做什么用这个方法
拿单个元素句柄page.$("selector")
拿所有匹配元素page.$$("selector")
读文本内容page.$eval("sel", "el => el.textContent")
读属性值page.$eval("sel", "el => el.getAttribute('xxx')")
批量取值page.$$eval("sel", "els => els.map(...)")
等元素出现page.waitForSelector("sel")
元素位置尺寸element.boundingBox()

写在最后

jvppeteer 的选择器 API 设计得很直觉,基本上你会 CSS 选择器就能上手。核心就三个方法:$$eval$$eval,覆盖了绝大多数场景。

记住一个原则:简单场景用 $eval,复杂交互拿 ElementHandle,异步页面先 waitForSelector


你在用 jvppeteer 时踩过什么坑?评论区聊聊👇


觉得有用?点个「在看」支持一下~

Q.E.D.


寻门而入,破门而出