Puppeteer遍历URL数组无法跳转页面问题求助
问题分析与解决方案
我帮你梳理下代码里的几个核心问题,这应该就是程序挂起的根源:
1. 页码起始值错误
你创建URL数组时循环从i=0开始,但观察你初始访问的URL是CurrentPage=1,说明网站的页码是从1开始计数的。CurrentPage=0的页面大概率不存在,或者加载后没有预期内容,直接导致后续等待逻辑超时。
2. page.goto后重复等待导航的冗余操作
page.goto()方法本身就会默认等待导航完成(waitUntil: load),你紧接着调用await page.waitForNavigation(),相当于在等待一个已经结束的导航事件,程序会陷入无限等待状态,直接挂起。
3. 单页应用的Hash导航特性
这个网站是单页应用(SPA),URL中#后的参数变化不会触发完整的页面刷新,page.goto()无法触发标准的导航事件,此时waitForNavigation完全不适用,应该改为等待页面关键元素加载来确认内容更新。
修复后的完整代码示例
const puppeteer = require("puppeteer-extra"); // 启用stealth插件规避反爬 puppeteer.use(require("puppeteer-extra-plugin-stealth")()); (async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); // 提取固定URL部分,只动态替换页码 const baseUrl = "https://www.realtor.ca/map#ZoomLevel=14&Center=43.771556%2C-79.427630&LatitudeMax=43.78395&LongitudeMax=-79.38710&LatitudeMin=43.75916&LongitudeMin=-79.46816&view=list&Sort=6-D&PGeoIds=g10_dpz90869&GeoName=Willowdale%2C%20Toronto%2C%20ON&PropertyTypeGroupID=1&PropertySearchTypeId=1&TransactionTypeId=2&PriceMax=1500000&BuildingTypeId=1&Currency=CAD"; // 初始化访问第一页 await page.goto(`${baseUrl}&CurrentPage=1`, { waitUntil: "domcontentloaded" }); await page.waitForSelector(".cardCon", { timeout: 15000 }); // 等待列表卡片加载完成 // 提取第一页数据 const firstPageData = await page.evaluate(() => { let data = []; let elements = document.querySelectorAll(".cardCon"); for (var element of elements) { let price = element.children[0].children[1].childNodes[1].children[1].childNodes[1].childNodes[3].innerText; let address = element.children[0].children[1].childNodes[1].children[1].childNodes[1].childNodes[5].innerText; let details = element.children[0].children[1].childNodes[1].children[1].childNodes[3].innerText.replace(/\n/g, " "); let url = element.children[0].childNodes[3].href; data.push({ price, details, address, url }); } return data; }); console.log("第一页数据:", firstPageData); // 获取总页数 let totalPageNumber = await page.$eval( "#ListViewPagination_Bottom > div > div > div > span.paginationTotalPagesNum", (element) => element.innerText ); console.log("总页数:", totalPageNumber); let pageNumberToNumber = parseInt(totalPageNumber); // 创建URL数组:从1到总页数 let urlList = []; for (let i = 1; i <= pageNumberToNumber; i++) { urlList.push(`${baseUrl}&CurrentPage=${i}`); } console.log("生成的页面URL列表:", urlList); console.log("待爬取页面数量:", urlList.length); // 遍历访问每一页(跳过第一页,因为已经爬过) for (let i = 1; i < urlList.length; i++) { const url = urlList[i]; // 针对SPA的hash变化,导航后等待关键元素更新 await page.goto(url, { waitUntil: "domcontentloaded" }); await page.waitForSelector(".cardCon", { timeout: 15000 }); // 提取当前页数据 const currentPageData = await page.evaluate(() => { let data = []; let elements = document.querySelectorAll(".cardCon"); for (var element of elements) { let price = element.children[0].children[1].childNodes[1].children[1].childNodes[1].childNodes[3].innerText; let address = element.children[0].children[1].childNodes[1].children[1].childNodes[1].childNodes[5].innerText; let details = element.children[0].children[1].childNodes[1].children[1].childNodes[3].innerText.replace(/\n/g, " "); let url = element.children[0].childNodes[3].href; data.push({ price, details, address, url }); } return data; }); console.log(`第${i+1}页数据:`, currentPageData); // 可选:添加随机延迟,避免被反爬拦截 await page.waitFor(Math.random() * 2000 + 1000); } await browser.close(); })();
额外优化建议
- 增加超时兜底:给所有等待元素的方法加上
timeout参数,避免因页面加载异常导致程序挂起。 - 添加异常捕获:用
try/catch包裹导航和数据提取逻辑,单个页面出错不会中断整个爬取流程。 - 复用数据提取逻辑:可以把提取页面数据的代码封装成函数,避免重复写相同的
evaluate代码。
内容的提问来源于stack exchange,提问作者Dpuiatti
相关产品推荐
相关产品推荐

