You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:50:27