元素操作基础
课程目标
- ✅ 掌握元素定位的多种方式
- ✅ 提取元素属性与文本内容
- ✅ 实现百度热搜实时抓取
- ✅ 理解DOM操作的安全边界
元素定位方法论
1. 选择器类型对比
类型 | 示例 | 适用场景 |
---|---|---|
CSS选择器 | #main .title | 常规静态元素定位 |
XPath | //div[@class="hot"] | 复杂层级关系定位 |
Text选择器 | text/="热搜" | 模糊文本匹配(需启用实验功能) |
2. 核心API详解
// 单元素查询(返回ElementHandle)
const element = await page.$('selector')
// 多元素查询(返回数组)
const elements = await page.$$('.list-item')
// XPath定位
const xpathElement = await page.$x('//div[contains(@class, "hot")]')
// 结合eval的快捷方式
const textContent = await page.$eval('#title', el => el.textContent)
实战:百度热搜抓取
步骤分解
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
// 导航到百度热搜页
await page.goto('https://top.baidu.com/board', {
waitUntil: 'networkidle2'
});
// 等待目标元素加载
await page.waitForSelector('.c-single-text-ellipsis', {
timeout: 5000
});
// 提取热搜文本
const hotSearchList = await page.$$eval('.c-single-text-ellipsis',
elements => elements.map(el => ({
title: el.textContent.trim(),
link: el.href
}))
);
console.log('实时热搜榜:', JSON.stringify(hotSearchList, null, 2));
// 保存数据到文件
const fs = require('fs');
fs.writeFileSync('./data/hot-search.json', JSON.stringify(hotSearchList));
} catch (err) {
console.error('抓取失败:', err);
} finally {
await browser.close();
}
})();
关键技术点解析
- waitForSelector:确保目标元素加载完成
- $$eval:批量处理元素集合
- DOM环境隔离:注意无法直接访问外部变量
元素操作安全指南
1. 防御性编程实践
// 使用try-catch处理元素不存在情况
try {
const element = await page.waitForSelector('#not-exist', { timeout: 3000 });
} catch (err) {
console.log('元素未找到,执行备用方案');
}
// 元素存在性检测
const isExist = await page.$('selector') !== null;
2. 内存管理
// 及时释放元素句柄
const element = await page.$('#large-element');
await element.dispose(); // 显式释放内存
// 避免循环内累积句柄
for (const item of list) {
const el = await page.$(item.selector);
// 处理元素...
await el.dispose();
}
常见问题排查
❌ Element is not attached to the DOM
-
原因:元素被动态移除
-
解决方案:使用
page.waitForFunction
检测元素稳定性await page.waitForFunction( selector => document.querySelector(selector).offsetParent !== null, {}, '#dynamic-element' );
❌ Execution context was destroyed
-
原因:页面跳转后访问旧元素
-
解决方案:在回调中重新获取元素
page.on('framenavigated', async () => { currentElement = await page.$('#refresh-element'); });
❌ Node is either not visible or not an HTMLElement
-
原因:元素不可交互
-
解决方案:滚动到可视区域
await element.scrollIntoView(); await element.click();
课后任务
- 抓取知乎热榜前10话题的标题与关注数
- 实现微博热搜的自动翻页抓取
- 设计元素超时重试机制(最多3次)
🛠️ 高级挑战:尝试用XPath重写百度热搜抓取逻辑
下节预告:表单交互与截图 - 学习自动化填写表单与页面快照技巧 📸