Home » Чтение и запись CSV файлов в Node.js с помощью node-csv
Чтение и запись CSV файлов в Node.js с помощью node-csv

Чтение и запись CSV файлов в Node.js с помощью node-csv

Сталкивались ли вы с задачей обработки CSV файлов в Node.js? Если да, то наверняка знаете, что работа с CSV может превратиться в настоящий кошмар без подходящих инструментов. Встречайте node-csv — мощнейший пакет для работы с CSV файлами в Node.js, который станет вашим верным спутником в автоматизации серверных задач.

Эта статья покажет вам, как легко и быстро освоить чтение и запись CSV файлов, что особенно важно для обработки логов, экспорта данных и создания отчетов на серверах. Мы разберём практические примеры, подводные камни и нестандартные способы использования.

## Что такое node-csv и как это работает?

node-csv — это не просто библиотека, а целая экосистема из четырёх модулей:

  • csv-generate — генерация CSV данных
  • csv-parse — парсинг CSV файлов
  • csv-transform — трансформация данных
  • csv-stringify — преобразование данных в CSV формат

Каждый модуль можно использовать отдельно или все вместе через основной пакет csv. Архитектура построена на потоках (streams), что делает её идеальной для обработки больших файлов без загрузки всего содержимого в память.

## Пошаговая настройка и установка

Начнём с установки пакета. Можете установить как основной пакет, так и отдельные модули:

# Установка основного пакета
npm install csv

# Или установка отдельных модулей
npm install csv-parse csv-stringify csv-transform csv-generate

Для работы с файлами также понадобится встроенный модуль fs:

const fs = require('fs');
const csv = require('csv');
// или
const { parse } = require('csv-parse');
const { stringify } = require('csv-stringify');

## Чтение CSV файлов: от простого к сложному

Давайте начнём с простого примера чтения CSV файла. Создадим файл data.csv:

name,age,email
John Doe,30,john@example.com
Jane Smith,25,jane@example.com
Bob Johnson,35,bob@example.com

Теперь прочитаем его:

const fs = require('fs');
const { parse } = require('csv-parse');

fs.createReadStream('data.csv')
  .pipe(parse({ 
    columns: true, // Первая строка как заголовки
    skip_empty_lines: true 
  }))
  .on('data', (row) => {
    console.log(row);
    // { name: 'John Doe', age: '30', email: 'john@example.com' }
  })
  .on('error', (err) => {
    console.error('Ошибка:', err);
  })
  .on('end', () => {
    console.log('Чтение завершено');
  });

## Запись CSV файлов: создаём данные правильно

Теперь научимся создавать CSV файлы. Вот пример записи данных:

const fs = require('fs');
const { stringify } = require('csv-stringify');

const data = [
  ['Name', 'Age', 'Email'],
  ['Alice Brown', 28, 'alice@example.com'],
  ['Charlie Davis', 32, 'charlie@example.com']
];

stringify(data, (err, output) => {
  if (err) throw err;
  
  fs.writeFileSync('output.csv', output);
  console.log('CSV файл создан успешно!');
});

Или используя потоки для больших объёмов данных:

const fs = require('fs');
const { stringify } = require('csv-stringify');

const writableStream = fs.createWriteStream('large_output.csv');
const stringifier = stringify({
  header: true,
  columns: ['name', 'age', 'email']
});

stringifier.pipe(writableStream);

// Добавляем данные
stringifier.write(['John Doe', 30, 'john@example.com']);
stringifier.write(['Jane Smith', 25, 'jane@example.com']);

stringifier.end();

## Продвинутые возможности и настройки

### Обработка нестандартных разделителей

CSV файлы не всегда используют запятые. Вот как работать с разными разделителями:

// Для файлов с точкой с запятой (популярно в Европе)
fs.createReadStream('data.csv')
  .pipe(parse({ 
    delimiter: ';',
    columns: true 
  }))
  .on('data', (row) => {
    console.log(row);
  });

// Для файлов с табуляцией
fs.createReadStream('data.tsv')
  .pipe(parse({ 
    delimiter: '\t',
    columns: true 
  }))
  .on('data', (row) => {
    console.log(row);
  });

### Трансформация данных на лету

Одна из крутых фич node-csv — возможность трансформировать данные в процессе чтения:

const { transform } = require('csv-transform');

fs.createReadStream('data.csv')
  .pipe(parse({ columns: true }))
  .pipe(transform((row) => {
    // Преобразуем возраст в число и добавляем статус
    row.age = parseInt(row.age);
    row.status = row.age >= 30 ? 'senior' : 'junior';
    return row;
  }))
  .pipe(stringify({ header: true }))
  .pipe(fs.createWriteStream('transformed.csv'));

## Практические кейсы и примеры использования

### Кейс 1: Обработка логов веб-сервера

Представьте, что у вас есть CSV лог с данными о посещениях:

const fs = require('fs');
const { parse } = require('csv-parse');

const logStats = {
  totalVisits: 0,
  uniqueIPs: new Set(),
  statusCodes: {}
};

fs.createReadStream('access_log.csv')
  .pipe(parse({ columns: ['ip', 'timestamp', 'method', 'url', 'status', 'size'] }))
  .on('data', (row) => {
    logStats.totalVisits++;
    logStats.uniqueIPs.add(row.ip);
    
    if (!logStats.statusCodes[row.status]) {
      logStats.statusCodes[row.status] = 0;
    }
    logStats.statusCodes[row.status]++;
  })
  .on('end', () => {
    console.log('Статистика логов:');
    console.log('Всего посещений:', logStats.totalVisits);
    console.log('Уникальных IP:', logStats.uniqueIPs.size);
    console.log('Коды ответов:', logStats.statusCodes);
  });

### Кейс 2: Генерация отчётов для системы мониторинга

Создадим скрипт для генерации ежедневных отчётов:

const fs = require('fs');
const { stringify } = require('csv-stringify');
const { exec } = require('child_process');

async function generateSystemReport() {
  const reportData = [
    ['Metric', 'Value', 'Timestamp'],
    ['CPU Usage', '45%', new Date().toISOString()],
    ['Memory Usage', '78%', new Date().toISOString()],
    ['Disk Usage', '23%', new Date().toISOString()]
  ];
  
  const output = await new Promise((resolve, reject) => {
    stringify(reportData, (err, output) => {
      if (err) reject(err);
      else resolve(output);
    });
  });
  
  const fileName = `system_report_${new Date().toISOString().split('T')[0]}.csv`;
  fs.writeFileSync(fileName, output);
  console.log(`Отчёт сохранён: ${fileName}`);
}

generateSystemReport();

## Сравнение с альтернативными решениями

Решение Преимущества Недостатки Лучше всего для
node-csv Полная экосистема, потоки, гибкость Может быть избыточным для простых задач Сложной обработки больших файлов
csv-parser Простота, скорость Только для чтения Быстрого парсинга
papaparse Работает в браузере и Node.js Больше для браузера Универсальных решений
fast-csv Высокая производительность Меньше возможностей Простых задач с упором на скорость

## Подводные камни и как их избежать

### Проблема с кодировкой

Часто CSV файлы приходят в разных кодировках. Вот как с этим бороться:

const iconv = require('iconv-lite');

fs.createReadStream('data.csv')
  .pipe(iconv.decodeStream('win1251')) // Для Windows-1251
  .pipe(parse({ columns: true }))
  .on('data', (row) => {
    console.log(row);
  });

### Обработка больших файлов

Для файлов размером в гигабайты используйте потоки с ограничением памяти:

const fs = require('fs');
const { parse } = require('csv-parse');

let processedRows = 0;
const batchSize = 1000;
let batch = [];

fs.createReadStream('huge_file.csv')
  .pipe(parse({ columns: true }))
  .on('data', (row) => {
    batch.push(row);
    
    if (batch.length >= batchSize) {
      processBatch(batch);
      batch = [];
    }
  })
  .on('end', () => {
    if (batch.length > 0) {
      processBatch(batch);
    }
    console.log(`Обработано ${processedRows} строк`);
  });

function processBatch(rows) {
  // Обработка батча
  processedRows += rows.length;
  console.log(`Обработано ${processedRows} строк`);
}

## Интеграция с другими пакетами

### Работа с базами данных

Часто нужно загрузить CSV данные в базу. Вот пример с PostgreSQL:

const { Client } = require('pg');
const fs = require('fs');
const { parse } = require('csv-parse');

const client = new Client({
  host: 'localhost',
  database: 'mydb',
  user: 'user',
  password: 'password'
});

async function importCSVtoDB() {
  await client.connect();
  
  fs.createReadStream('users.csv')
    .pipe(parse({ columns: true }))
    .on('data', async (row) => {
      try {
        await client.query(
          'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)',
          [row.name, row.email, parseInt(row.age)]
        );
      } catch (error) {
        console.error('Ошибка вставки:', error);
      }
    })
    .on('end', () => {
      console.log('Импорт завершён');
      client.end();
    });
}

importCSVtoDB();

### Интеграция с Express.js

Создадим API для загрузки и обработки CSV файлов:

const express = require('express');
const multer = require('multer');
const { parse } = require('csv-parse');
const { stringify } = require('csv-stringify');

const app = express();
const upload = multer({ dest: 'uploads/' });

app.post('/upload-csv', upload.single('csvfile'), (req, res) => {
  const results = [];
  
  fs.createReadStream(req.file.path)
    .pipe(parse({ columns: true }))
    .on('data', (data) => {
      results.push(data);
    })
    .on('end', () => {
      res.json({
        message: 'CSV обработан успешно',
        rowCount: results.length,
        data: results.slice(0, 5) // Первые 5 строк для preview
      });
    })
    .on('error', (err) => {
      res.status(500).json({ error: err.message });
    });
});

app.listen(3000, () => {
  console.log('API запущен на порту 3000');
});

## Автоматизация и скрипты

### Создание cron-задач для обработки CSV

Напишем скрипт для автоматической обработки CSV файлов:

#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const { parse } = require('csv-parse');
const { stringify } = require('csv-stringify');

const watchDir = '/var/log/csv-import';
const processedDir = '/var/log/csv-processed';

function processCSVFile(filePath) {
  const fileName = path.basename(filePath, '.csv');
  const outputPath = path.join(processedDir, `${fileName}_processed.csv`);
  
  const processedData = [];
  
  fs.createReadStream(filePath)
    .pipe(parse({ columns: true }))
    .on('data', (row) => {
      // Добавляем timestamp обработки
      row.processed_at = new Date().toISOString();
      processedData.push(row);
    })
    .on('end', () => {
      stringify(processedData, { header: true }, (err, output) => {
        if (err) {
          console.error('Ошибка обработки:', err);
          return;
        }
        
        fs.writeFileSync(outputPath, output);
        console.log(`Файл обработан: ${outputPath}`);
        
        // Перемещаем оригинал в архив
        fs.renameSync(filePath, path.join(processedDir, `${fileName}_original.csv`));
      });
    });
}

// Мониторинг папки
fs.watch(watchDir, (eventType, filename) => {
  if (eventType === 'rename' && filename.endsWith('.csv')) {
    const filePath = path.join(watchDir, filename);
    
    // Небольшая задержка, чтобы файл полностью записался
    setTimeout(() => {
      if (fs.existsSync(filePath)) {
        processCSVFile(filePath);
      }
    }, 1000);
  }
});

console.log(`Мониторинг папки: ${watchDir}`);

Добавьте этот скрипт в crontab для автоматического запуска:

# Запуск каждые 5 минут
*/5 * * * * /usr/bin/node /path/to/csv-processor.js

## Оптимизация производительности

### Настройка размера буфера

Для максимальной производительности при работе с большими файлами:

const fs = require('fs');
const { parse } = require('csv-parse');

const stream = fs.createReadStream('large_file.csv', {
  highWaterMark: 64 * 1024 // 64KB buffer
});

stream
  .pipe(parse({ 
    columns: true,
    skip_empty_lines: true,
    // Оптимизация для больших файлов
    relax_column_count: true,
    max_record_size: 1048576 // 1MB на запись
  }))
  .on('data', (row) => {
    // Обработка данных
  });

### Параллельная обработка

Для ускорения обработки можно использовать воркеры:

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const fs = require('fs');
const { parse } = require('csv-parse');

if (isMainThread) {
  // Основной поток
  const numWorkers = require('os').cpus().length;
  const workers = [];
  
  for (let i = 0; i < numWorkers; i++) {
    const worker = new Worker(__filename, {
      workerData: { workerId: i }
    });
    
    worker.on('message', (data) => {
      console.log(`Worker ${data.workerId} обработал ${data.rowCount} строк`);
    });
    
    workers.push(worker);
  }
  
  // Распределение файлов между воркерами
  const files = fs.readdirSync('./csv-files').filter(f => f.endsWith('.csv'));
  files.forEach((file, index) => {
    const workerIndex = index % numWorkers;
    workers[workerIndex].postMessage({ file });
  });
  
} else {
  // Воркер
  const { workerId } = workerData;
  
  parentPort.on('message', ({ file }) => {
    let rowCount = 0;
    
    fs.createReadStream(`./csv-files/${file}`)
      .pipe(parse({ columns: true }))
      .on('data', (row) => {
        rowCount++;
        // Обработка строки
      })
      .on('end', () => {
        parentPort.postMessage({ workerId, rowCount, file });
      });
  });
}

## Мониторинг и логирование

Не забывайте про мониторинг обработки CSV файлов:

const fs = require('fs');
const { parse } = require('csv-parse');
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'csv-processing.log' })
  ]
});

function processCSVWithLogging(filePath) {
  const startTime = Date.now();
  let rowCount = 0;
  let errorCount = 0;
  
  logger.info('Начата обработка CSV', { file: filePath });
  
  fs.createReadStream(filePath)
    .pipe(parse({ columns: true }))
    .on('data', (row) => {
      rowCount++;
      
      try {
        // Обработка строки
        processRow(row);
      } catch (error) {
        errorCount++;
        logger.error('Ошибка обработки строки', { 
          row: rowCount, 
          error: error.message 
        });
      }
    })
    .on('end', () => {
      const duration = Date.now() - startTime;
      logger.info('Обработка завершена', {
        file: filePath,
        rowCount,
        errorCount,
        duration: `${duration}ms`,
        rowsPerSecond: (rowCount / (duration / 1000)).toFixed(2)
      });
    })
    .on('error', (error) => {
      logger.error('Критическая ошибка обработки', {
        file: filePath,
        error: error.message
      });
    });
}

## Интересные факты и нестандартные применения

### Факт 1: CSV как формат конфигурации

CSV можно использовать для хранения конфигурации сервера:

// config.csv
service,enabled,port,timeout
nginx,true,80,30
mysql,true,3306,60
redis,false,6379,10

// Загрузка конфигурации
const config = {};

fs.createReadStream('config.csv')
  .pipe(parse({ columns: true }))
  .on('data', (row) => {
    config[row.service] = {
      enabled: row.enabled === 'true',
      port: parseInt(row.port),
      timeout: parseInt(row.timeout)
    };
  })
  .on('end', () => {
    console.log('Конфигурация загружена:', config);
  });

### Факт 2: Создание CSV-прокси для API

Можно создать прокси, который конвертирует JSON API в CSV:

const express = require('express');
const axios = require('axios');
const { stringify } = require('csv-stringify');

const app = express();

app.get('/api/users.csv', async (req, res) => {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    const users = response.data;
    
    const csvData = users.map(user => ([
      user.id,
      user.name,
      user.email,
      user.phone,
      user.website
    ]));
    
    csvData.unshift(['ID', 'Name', 'Email', 'Phone', 'Website']);
    
    stringify(csvData, (err, output) => {
      if (err) {
        res.status(500).send('Ошибка генерации CSV');
        return;
      }
      
      res.setHeader('Content-Type', 'text/csv');
      res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
      res.send(output);
    });
    
  } catch (error) {
    res.status(500).send('Ошибка получения данных');
  }
});

app.listen(3000);

## Полезные ссылки

Для более глубокого изучения рекомендую:

Для тестирования CSV обработки на реальных серверах понадобится надёжный хостинг. Рекомендую VPS сервер для разработки и тестирования, или выделенный сервер для production нагрузок.

## Заключение и рекомендации

node-csv — это мощный инструмент, который должен быть в арсенале каждого разработчика, работающего с серверами. Основные рекомендации:

  • Используйте потоки для больших файлов — это экономит память и повышает производительность
  • Всегда обрабатывайте ошибки — CSV файлы могут быть непредсказуемыми
  • Логируйте процесс — это поможет при отладке проблем в production
  • Тестируйте с реальными данными — файлы из внешних источников часто содержат сюрпризы
  • Используйте батчинг для обработки больших объёмов данных

node-csv идеально подходит для автоматизации серверных задач: от обработки логов до генерации отчётов. Это решение, которое масштабируется от простых скриптов до enterprise-приложений.

Помните: правильная обработка CSV файлов — это не просто парсинг данных, это целая философия работы с потоками информации. Используйте node-csv мудро, и он станет вашим надёжным помощником в мире серверной разработки!


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked