
本文详解 Puppeteer 中 page.$$() 与 page.evaluate() 的配合要点,解决因选择器不精确或 DOM 结构理解偏差导致“只提取首个元素”的常见问题,并提供性能更优的批量提取方案。
本文详解 puppeteer 中 `page.$$()` 与 `page.evaluate()` 的配合要点,解决因选择器不精确或 dom 结构理解偏差导致“只提取首个元素”的常见问题,并提供性能更优的批量提取方案。
在使用 Puppeteer 进行网页数据抓取时,一个高频陷阱是:看似正确的 CSS 选择器,却只能获取到第一个匹配项。这通常并非代码语法错误,而是源于对页面实际 DOM 结构、元素可见性、选择器层级或执行上下文的理解偏差。
以你的代码为例:
const productsHandles = await page.$$("#containerVideos > section > section > div.post_cat.notranslate");
该选择器意图匹配所有 .post_cat.notranslate 元素,但实际页面中,这些元素内部可能嵌套了多个 .video_poster(即真正承载标题的容器)。而你后续在 page.evaluate() 中使用的子选择器:
el.querySelector('div.title_cat > span')
若 el 指向的是外层 .post_cat.notranslate,而 div.title_cat 并非其直接子元素(中间隔着 .video_poster),则查询会失败——此时 textContent 可能为 undefined,或因异常被静默吞掉(catch 中未打印错误),最终仅第一个“侥幸成功”的元素被输出。
✅ 正确做法是:精准定位可迭代的原子单元
应将 productsHandles 指向最内层、包含完整目标信息的重复块级容器。观察目标页结构可知,每个视频条目由 .video_poster 包裹,其内部才包含 <a><div class="title_cat"><span>...</span></div></a>。因此主选择器应修正为:
const selector = '#containerVideos > section > section > .post_cat.notranslate > .video_poster';
同时确保等待该选择器可见,避免因异步加载导致元素尚未渲染:
await page.waitForSelector(selector, { visible: true });
? 进阶优化:减少跨进程通信开销
频繁调用 page.evaluate()(尤其在循环中)会引发大量浏览器 ↔ Node.js 上下文切换,显著拖慢性能。更高效的方式是一次性在浏览器端完成全部提取:
const products = await page.evaluate((selector) => {
const items = document.querySelectorAll(selector);
return Array.from(items).map(item => ({
title: item.querySelector('a > div.title_cat > span')?.textContent?.trim() || '',
link: item.querySelector('a')?.getAttribute('href') || ''
}));
}, selector);
此方案优势明显:
- 仅 1 次
evaluate()调用,大幅降低 IPC 开销; - 使用
?.链式可选操作符增强健壮性,避免因个别元素缺失子节点而报错; -
Array.from()确保兼容性,trim()清理多余空白。
? 额外建议:提升稳定性与效率
-
拦截非必要资源:通过
setRequestInterception屏蔽图片、字体、媒体等,加速页面加载与解析; -
显式错误处理:
catch块中务必console.error(error),便于快速定位选择器失效或网络问题; -
关闭浏览器:生产环境切勿遗漏
await browser.close(),防止僵尸进程。
最终精简可靠的实现如下:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
userDataDir: './tmp'
});
try {
const page = await browser.newPage();
// 拦截非关键资源加速加载
await page.setRequestInterception(true);
page.on('request', req =>
['image', 'font', 'media'].includes(req.resourceType())
? req.abort()
: req.continue()
);
await page.goto('https://www.superanimes.biz/lista-de-animes', { waitUntil: 'networkidle0' });
await page.waitForSelector('#containerVideos > section > section > .post_cat.notranslate > .video_poster', { visible: true });
const titles = await page.evaluate(() =>
Array.from(document.querySelectorAll('#containerVideos > section > section > .post_cat.notranslate > .video_poster'))
.map(el => el.querySelector('a > div.title_cat > span')?.textContent?.trim())
.filter(Boolean) // 移除空值
);
titles.forEach((title, i) => console.log(`[${i + 1}] ${title}`));
} catch (err) {
console.error('抓取失败:', err);
} finally {
await browser.close();
}
})();
掌握选择器的“语义精度”与 evaluate 的“执行粒度”,是 Puppeteer 稳健抓取的核心能力。始终以浏览器开发者工具(Elements 面板)实时验证选择器有效性,比依赖静态 HTML 更可靠。










