php如何解析一个包含1500万行数据的csv文件

How to parse a csv file that contains 15 million lines of data in php

我有一个脚本可以解析 CSV 文件并开始验证电子邮件。这适用于 1000 行。但在 1500 万行上它显示内存耗尽错误。文件大小为 400MB。有什么建议么?如何解析和验证它们?

服务器规格:Core i7,32GB Ram

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 = [];    
}

迭代大型数据集(文件行等)并将其推入数组会增加内存使用量,这与处理的项目数量成正比。 所以文件越大,内存使用量就越大——在这种情况下。 如果需要在处理 CSV 数据之前格式化 CSV 数据的功能,支持它 generators 听起来是个好主意。

阅读 PHP 文档它非常适合你的情况(强调我的):

A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate.

像这样:



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);
}

这里的主要区别是: 您不会(从内存中)获取并消耗所有数据一次。您按需(如流)获取项目并处理它,一次一个项目。它对内存使用有巨大影响。


P.S.: 上面的 CSV 文件取自: https://data.townofcary.org/api/v2/catalog/datasets/rdu-weather-history/exports/csv

没必要写生成器函数。 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" (> 500KB)。 memory_get_peak_usage() 在 foreach 循环后返回值 424k。值必须逐行处理。 如果创建二维数组,示例所需的存储 space 增加到 8 MB 以上。

您可以尝试的一件事是批量导入 MySQL,这可能会在导入后为您提供更好的工作平台。

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

有点左手边的建议,但根据您的用例,它可能是解析海量数据集的更好方法。