如何不使用slowMo获取动态页面数据?Steam爬虫问题排查
Puppeteer获取Steam游戏数据的问题解决
问题场景
编写了一段用于获取Steam游戏标题和价格的Puppeteer代码:
async function handleAgeRestriction(p) { await p.evaluate(() => { const select = document.querySelector("#ageYear"); const options = select.querySelectorAll("option"); const selectedOption = [...options].find( (option) => option.text === "1900" ); selectedOption.selected = true; }); await p.click("#view_product_page_btn"); } async function getDataFromGame() { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto( "https://store.steampowered.com/app/221100/" ); await handleAgeRestriction(page); const result = await page.evaluate(() => { const title = document.querySelector(".apphub_AppName").innerText; const price = document.querySelector('.game_purchase_price').innerText; const bundle_title = document.querySelector('div.game_area_purchase_game > h1').innerText; const bundle_price = document.querySelector('.discount_final_price').innerText; const game = { title, price, bundle_title, bundle_price } return game; }); console.log(result); await browser.close(); }
运行时触发以下错误:
node:internal/process/promises:279
triggerUncaughtException(err, true /* fromPromise */);
^
Error [TypeError]: Cannot read properties of null (reading 'innerText')
at evaluate
添加slowMo:1000参数后代码可正常运行,但耗时过长,且不想让用户看到浏览器运行过程。
核心原因
错误本质是页面元素尚未加载完成就执行了获取操作,slowMo靠强制延迟规避了问题,但属于低效且不严谨的方案。
解决方案
1. 启用无头模式
使用Puppeteer的新版无头模式(不显示浏览器窗口),替代旧版headless: true:
const browser = await puppeteer.launch({ headless: "new" });
2. 用等待API替代固定延迟
针对每个需要操作的元素,使用page.waitForSelector()等待元素加载完成,确保元素存在后再执行操作:
- 年龄验证环节:先等待年份选择器和确认按钮出现
- 数据获取环节:等待目标标题、价格元素加载完成
3. 兼容元素不存在的情况
部分游戏可能没有捆绑包信息,直接获取会导致null报错,需要做容错处理。
修改后的完整代码
async function handleAgeRestriction(p) { // 等待年龄选择器加载完成,超时5秒 await p.waitForSelector("#ageYear", { timeout: 5000 }); await p.evaluate(() => { const select = document.querySelector("#ageYear"); const options = select.querySelectorAll("option"); const selectedOption = [...options].find( (option) => option.text === "1900" ); if (selectedOption) selectedOption.selected = true; }); // 等待确认按钮加载完成再点击 await p.waitForSelector("#view_product_page_btn", { timeout: 5000 }); await p.click("#view_product_page_btn"); } async function getDataFromGame() { // 启用新版无头模式,不显示浏览器窗口 const browser = await puppeteer.launch({ headless: "new" }); const page = await browser.newPage(); // 页面跳转时等待网络空闲(确保静态资源加载完成) await page.goto("https://store.steampowered.com/app/221100/", { waitUntil: "networkidle0" }); await handleAgeRestriction(page); // 等待核心数据元素加载完成 await page.waitForSelector(".apphub_AppName", { timeout: 5000 }); await page.waitForSelector(".game_purchase_price", { timeout: 5000 }); const result = await page.evaluate(() => { // 用可选链处理元素不存在的情况,避免报错 const title = document.querySelector(".apphub_AppName")?.innerText || "无标题"; const price = document.querySelector('.game_purchase_price')?.innerText || "无价格"; const bundle_title = document.querySelector('div.game_area_purchase_game > h1')?.innerText || "无捆绑包"; const bundle_price = document.querySelector('.discount_final_price')?.innerText || "无捆绑包价格"; return { title, price, bundle_title, bundle_price }; }); console.log(result); await browser.close(); }
关键优化点说明
waitForSelector的timeout参数设置超时时间,避免无限等待- 使用可选链操作符
?.处理元素不存在的情况,防止报错 page.goto的waitUntil设置为networkidle0,确保页面静态资源加载完成后再执行后续操作
内容的提问来源于stack exchange,提问作者Tomas Madariaga
相关产品推荐
相关产品推荐

