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

循环调用Piwik API写入CSV仅保留首个日期数据的问题

解决循环调用Piwik API写入CSV仅保留首个日期数据的问题

我帮你排查出问题的核心原因了,主要是异步操作的竞争条件+文件流配置错误导致的,具体分析和修复方案如下:

问题根源分析

  1. 文件流配置错误:你在csv.writeToStream里把flags: 'a'放在了fast-csv的配置参数中,但这个参数是给fs.createWriteStream用的,不属于fast-csv的配置项。你每次回调里创建的fs.createWriteStream("my.csv")默认是覆盖模式('w'),后面的异步回调执行时会直接覆盖之前写入的内容。
  2. 多流竞争写入:循环里会快速触发多个异步API请求,每个回调都新建一个文件流操作同一个文件,这种多流同时写入的竞争会导致数据混乱,最终可能只有第一个完成的请求内容被保留。
  3. 表头重复写入:每次调用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));

关键修改点说明

  1. 异步流程控制:把Piwik API包装成Promise,用async/await让循环逐个处理每个日期的请求,避免多个异步回调同时写入文件导致的竞争问题。
  2. 复用文件流:在循环外只创建一次文件流,并设置flags: 'a'实现追加写入,彻底解决覆盖问题。
  3. 表头控制:用hasWrittenHeaders变量确保表头只在第一次写入时添加,避免重复写入表头。
  4. 错误处理优化:给每个日期的请求单独添加错误捕获,单个日期的请求失败不会导致整个程序崩溃。
  5. 流的正确关闭:通过监听流的finish事件,确保所有数据都写入文件后再结束程序。

内容的提问来源于stack exchange,提问作者user3757962

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:59:14