如何将Puppeteer page.evaluate结果导出至CSV文件?
解决Puppeteer爬取多页评论并导出CSV的问题
嘿,刚看了你的代码,你已经把登录、翻页这些核心流程跑通了,很棒!现在咱们来解决数据合并和CSV导出的问题,一步步来:
1. 先搞定数据合并的问题
你的代码里每次循环都能拿到当前页的评论,但没把它们存到一个全局容器里,还有个小bug:console.log(result)里的result根本没定义,应该是options对吧?
首先,在循环外面定义一个空数组,用来存所有页面的评论:
let allReviews = []; // 全局数组,存储所有爬取到的评论
然后在循环里,把当前页的评论合并到这个数组里:
const options = await page.evaluate(() => Array.from(document.querySelectorAll('p.dtl')).map(partner => partner.innerText) ); allReviews = allReviews.concat(options); // 把当前页评论追加到总数组
另外,你之前用固定的waitFor(500)等页面加载不太可靠,换成等待评论元素加载完成会更稳妥:
await page.waitForSelector('p.dtl'); // 等当前页的评论节点加载出来再继续
还有个关键问题:你循环一开始就点下一页,导致第一页的评论完全漏掉了!所以要在循环前先把第一页的评论爬下来。
2. 导出为CSV文件
Node.js自带的fs模块就能搞定文件写入,咱们只需要把数组转成符合CSV格式的字符串就行,还要注意处理评论里的换行、逗号这些特殊字符,避免导出的文件格式错乱。
首先引入fs模块:
const fs = require('fs');
等所有评论爬完后,把数组转成CSV并写入文件:
// 转换数组为CSV格式 const csvHeader = '评论内容\n'; // CSV表头 const csvRows = allReviews.map(review => { // 处理CSV格式的特殊字符:双引号替换为两个双引号,换行替换为空格 return `"${review.replace(/"/g, '""').replace(/\n/g, ' ')}"`; }); const csvContent = csvHeader + csvRows.join('\n'); // 写入本地文件 fs.writeFileSync('qoo10商品评论.csv', csvContent, 'utf8'); console.log(`成功导出${allReviews.length}条评论!`);
完整修正后的代码
我把所有修改整合到一起,加上了详细注释,方便你理解:
const puppeteer = require('puppeteer'); const CREDS = require('./creds'); const fs = require('fs'); // 引入文件系统模块 (async function main() { try{ const URL = 'https://www.qoo10.sg/gmkt.inc/Login/Login.aspx'; const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36') await page.goto(URL ,{waitUntil:'networkidle0'}); // 登录流程 const USERNAME_SELECTOR = '#login_id'; const PASSWORD_SELECTOR = '#passwd'; const BUTTON_SELECTOR = '#dv_member_login > fieldset > a'; await page.click(USERNAME_SELECTOR); await page.keyboard.type(CREDS.username); await page.click(PASSWORD_SELECTOR); await page.keyboard.type(CREDS.password); await page.click(BUTTON_SELECTOR); await page.waitForNavigation({waitUntil:'networkidle0'}); // 进入商品详情页 const itemUrl = 'https://www.qoo10.sg/item/FANCO-FANCO-CEILING-FAN-FFM2000-FFM3000-FFM4000-FFM6000-LIGHT-KITS/496619203?banner_no=1305330'; await page.goto(itemUrl, {waitUntil:'networkidle0'}); // 计算图片评论的总页数 const photoreviewsnumber = await page.evaluate(() => document.querySelector('#divPremiumList > div.rv_tit.photo > h2 > span').innerText); const photoreviewspage = Math.ceil(parseInt(photoreviewsnumber)/5); // 转成数字再计算,避免字符串运算错误 let allReviews = []; // 存储所有评论的全局数组 // 先爬取第一页的评论 const firstPageReviews = await page.evaluate(() => Array.from(document.querySelectorAll('p.dtl')).map(partner => partner.innerText) ); allReviews = allReviews.concat(firstPageReviews); console.log('已获取第1页评论'); // 循环爬取剩下的页面 for (let i = 1; i < photoreviewspage; i++) { // 从第2页开始,因为第一页已经爬过了 const nextButton = await page.$('#photo_review_paging > a.next'); if (nextButton) { // 先判断下一页按钮是否存在,避免最后一页报错 await nextButton.click(); await page.waitForNavigation({waitUntil:'networkidle0'}); // 等待页面跳转完成 await page.waitForSelector('p.dtl'); // 等待当前页评论加载 // 获取当前页评论并合并到总数组 const currentPageReviews = await page.evaluate(() => Array.from(document.querySelectorAll('p.dtl')).map(partner => partner.innerText) ); allReviews = allReviews.concat(currentPageReviews); console.log(`已获取第${i+1}页评论`); } else { console.log('没有更多评论页面了'); break; } } // 导出CSV文件 if (allReviews.length > 0) { const csvHeader = '评论内容\n'; const csvRows = allReviews.map(review => { return `"${review.replace(/"/g, '""').replace(/\r?\n/g, ' ')}"`; }); const csvContent = csvHeader + csvRows.join('\n'); fs.writeFileSync('qoo10商品评论.csv', csvContent, 'utf8'); console.log(`成功导出${allReviews.length}条评论到本地文件!`); } else { console.log('没有获取到任何评论数据'); } await browser.close(); } catch(e) { console.log('程序出错了:', e); } })();
几个额外的小提醒
- 我把
photoreviewsnumber转成了数字parseInt(photoreviewsnumber),因为innerText返回的是字符串,直接做除法会出问题; - 循环从
i=1开始,避免重复爬第一页或者漏掉第一页; - 加入了下一页按钮的存在性判断,防止最后一页没有按钮时报错;
- CSV处理里替换了双引号和换行,保证导出的文件格式规范,不会出现单元格内容错乱的情况。
内容的提问来源于stack exchange,提问作者SG Fresh Idea
相关产品推荐
相关产品推荐

