Как проанализировать CSV-файл, который содержит 15 миллионов строк данных в php - PullRequest
3 голосов
/ 22 марта 2020

У меня есть скрипт, который анализирует файл CSV и начинает проверять электронную почту. это прекрасно работает для 1000 строк. но на 15 миллионах строк это показывает ошибку исчерпанной памяти. размер файла составляет 400 МБ. какие-либо предложения? как разобрать и проверить их?

Характеристики сервера: Core i7 с 32 ГБ оперативной памяти

function parse_csv($file_name, $delimeter=',') {
  $header = false;
  $row_count = 0;
  $data = [];

  //  clear any previous results
  reset_parse_csv();

  // parse
  $file = fopen($file_name, 'r');
  while (!feof($file)) {
    $row = fgetcsv($file, 0, $delimeter);
    if ($row == [NULL] || $row === FALSE) { continue; }
    if (!$header) {
      $header = $row;
    } else {
      $data[] = array_combine($header, $row);
      $row_count++;
    }
  }
  fclose($file);

  return ['data' => $data, 'row_count' => $row_count];

}

function reset_parse_csv() {
  $header = false;
  $row_count = 0;
  $data = [];    
}

Ответы [ 3 ]

4 голосов
/ 22 марта 2020

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

Чтение PHP do c подходит очень хорошо для вашего случая (выделение мое):

Генератор позволяет вам писать код, который использует foreach для перебора набора данных без необходимости создания массива в памяти , что может привести к превышению лимита памяти или значительному времени обработки для генерации.

Примерно так:



function csv_read($filename, $delimeter=',')
{
    $header = [];
    $row = 0;
    # tip: dont do that every time calling csv_read(), pass handle as param instead ;)
    $handle = fopen($filename, "r"); 

    if ($handle === false) {
        return false;
    }

    while (($data = fgetcsv($handle, 0, $delimeter)) !== false) {

        if (0 == $row) {
            $header = $data;
        } else {
            # on demand usage
            yield array_combine($header, $data);
        }

        $row++;
    }
    fclose($handle);
}

А потом:

$generator = csv_read('rdu-weather-history.csv', ';');

foreach ($generator as $item) {
   do_something($item);
}

Основное отличие здесь заключается в следующем: вы не получаете (из памяти) и не используете все данные за один раз . Вы получаете элементы по требованию (как поток) и обрабатываете их вместо один элемент за раз . Это имеет огромное влияние на использование памяти.


PS: CSV-файл выше взят из: https://data.townofcary.org/api/v2/catalog/datasets/rdu-weather-history/exports/csv

1 голос
/ 23 марта 2020

Нет необходимости писать функцию генератора. SplFileObject также отлично работает.

$fileObj = new SplFileObject($file);

$fileObj->setFlags(SplFileObject::READ_CSV 
  | SplFileObject::SKIP_EMPTY 
  | SplFileObject::READ_AHEAD 
  | SplFileObject::DROP_NEW_LINE
);
$fileObj->setCsvControl(';');

foreach($fileObj as $row){
  //do something 
}

Я пробовал это с файлом "rdu-weather-history.csv" (> 500 КБ). memory_get_peak_usage () вернула значение 424k после foreach l oop. Значения должны обрабатываться построчно. Если создается двумерный массив, объем памяти, необходимый для примера, увеличивается до 8 Мбайт.

0 голосов
/ 23 марта 2020

Одна вещь, которую вы, возможно, можете попробовать, это Bulk Import to MySQL, которая может дать вам лучшую платформу для работы после ее импорта.

LOAD DATA INFILE '/home/user/data.csv' INTO TABLE CSVImport; where CSVimport columns match your CSV.

Бит левого поля, но в зависимости от на том, что ваш вариант использования может быть лучшим способом для анализа массивных наборов данных.

...