Hướng dẫn read large csv file php - đọc tệp csv lớn php

Tìm hiểu cách đọc hiệu quả một tệp CSV khổng lồ và phân tích dữ liệu của nó trong PHP.

Nếu bạn làm việc cho một công ty cung cấp dịch vụ phát triển ngay cả đối với các công ty khác trong cùng ngành, bạn có thể đã có nhiệm vụ "tuyệt vời" này là nhập một "cơ sở dữ liệu" khổng lồ từ khách hàng vào công cụ cơ sở dữ liệu mà công ty của bạn ưa thích. Ví dụ, trong công ty của chúng tôi, chúng tôi làm việc với MySQL và khách hàng của chúng tôi đã đưa ra một tệp CSV khoảng 25GB với các hàng ~ 7,5M.

Hướng dẫn read large csv file php - đọc tệp csv lớn php

Rõ ràng, do logic mà dự án yêu cầu, chúng tôi không thể chỉ cần nhập tệp vào cơ sở dữ liệu thông qua một công cụ như phpmyadmin, vì mỗi hàng trong CSV nên được sửa đổi để phù hợp với thiết kế cơ sở dữ liệu mới của chúng tôi.

Trong bài viết này, chúng tôi sẽ giải thích cho bạn cách tiếp cận của chúng tôi để đọc một cách hiệu quả một tệp CSV khổng lồ trong PHP.

1. Chia tệp của bạn thành các khối nhỏ hơn

Để bắt đầu, khi chúng tôi nói về các tệp khổng lồ, chúng tôi không nói về các tệp có hàng 50k hoặc 70k, chúng tôi nói về hàng triệu hàng như trong ví dụ này, với tệp CSV là 25GB. Vì vậy, cách tiếp cận chính xác cho các trường hợp như vậy là không hoạt động trực tiếp với tệp, mà là với các tệp nhỏ hơn.

Tệp càng nhỏ thì càng tốt để có hiệu suất tối ưu và kiểm soát tập lệnh của bạn, không chỉ về quan điểm hiệu suất mà cả logic. Chúng tôi đã viết một bài viết trước đây về cách chia các bộ dữ liệu CSV lớn thành các khối nhỏ hơn bằng cách sử dụng CSV Splitter, một công cụ cho Windows 10. Tất nhiên bạn có thể làm điều tương tự bằng cách sử dụng một cách tiếp cận khác, nhưng, bạn có ý tưởng đúng không? Chia tệp thành các khối nhỏ hơn có thể dễ dàng được xử lý bởi các tập lệnh của bạn sau này.Split the file into smaller chunks that can be easily processed by your scripts later.

2. Thực hiện tập lệnh đọc và lặp

Để đọc tệp, chúng tôi sẽ sử dụng chức năng fopen của PHP, chức năng Inbuilt này được sử dụng để chỉ cần mở một tệp từ URL cục bộ, nó được sử dụng để liên kết tài nguyên với hơi nước. Nó mong đợi là đối số thứ hai, chế độ mà chúng ta sẽ hoạt động, trong trường hợp này, chỉ đọc với định danh r. Phương thức trả về một con trỏ tệp miễn là tệp tồn tại, nếu không nó sẽ trả về sai trong trường hợp lỗi.

Chúng tôi sẽ đọc tệp bằng phương pháp này và sẽ lưu trữ con trỏ vào biến $handle. Tạo cũng như biến sẽ lưu trữ số dòng hiện tại khi chúng tôi sẽ lặp lại trên các hàng với một vòng lặp được điều khiển (trong khi). Với một vòng lặp trong thời gian, chúng tôi sẽ lặp lại trên từng hàng của tệp, xác minh điều kiện FGET luôn trả về một số nội dung.

Chức năng FGETS của PHP trả về một dòng từ một tệp mở với fopen và nó trả về sai khi không còn gì để đọc. Sau đó, bên trong vòng lặp trong khi bạn sẽ có thể phân tích chuỗi CSV thô với hàm str_getcsv. Có các công cụ cơ bản được triển khai, bạn sẽ sẵn sàng sửa đổi tập lệnh để làm bất cứ điều gì bạn cần làm với hiệu suất tốt nhất có thể trong PHP:

// Read a CSV file
$handle = fopen("my_huge_csv_file.csv", "r");

// Optionally, you can keep the number of the line where
// the loop its currently iterating over
$lineNumber = 1;

// Iterate over every line of the file
while (($raw_string = fgets($handle)) !== false) {
    // Parse the raw csv string: "1, a, b, c"
    $row = str_getcsv($raw_string);

    // into an array: ['1', 'a', 'b', 'c']
    // And do what you need to do with every line
    var_dump($row);
    
    // Increase the current line
    $lineNumber++;
}

fclose($handle);

Ưu điểm của phương pháp này là:

  • Bạn không trực tiếp đọc toàn bộ tệp trong bộ nhớ giống như file_get_contents, vì vậy lượng bộ nhớ tối đa cần thiết để chạy tập lệnh phụ thuộc vào dòng dài nhất trong dữ liệu đầu vào.
  • Khá dễ đọc và hiểu.

Happy Coding ❤!

Sau khi đấu tranh rất nhiều, cuối cùng tôi cũng tìm thấy một giải pháp tốt, cũng có thể giúp đỡ người khác. Khi tôi đã thử tệp CSV 2.367kB chứa 18226 hàng, thời gian ít nhất được thực hiện bởi các tập lệnh PHP khác nhau là (1) từ tài liệu php.net fgetcsv có tên CsvImporter và (2)

.

Giải pháp tốt nhất tôi tìm thấy sử dụng tổng số 3.0644409656525 bao gồm thêm vào cơ sở dữ liệu và một số kiểm tra có điều kiện. Phải mất 11 giây trong việc xử lý một tệp 8MB. Giải pháp là:

$csvInfo = analyse_file($file, 5);
    $lineSeperator = $csvInfo['line_ending']['value'];
    $fieldSeperator = $csvInfo['delimiter']['value'];
    $columns = getColumns($file);
    echo '
========Details========
'; echo 'Line Sep: \t '.$lineSeperator; echo '
Field Sep:\t '.$fieldSeperator; echo '
Columns: ';print_r($columns); echo '
========Details========
'; $ext = pathinfo($file, PATHINFO_EXTENSION); $table = str_replace(' ', '_', basename($file, "." . $ext)); $rslt = table_insert($table, $columns); if($rslt){ $query = "LOAD DATA LOCAL INFILE '".$file."' INTO TABLE $table FIELDS TERMINATED BY '$fieldSeperator' "; var_dump(addToDb($query, false)); } function addToDb($query, $getRec = true){ //echo '
Query : '.$query; $con = @mysql_connect('localhost', 'root', ''); @mysql_select_db('rtest', $con); $result = mysql_query($query, $con); if($result){ if($getRec){ $data = array(); while ($row = mysql_fetch_assoc($result)) { $data[] = $row; } return $data; }else return true; }else{ var_dump(mysql_error()); return false; } } function table_insert($table_name, $table_columns) { $queryString = "CREATE TABLE " . $table_name . " ("; $columns = ''; $values = ''; foreach ($table_columns as $column) { $values .= (strtolower(str_replace(' ', '_', $column))) . " VARCHAR(2048), "; } $values = substr($values, 0, strlen($values) - 2); $queryString .= $values . ") "; //// echo $queryString; return addToDb($queryString, false); } function getColumns($file){ $cols = array(); if (($handle = fopen($file, 'r')) !== FALSE) { while (($row = fgetcsv($handle)) !== FALSE) { $cols = $row; if(count($cols)>0){ break; } } return $cols; }else return false; } function analyse_file($file, $capture_limit_in_kb = 10) { // capture starting memory usage $output['peak_mem']['start'] = memory_get_peak_usage(true); // log the limit how much of the file was sampled (in Kb) $output['read_kb'] = $capture_limit_in_kb; // read in file $fh = fopen($file, 'r'); $contents = fread($fh, ($capture_limit_in_kb * 1024)); // in KB fclose($fh); // specify allowed field delimiters $delimiters = array( 'comma' => ',', 'semicolon' => ';', 'tab' => "\t", 'pipe' => '|', 'colon' => ':' ); // specify allowed line endings $line_endings = array( 'rn' => "\r\n", 'n' => "\n", 'r' => "\r", 'nr' => "\n\r" ); // loop and count each line ending instance foreach ($line_endings as $key => $value) { $line_result[$key] = substr_count($contents, $value); } // sort by largest array value asort($line_result); // log to output array $output['line_ending']['results'] = $line_result; $output['line_ending']['count'] = end($line_result); $output['line_ending']['key'] = key($line_result); $output['line_ending']['value'] = $line_endings[$output['line_ending']['key']]; $lines = explode($output['line_ending']['value'], $contents); // remove last line of array, as this maybe incomplete? array_pop($lines); // create a string from the legal lines $complete_lines = implode(' ', $lines); // log statistics to output array $output['lines']['count'] = count($lines); $output['lines']['length'] = strlen($complete_lines); // loop and count each delimiter instance foreach ($delimiters as $delimiter_key => $delimiter) { $delimiter_result[$delimiter_key] = substr_count($complete_lines, $delimiter); } // sort by largest array value asort($delimiter_result); // log statistics to output array with largest counts as the value $output['delimiter']['results'] = $delimiter_result; $output['delimiter']['count'] = end($delimiter_result); $output['delimiter']['key'] = key($delimiter_result); $output['delimiter']['value'] = $delimiters[$output['delimiter']['key']]; // capture ending memory usage $output['peak_mem']['end'] = memory_get_peak_usage(true); return $output; }