循环调用Piwik API写入CSV仅保留首个日期数据的问题
解决循环调用Piwik API写入CSV仅保留首个日期数据的问题
我帮你排查出问题的核心原因了,主要是异步操作的竞争条件+文件流配置错误导致的,具体分析和修复方案如下:
问题根源分析
- 文件流配置错误:你在
csv.writeToStream里把flags: 'a'放在了fast-csv的配置参数中,但这个参数是给fs.createWriteStream用的,不属于fast-csv的配置项。你每次回调里创建的fs.createWriteStream("my.csv")默认是覆盖模式('w'),后面的异步回调执行时会直接覆盖之前写入的内容。 - 多流竞争写入:循环里会快速触发多个异步API请求,每个回调都新建一个文件流操作同一个文件,这种多流同时写入的竞争会导致数据混乱,最终可能只有第一个完成的请求内容被保留。
- 表头重复写入:每次调用
writeToStream都设置headers: true,会导致表头被重复写入,这也不符合预期。
修复后的代码示例
我把代码改成了用async/await控制异步顺序,复用单个文件流,同时优化了错误处理:
// 加载依赖并配置Piwik const piwik = require('piwik').setup('placeholderurl', 'XXXXX'); const fs = require('fs'); const csv = require("fast-csv"); const moment = require('moment'); // 将Piwik的异步API包装为Promise,方便用async/await控制流程 const fetchPiwikDailyData = (dateStr) => { return new Promise((resolve, reject) => { piwik.api({ method: 'Live.getLastVisitsDetails', idSite: 3, period: 'day', format: 'csv', date: dateStr }, (err, data) => { if (err) reject(err); else resolve(data); }); }); }; // 主逻辑函数 const exportToCsv = async () => { const startDate = moment('2016-05-12'); const endDate = moment('2016-05-15'); // 只创建一次文件流,设置追加模式(文件存在则追加,不存在则新建) const outputStream = fs.createWriteStream('my.csv', { flags: 'a' }); let hasWrittenHeaders = false; // 标记是否已写入表头 // 用async/await确保逐个日期处理,避免异步竞争 for (let currentDate = moment(startDate); currentDate.isBefore(endDate); currentDate.add(1, 'days')) { const dateStr = currentDate.format('YYYY-MM-DD'); try { const dailyData = await fetchPiwikDailyData(dateStr); console.log(`已获取${dateStr}的数据:`, dailyData); // 写入CSV,仅第一次写入表头 csv.writeToStream(outputStream, dailyData, { headers: !hasWrittenHeaders, includeEndRowDelimiter: true }); // 标记表头已写入,后续不再重复写入 if (!hasWrittenHeaders) { hasWrittenHeaders = true; } } catch (error) { console.error(`获取${dateStr}数据失败:`, error); } } // 所有数据处理完成后关闭流,确保数据全部写入文件 outputStream.on('finish', () => { console.log('所有日期的数据已成功写入my.csv'); }); outputStream.end(); }; // 执行导出逻辑 exportToCsv().catch(err => console.error('程序执行出错:', err));
关键修改点说明
- 异步流程控制:把Piwik API包装成Promise,用
async/await让循环逐个处理每个日期的请求,避免多个异步回调同时写入文件导致的竞争问题。 - 复用文件流:在循环外只创建一次文件流,并设置
flags: 'a'实现追加写入,彻底解决覆盖问题。 - 表头控制:用
hasWrittenHeaders变量确保表头只在第一次写入时添加,避免重复写入表头。 - 错误处理优化:给每个日期的请求单独添加错误捕获,单个日期的请求失败不会导致整个程序崩溃。
- 流的正确关闭:通过监听流的
finish事件,确保所有数据都写入文件后再结束程序。
内容的提问来源于stack exchange,提问作者user3757962
相关产品推荐
相关产品推荐

