如何使用Puppeteer爬取多页面数据并写入单个JSON文件
Puppeteer多页爬取数据合并写入JSON实现方案
问题原因
变量未定义报错来自两个核心问题:
vmz01、vmz02、mb01、mb02都是异步爬取函数内部的局部作用域变量,函数外部无法直接访问- 爬取函数是异步执行的,没有等待执行完成就读取变量的话,变量还没完成声明赋值,自然触发报错
修改步骤
- 改造两个爬取函数,将爬取到的价格数据作为返回值对外暴露,同时给
browser.close()加上await避免浏览器资源泄漏 - 新增主执行逻辑,通过
Promise.all并行等待两个爬取任务全部执行完成,拿到所有爬取结果 - 按照指定结构组装最终数据对象,调用Node.js内置的文件系统模块,将完整数据写入单个JSON文件
完整修改后代码
const puppeteer = require('puppeteer'); // 引入Node.js内置的promise版文件系统模块,无需额外安装 const fs = require('fs/promises'); async function scrapeVMZ(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'domcontentloaded' }); const [vmzel1] = await page.$x('//*[@id="__layout"]/div/div[1]/section/div/div/div[2]/div[2]/div[1]/div/div/div[2]/div/div[1]/span[2]'); const vmztxt1 = await vmzel1.getProperty('textContent'); const vmzRawTxt1 = await vmztxt1.jsonValue(); const [vmzel2] = await page.$x('//*[@id="__layout"]/div/div[1]/section/div/div/div[2]/div[2]/div[1]/div/div/div[2]/div/div[1]/span[4]/b'); const vmztxt2 = await vmzel2.getProperty('textContent'); const vmzRawTxt2 = await vmztxt2.jsonValue(); const vmz01 = JSON.stringify(vmzRawTxt1.trim()); const vmz02 = JSON.stringify(vmzRawTxt2.trim()); await browser.close(); // 对外返回爬取到的两个价格数据 return { vmz01, vmz02 }; } async function scrapeMB(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'domcontentloaded' }); const [mbel1] = await page.$x('/html/body/section[3]/div/div/div[2]/div[1]/div/div[2]/a[1]/span[2]/span/div/div[2]/span'); const mbtxt1 = await mbel1.getProperty('textContent'); const mbRawTxt1 = await mbtxt1.jsonValue(); const [mbel2] = await page.$x('/html/body/section[3]/div/div/div[2]/div[1]/div/div[2]/a[1]/span[2]/span/div/div[4]/span'); const mbtxt2 = await mbel2.getProperty('textContent'); const mbRawTxt2 = await mbtxt2.jsonValue(); const mb01 = JSON.stringify(mbRawTxt1.trim()); const mb02 = JSON.stringify(mbRawTxt2.trim()); await browser.close(); // 对外返回爬取到的两个价格数据 return { mb01, mb02 }; } // 主执行逻辑 async function main() { // 并行执行两个爬取任务,等待全部完成后拿结果 const [vmzRes, mbRes] = await Promise.all([ scrapeVMZ('https://www.vmzviagens.com.br/ingressos/orlando/walt-disney-orlando'), scrapeMB('https://www.ingressosmagicblue.com.br/produtos/?mpage=2') ]); // 按照指定结构组装数据 const abc = { "MB": { preco: mbRes.mb01, preco2: mbRes.mb02 }, "VMZ": { preco: vmzRes.vmz01, preco2: vmzRes.vmz02 } }; // 写入JSON文件,null和2参数是为了格式化输出JSON,方便阅读 await fs.writeFile('./price_data.json', JSON.stringify(abc, null, 2), 'utf-8'); console.log('数据已成功写入price_data.json文件'); } // 启动主逻辑 main().catch(err => console.error('执行出错:', err));
补充说明
- 代码里给
page.goto加了waitUntil: 'domcontentloaded'参数,减少页面等待时间,避免因静态资源加载慢导致爬取超时 - 对爬取到的原始文本加了
trim(),自动去掉文本前后多余的换行、空格 - 写入文件时
JSON.stringify传入了格式化参数,生成的JSON文件会自动缩进,不会挤成一行 - 所有异步操作都加了
await,不会出现浏览器提前关闭、文件写入失败的问题
内容的提问来源于stack exchange,提问作者Fadrick
相关产品推荐
相关产品推荐

