Как разбить файл CSV размером 6 ГБ на куски с помощью php

Я начинающий разработчик, изучающий php. Задача, которую мне нужно сделать, это загрузить 6-гигабайтный CSV-файл, содержащий данные, в базу данных. Мне нужно получить доступ к данным, т.е. прочитать файл через файл controller.php, а затем разделить этот огромный CSV-файл в 10 000 строк вывода CSV-файлов и запись данных в эти выходные CSV-файлы. Я выполняю эту задачу уже неделю и еще не разобрался. Не могли бы вы, ребята, помочь мне в решении этой проблемы.

<?php

namespace App\Http\Controllers;
use Illuminate\Queue\SerializesModels;

use App\User;
use DateTime;
use Illuminate\Http\Request;
use Storage;
use Validator;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use Queue;
use App\model;


class Name extends Controller
{


     public function Post(Request $request)
     {

         if($request->hasfile('upload')){
            ini_set('auto_detect_line_endings', TRUE);
                $main_input = $request->file('upload');
                $main_output = 'output';
                $filesize = 10000;
                $input = fopen($main_input,'r');
                $rowcount = 0;
                $filecount = 1;
                $output = '';

                // echo "here1";
                while(!feof($input)){
                    if(($rowcount % $filesize) == 0){
                        if($rowcount>0) { 
                            fclose($output);
                        }
                    $output = fopen(storage_path(). "/tmp/".$main_output.$filecount++ . '.csv','w');
                    }
                    $data = fgetcsv($input);
                    print_r($data);

                    if($data) {

                        fputcsv($output, $data);
                    }

                    $rowcount++;
                }
                fclose($output);
        }
     }
}  

person Hemanth    schedule 09.10.2018    source источник
comment
Ваши загружаемые файлы всегда будут иметь размер 6 ГБ? или это отдельный/уникальный случай инициализации базы данных?   -  person Kenny Horna    schedule 09.10.2018
comment
Не все файлы такие большие. В этой задаче да, файл представляет собой файл CSV размером 6 ГБ, и да, это уникальный случай.   -  person Hemanth    schedule 09.10.2018
comment
Поэтому, возможно, было бы лучше загрузить файл csv непосредственно в вашу базу данных (если вы используете MySQL, вы можете сделать это визуально с помощью PHPMyAdmin), а затем структурировать свой проект, создавая модели для ваших таблиц и так далее.   -  person Kenny Horna    schedule 09.10.2018
comment
Если речь идет только об одном файле, не могли бы вы сделать это вручную?   -  person B001ᛦ    schedule 09.10.2018
comment
Я не понимаю тебя @HCK. Не могли бы вы немного рассказать об этом. Также код, который я разместил выше, не работает. Не могли бы вы проверить, где я делаю ошибку   -  person Hemanth    schedule 09.10.2018
comment
@B001ᛦ Нет, друг мой.   -  person Hemanth    schedule 09.10.2018
comment
@Hemanth, как я уже сказал, это поможет вам, если вы используете этот клиент для более наглядного управления своими базами данных (PHPMyAdmin), вот ссылка на то, как загрузить ваш файл: Как импортировать или экспортировать csv в базу данных   -  person Kenny Horna    schedule 09.10.2018
comment
@HCK, да, это помогает, но дело в том, что мне нужно кодировать его по частям.   -  person Hemanth    schedule 09.10.2018
comment
Вы получаете какие-либо ошибки? Вы уже пробовали это с гораздо меньшим файлом?   -  person Martin Cup    schedule 09.10.2018
comment
@MartinCup, да, Мартин, я пробовал код с меньшим тестовым файлом. Выходной файл создается с заголовками и данными в одной строке. Данные не сортируются по соответствующим заголовкам. Данные размещаются по заголовкам в той же строке1.   -  person Hemanth    schedule 09.10.2018
comment
[ссылка] stackoverflow.com/questions/16732590/ Моя задача аналогична этой задаче.   -  person Hemanth    schedule 09.10.2018
comment
Почему вы открываете файл с помощью «php://output»? Что происходит, когда вы убираете этот параметр?   -  person Martin Cup    schedule 09.10.2018
comment
Это поток только для записи, который позволяет вам записывать в механизм выходного буфера так же, как печать и эхо. Исключение этого также дало бы мне ту же ошибку.   -  person Hemanth    schedule 09.10.2018


Ответы (2)


Возможно, это потому, что вы создаете новый обработчик файлов $output для каждого файла iteration.

Я внес некоторые коррективы, так что мы создаем файл только тогда, когда rowCount = 0, и закрываем его, когда достигается fileSize. Также rowCount нужно сбрасывать на 0 каждый раз, когда мы закрываем файл.

public function Post(Request $request)
     {

         if($request->hasfile('upload')){
            ini_set('auto_detect_line_endings', TRUE);
                $main_input = $request->file('upload');
                $main_output = 'output';
                $filesize = 10000;
                $input = fopen($main_input,'r');
                $rowcount = 0;
                $filecount = 1;
                $output = '';

                // echo "here1";
                while(!feof($input)){
                    if ($rowCount == 0) {
                        $output = fopen('php://output', storage_path(). "/tmp/".$main_output.$filecount++ . '.csv','w');
                    }
                    if(($rowcount % $filesize) == 0){
                        if($rowcount>0) { 
                            fclose($output);
                            $rowCount = 0;
                            continue;
                        }

                    }
                    $data = fgetcsv($input);
                    print_r($data);

                    if($data) {

                        fputcsv($output, $data);
                    }

                    $rowcount++;
                }
                fclose($output);
        }
     }
person xtothea    schedule 09.10.2018
comment
большое спасибо, это сработало отлично для меня. - person Hemanth; 09.10.2018
comment
Эй, Кстотея. Код отлично работает для образцов файлов данных, а также в то же время я нахожу объект запроса 413 слишком большой ошибкой при загрузке большого файла, скажем, файла csv размером 6 ГБ. Не могли бы вы помочь мне - person Hemanth; 09.10.2018
comment
Рад, что смог помочь. Ошибка 413 — отдельная тема. Вероятно, ограничение веб-сервера. Ознакомьтесь с stackoverflow. ком/вопросы/24306335/ - person xtothea; 09.10.2018
comment
Привет Xtothea, я внес изменения в файл php.ini, изменил memory_limit, file_upload_size и другие необходимые изменения. Перезапустил веб-сервер. Мне удалось сгенерировать только один выходной файл с 10000 строк, и после этого он возвращает ту же ошибку 413 Request Entity too big. Не могли бы вы помочь мне, где я делаю неправильно. - person Hemanth; 10.10.2018

Вот рабочий пример разделения CSV-файла по количеству строк (определяется$numberOfLines). Просто укажите свой путь в $filePath и запустите скрипт в оболочке, например:

php -f convert.php

код скрипта: convert.php

<?php

$filePath = 'data.csv';
$numberOfLines = 10000;

$file = new SplFileObject($filePath);

//get header of the csv
$header = $file->fgets();

$outputBuffer = '';
$outputFileNamePrefix = 'datasplit-';

$readLinesCount = 1;
$readlLinesTotalCount = 1;
$suffix=0;

$outputBuffer .= $header;

while ($currentLine = $file->fgets()) {
    $outputBuffer .= $currentLine;
    $readLinesCount++;
    $readlLinesTotalCount++;

    if ($readLinesCount >= $numberOfLines) {
        $outputFilename = $outputFileNamePrefix . $suffix . '.csv';
        file_put_contents($outputFilename, $outputBuffer);
        echo 'Wrote '  . $readLinesCount . ' lines to: ' . $outputFilename . PHP_EOL;    

        $outputBuffer = $header;
        $readLinesCount = 0;
        $suffix++;
    }
}

//write remainings of output buffer if it is not empty
if ($outputBuffer !== $header) {
    $outputFilename = $outputFileNamePrefix . $suffix . '.csv';
    file_put_contents($outputFilename, $outputBuffer);
    echo 'Wrote (last time)'  . $readLinesCount . ' lines to: ' . $outputFilename . PHP_EOL;

    $outputBuffer = '';
    $readLinesCount = 0;

}

вы не сможете преобразовать такой объем данных за одно выполнение php, если он запускается из Интернета, из-за максимального времени выполнения php-скриптов, которое обычно составляет 30-60 секунд, и для этого есть причина - не пытайтесь чтобы расширить его до некоторого огромного числа. Если вы хотите, чтобы ваш скрипт работал даже часами, вам нужно вызвать его из командной строки, но вы также можете вызвать его аналогичным образом из другого скрипта (например, из вашего контроллера). Вы делаете это следующим образом:

exec('php -f convert.php');

вот и все.

Контроллер, который у вас есть, не сможет сказать, были ли преобразованы все данные, потому что до того, как это произойдет, он будет завершен. Что вы можете сделать, так это написать свой собственный код в convert.php, который обновляет некоторые поля в базе данных, а другой контроллер в вашем приложении может прочитать это и распечатать пользователю ход выполнения convert.php.

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

Имейте в виду, что если вы что-то разделили и присоединились к другому местоположению, у вас может возникнуть проблема с ошибкой в ​​​​этом процессе. Метод, который гарантирует, что вы успешно разделили, передали, объединили свои данные, заключается в вычислении HASH, т.е. SHA-1 весь файл размером 6 ГБ перед разделением, отправьте этот ХЭШ в место назначения, где необходимо объединить все небольшие части данных, объединить их в один файл размером 6 ГБ, вычислить ХЭШ этого файла и сравнить с тем, который был отправлен. Имейте в виду, что каждая из небольших частей ваших данных после разделения имеет свой собственный заголовок, чтобы CSV-файл был легко интерпретировать (импортировать), где в исходном файле у вас есть только одна строка заголовка.

person Jimmix    schedule 09.10.2018