
本文详解 Puppeteer 中 $$() 与 evaluate() 的协同用法,指出常见 selector 匹配失败和仅返回首项的典型原因,并提供性能优化方案(如资源拦截、批量 DOM 提取),助你稳定抓取多元素文本内容。
本文详解 puppeteer 中 `$$()` 与 `evaluate()` 的协同用法,指出常见 selector 匹配失败和仅返回首项的典型原因,并提供性能优化方案(如资源拦截、批量 dom 提取),助你稳定抓取多元素文本内容。
在使用 Puppeteer 进行网页数据提取时,一个高频痛点是:明明 selector 看似正确,却只获取到第一个元素的值,或遍历时抛出 null 错误。根本原因往往不是语法错误,而是选择器精度不足、DOM 加载时机不当,或 page.evaluate() 在循环中低效调用导致上下文不稳定。
以目标站点 https://www.superanimes.biz/lista-de-animes 为例,原始代码中使用:
const productsHandles = await page.$$("#containerVideos > section > section > div.post_cat.notranslate");该选择器存在两个关键问题:
-
层级过于刚性:
#containerVideos > section > section > ...强依赖固定嵌套深度,而实际 HTML 中<section></section>可能被动态插入或存在冗余包装层; -
未过滤无效节点:
.post_cat.notranslate下可能包含无div.title_cat > span子元素的占位节点,导致querySelector()返回null,进而使textContent报错(虽被try/catch静默吞掉,但循环实际中断)。
✅ 推荐写法:聚焦语义化、可复用的选择器,并确保目标子元素存在:
// ✅ 更鲁棒的选择器:定位到每个视频卡片容器(.video_poster)
const selector = '#containerVideos > section > section > .post_cat.notranslate > .video_poster';
await page.waitForSelector(selector, { visible: true }); // 确保元素可见后再操作
const productHandles = await page.$$(selector);
for (const el of productHandles) {
const title = await page.evaluate(node => {
const span = node.querySelector('a > div.title_cat > span');
return span ? span.textContent.trim() : null;
}, el);
if (title) console.log(title);
}⚠️ 更重要的是性能与健壮性升级:避免在循环中频繁跨进程通信(即多次调用 page.evaluate())。Puppeteer 的 evaluate() 每次都会序列化/反序列化数据,大量调用会显著拖慢速度并增加内存压力。
? 最优实践:一次性在浏览器上下文中完成全部提取
利用 page.evaluate() 执行完整 DOM 遍历,返回结构化数据数组,彻底规避循环开销:
const products = await page.evaluate(selector => {
return Array.from(document.querySelectorAll(selector)).map(el => {
const titleEl = el.querySelector('a > div.title_cat > span');
const linkEl = el.querySelector('a');
return {
title: titleEl?.textContent?.trim() || '',
href: linkEl?.getAttribute('href') || ''
};
});
}, selector);
products.forEach(p => console.log(p.title)); // 安全、高效、可扩展? 进阶优化:通过请求拦截减少干扰
动画、图片、字体等非必要资源会拖慢页面加载和 waitForSelector 响应。添加资源拦截可提速 30%+:
await page.setRequestInterception(true);
page.on('request', req => {
['image', 'font', 'media'].includes(req.resourceType())
? req.abort()
: req.continue();
});? 总结要点:
- 选择器优先用类名 + 语义容器(如
.video_poster),少用深层>直接子代; - 务必
waitForSelector(..., { visible: true })确保元素渲染完成; -
page.evaluate()应尽量“批量处理”,避免循环内调用; - 使用可选链
?.和空值判断提升容错性; - 合理拦截非关键资源,提升整体稳定性与执行效率。

















