Php запись данных в csv файл. PHP: построчное чтение и обработка больших CSV-файлов. Импорт и экспорт CSV данных на PHP

  • 20.06.2020

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

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

Куда проще и удобней работать с csv-файлами .

Для примера сделаем небольшую программку. Она будет обрабатывать csv-файл сотрудников компании OX2.ru.

Создадим новую таблицу в Excel, содержащую следующие поля:

Сохраним таблицу как csv-файл, с разделителем поля «;».

У нас должен получиться такой файл:

"Иванов А.А.";"Программист компании OX2.ru";89255552332 "Сидоров А.Е.";"Дизайне компании OX2.ru";89161231212 "Пирожков А.Б.";"Арт-директор OX2.ru";84951232121 "Кулибин Б.А.";"Менеджер OX2.ru";89031233333

Ниже приведен исходный код с подробным описанием.

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

Так же в коде используется генерация исключений (Exception), о них читайте в следующих наших статьях.

_csv_file = $csv_file; //Записываем путь к файлу в переменную } else { //Если файл не найден то вызываем исключение throw new Exception("Файл "$csv_file" не найден"); } } public function setCSV(Array $csv) { //Открываем csv для до-записи, //если указать w, то ифнормация которая была в csv будет затерта $handle = fopen($this->_csv_file, "a"); foreach ($csv as $value) { //Проходим массив //Записываем, 3-ий параметр - разделитель поля fputcsv($handle, explode(";", $value), ";"); } fclose($handle); //Закрываем } /** * Метод для чтения из csv-файла. Возвращает массив с данными из csv * @return array; */ public function getCSV() { $handle = fopen($this->_csv_file, "r"); //Открываем csv для чтения $array_line_full = array(); //Массив будет хранить данные из csv //Проходим весь csv-файл, и читаем построчно. 3-ий параметр разделитель поля while (($line = fgetcsv($handle, 0, ";")) !== FALSE) { $array_line_full = $line; //Записываем строчки в массив } fclose($handle); //Закрываем файл return $array_line_full; //Возвращаем прочтенные данные } } try { $csv = new CSV("ox2.csv"); //Открываем наш csv /** * Чтение из CSV (и вывод на экран в красивом виде) */ echo "

CSV до записи:

"; $get_csv = $csv->getCSV(); foreach ($get_csv as $value) { //Проходим по строкам echo "Имя: " . $value . "
"; echo "Должность: " . $value . "
"; echo "Телефон: " . $value . "
"; echo "--------
"; } /** * Запись новой информации в CSV */ $arr = array("Антонов Б.А.;Админ OX2.ru;89031233333", "Колобков В.Б.;Босс OX2.ru;89162233333"); $csv->setCSV($arr); } catch (Exception $e) { //Если csv файл не существует, выводим сообщение echo "Ошибка: " . $e->getMessage(); } ?>

Основную работу с CSV файлами на себя берет класс CSV , Он имеет следующие методы:

setCSV(Array $csv) - записывает данные в csv-файл. Данные должны быть переданы массивом. Метод может дозаписывать csv файл, и создавать новый (читайте в описании).

Метод getCSV читает данные из csv файла, и возвращает двумерный массив следующего вида:

Array ( => Array ( => Иванов А.А. => Программист компании OX2.ru => 89255552332) => Array ( => Сидоров А.Е. => Дизайне компании OX2.ru => 89161231212) => Array ( => Пирожков А.Б. => Арт-директор OX2.ru => 84951232121) => Array ( => Кулибин Б.А. => Менеджер OX2.ru => 89031233333))

Пример достаточно простой, несмотря на то что получилось достаточно много кода.

Импорт данных из csv файлов будет полезен не только для обновление товаров в интернет-магазинах, но и для подгрузки/обновлении какой-либо информации на обычный сайт.

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

При этом стоимость создания сайта останется не изменой, а функционал и автоматизация сайта будет на уровень выше чем у конкурентов.

Разберём ещё несколько практических задач из области разработки PHP парсеров, связанных с импортом, сортировкой и экспортом CSV данных. Часто требуется представить результаты парсинга в виде CSV файла, далее такой файл можно импортировать в базу данных сайта. Средства импорта CSV данных часто поддерживаются стандартными средствами различных CMS.

CSV (Comma-Separated Values ) — текстовый формат, предназначенный для представления табличных данных. Из названия следует, что данные разделены запятыми, но могут использоваться и другие разделители, например, точка с запятой (DSV формат).

CSV файлы можно открывать разными текстовыми редакторами, а также программой EXCEL. Но бывают проблемы с кодировкой. Например, EXCEL плохо переваривает UTF-8 без BOM. Под EXCEL нужно данные просто в UTF-8 сохранять.

В тестовой задаче нам нужно отсортировать ряд CSV файлов по столбцу email. Файлы содержат списки организаций с различными контактными данными, при этом не у всех фирм есть электронная почта. Так, чтобы облегчить работу со списками удобно скриптом отсортировать списки и наверх поставить компании с известными почтовыми ящиками.

Скрипт index.php и каталог с входными данными следует разместитьть в папке csv-sorter .

Обработанные файлы будут записываться в директорию output .

Импорт и экспорт CSV данных на PHP

Листинг файла index.php

Сортировка CSV файлов по заполненности полей

Сортировка CSV файлов

count($row)) { $difference = count($header) - count($row); for ($i = 1; $i <= $difference; $i++) { $row = ""; } } } if($row != "sep=") { $data = $row; } } fclose($handle); } return $data; } /** * Функция сортировки массива по 1-му полю или N полей * * @param string|array $keys, string $order * * @return int */ function sort_arr_ncol($keys, $order = "ASC") { $order = ($order == "DESC") ? -1: 1; if(is_array($keys)) { //если сортировка по нескольким полям return function($a, $b) use ($keys, $order) { foreach($keys as $k) { if($a[$k] != $b[$k]) { return $order * (($a[$k] < $b[$k]) ? 1: -1); } } return 0; }; } else { //если сортировка по одному полю return function($a, $b) use ($keys, $order) { if ($a[$keys] == $b[$keys]) { return 0; } return $order * (($a[$keys] < $b[$keys]) ? 1: -1); }; } } /** * Функция преобразования массива в строку в CSV формате * * @param @param string $input, int $file_size, string $delimiter, string $enclosure * * @return string */ function str_putcsv($input, $file_size, $delimiter = ";", $enclosure = """) { // Open a memory "file" for read/write... $fp = fopen("php://temp", "r+"); // ... write the $input array to the "file" using fputcsv()... fputcsv($fp, $input, $delimiter, $enclosure); // ... rewind the "file" so we can read what we just wrote... rewind($fp); // ... read the entire line into a variable... $data = stream_get_contents($fp); // ... close the "file"... fclose($fp); // ... and return the $data to the caller, with the trailing newline from fgets() removed. return $data; //rtrim($data, "\r"); } // Параметры сортировщика $data_dir_name = "input"; // Каталог с исходными файлами $res_dir_name = "output"; // Каталог с отсортированными файлами $key_list_str = "email"; // Название столбца для сортировки (с нуля) if(!empty($_REQUEST["action"]) && $_REQUEST["action"] = "run") { if(!isset($_REQUEST["charset"])) { $charset = "no"; } else { $charset = $_REQUEST["charset"]; } $k = 0; $er = 0; $error_file_names = array(); $all_file_count = 0; $entries = scandir($data_dir_name); foreach($entries as $entry) { if(mb_strpos($entry, ".csv") !== false) { // Обрабатываем только CSV файлы $filepath_in = $data_dir_name . "/" . $entry; $file_size = filesize($filepath_in); $csv_data_arr = csv2array($filepath_in, $file_size); $header_arr = array_shift($csv_data_arr); $key_list = array_keys($header_arr, $key_list_str); usort($csv_data_arr, sort_arr_ncol($key_list, "ASC")); $csv_data_arr = array_merge($header_arr, $csv_data_arr); // Формируем строку для CSV файла $res_csv_file = ""; foreach($csv_data_arr as $key_row => $csv_data_arr_row) { $res_csv_file .= str_putcsv($csv_data_arr_row, $file_size); } if($charset == "yes") { $res_csv_file = iconv("WINDOWS-1251", "UTF-8", $res_csv_file); if($res_csv_file == false) { $res_csv_file = iconv("WINDOWS-1251", "UTF-8//IGNORE", $res_csv_file); $error_file_names = $entry; } } $filepath_out = $res_dir_name . "/" . $entry; file_put_contents($filepath_out, $res_csv_file) ? $k++ : $er++; $all_file_count++; } } echo "
Обработано файлов: " . $k . " из " . $all_file_count . ". Ошибок: " . $er . "
"; echo "

" . implode("

", $error_file_names) . "

"; } ?> :: Запуск...

Алгоритм работы следующий. Считываются файлы из заданного каталога. Далее данные преобразуются из CSV формата в PHP массив — это по сути импорт CSV.

Функция csv2array($filepath_in, $file_size) считывает CSV данные из файла и преобразует их в массив с помощью стандартной функции PHP fgetcsv() .

Список компаний до сортировки:

Я использую класс parseCSV для чтения данных из файлов csv. Это может обеспечить большую гибкость при чтении csv-файла.

это не проверено … но что-то вроде этого должно сделать трюк:

$row = 1; if (($handle = fopen("xxxxxxxxx.csv", "r")) !== FALSE) { while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { $num = count($data); echo "

$num fields in line $row:

\n"; $row++; for ($c=0; $c < $num; $c++) { $blackpowder = $data; $dynamit = implode(";", $blackpowder); $pieces = explode(";", $dynamit); $col1 = $pieces; $col2 = $pieces; $col3 = $pieces; $col4 = $pieces; $col5 = $pieces; mysql_query(" INSERT INTO `xxxxxx` (`xxx`,`xxx`,`xxx`,`xxxx`,`xxx`) VALUES ("".$col1."","".$col2."","".$col3."","".$col4."","".$col5."") "); } } }

$fp = fopen("ReadMe.csv","r") or die("can"t open file"); print "

\n"; while($csv_line = fgetcsv($fp,1024)) { print ""; for ($i = 0, $j = count($csv_line); $i < $j; $i++) { print ""; } print "\n"; } print "
".$csv_line[$i]."
"; fclose($fp) or die("can"t close file");

Подробнее

Попробуй это….

В PHP часто бывает полезно читать CSV-файл и получать доступ к его данным. Вот где функция fgetcsv () пригодится, она будет считывать каждую строку файла CSV и присваивать каждое значение массиву ARRAY. Вы можете определить разделитель в функции, а также просмотреть документы PHP для fgetcsv () для получения дополнительных опций и примеров.

Function readCSV($csvFile){ $file_handle = fopen($csvFile, "r"); while (!feof($file_handle)) { $line_of_text = fgetcsv($file_handle, 1024); } fclose($file_handle); return $line_of_text; } // Set path to CSV file $csvFile = "test.csv"; $csv = readCSV($csvFile); echo "

"; print_r($csv); echo "
";

Один лайнер для разбора CSV-файла в массив с помощью str_getcsv .

$csv = array_map("str_getcsv", file("qryWebsite.csv"));

Чтобы создать запрос базы данных, который сразу импортирует все значения в базу данных:

$query = "INSERT INTO tbl_name (a,b,c) VALUES " . implode(",", array_map(function($params) use (&$values) { $values = array_merge((array) $values, $params); return "(" . implode(",", array_fill(0, count($params), "?")) . ")"; }, $csv));

Это создаст подготовленное заявление с заполнителями вопросительных знаков, например:

INSERT INTO tbl_name (a,b,c) VALUES (?,?,?),(?,?,?),(?,?,?),(?,?,?)

А переменные $values будут одномерным массивом, который содержит значения для оператора. Одно из предостережений здесь состоит в том, что файл csv должен содержать менее 65 536 записей (максимальное количество заполнителей).

Один лайнер для разбора CSV-файла в массив

$csv = array_map("str_getcsv", file("data.csv"));

Вы можете попробовать приведенный ниже код. Он отлично подходит для меня. У меня есть комментарий, чтобы сделать его более понятным. Вы можете получить ссылку на этот код.


"; $id++; } echo "

"; fclose($handle); } //close the connection mysql_close($conn); в Entered data having id = " .$id. " successfully


"; $id++; } echo "

Congratulation all data successfully inserted

"; fclose($handle); } //close the connection mysql_close($conn);

10948|Book|Type1

SHA512||0||10948

0|10948|SHA512|

Я бы хотел, чтобы это было так

C3884fbd7fc122b5273262b7a0398e63|SHA512|Type1|Book

У меня нет доступа к реальной базе данных, есть ли способ сделать это? В основном ищет $id = $file1; if($file3 == $id) $id = $file1; if($file3 == $id) или что-то еще более эффективно.

Каждый файл CSV находится в любом месте от линий 100k-300k. Мне все равно, если это займет некоторое время, я могу просто позволить ей работать на EC2 некоторое время.

$data = array(); $fh = fopen("file1") or die("Unable to open file1"); while(list($id, $val1, $val2) = fgetcsv($fh, 0, "|")) { $data[$id]["val1"] = $val1; $data[$id]["val2"] = $val2; } fclose($fh); $fh = fopen("file2") or die ("Unable to open file2"); while(list($method, null, null, null, $id) = fgetcsv($fh, 0, "|")) { $data[$id]["method"] = $method; } fclose($fh); $fh = fopen("file3") or die("Unable to open file3"); while(list(null, $id, null, $hash) = fgetcsv($fh, 0, "|")) { $data[$id]["hash"] = $hash; } fclose($fh);

Нужно, но вы должны получить массив с данными, которые вы хотите. Вывод его в качестве другого csv оставлен как упражнение для читателя (подсказка: см. fputcsv()).

Все три файла имеют общее поле (т. Е. В вашем примере «10948» было общим для всех трех строк). Если вас не беспокоит использование большого количества памяти, вы можете загрузить все три файла в другом массиве, установив общее поле в качестве ключа массива и используя цикл foreach, чтобы собрать все три.

Например:

$result = array(); // File 1 $fh = fopen("file1"); while (($data = fgetcsv($fh, 0, "|")) !== FALSE) $result[$data] = $data; fclose($fh); // File 2 $fh = fopen("file2") while (($data = fgetcsv($fh, 0, "|")) !== FALSE) $result[$data] = array_merge($result[$data], $data); fclose($fh); // File 3 $fh = fopen("file3") while (($data = fgetcsv($fh, 0, "|")) !== FALSE) $result[$data] = array_merge($result[$data], $data); fclose($fh);

Я бы предложил выполнить слияние-сортировку с использованием основных инструментов unix:
a) сортировать файлы.CSV по столбцам, общим для каждого файла, sort -d "" -K? -К? -К?
b) Использование команды unix "join" для вывода записей, общих между парами файлов.CSV. Команда «join» работает только с двумя файлами за раз, поэтому вам придется «цепочки» результатов для нескольких источников данных:

# where "x" is field number from file A, and "y" is field number from file B sort -kx "fileA" sort -ky "fileB" join -1x -2y "fileA" "fileB" > file1 sort -kx "fileC" join -1x -2y "file1" "fileC" > file2 sort -kx "fileD" join -1x -2y "file2" "fileD" > file3 etc...

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

Если вам нужно написать свой собственный метод merge-sort в php: (Read Here: Merge Sort)

Простейшая реализация для объединения файлов.CSV является двухэтапной: a) unix сортирует ваши файлы, затем B) «объединяет» все источники параллельно, читая в записи от каждого, ища случай, когда ваше значение в ваши общие поля соответствуют всем другим источникам (JOIN в терминологии базы данных):
правило 1) Пропустить запись, которая меньше (<) ВСЕХ других источников.
правило 2) Когда общее значение записи равно (==), все другие источники имеют совпадение.
правило 3) Когда общее значение записи равно (==), является НЕКОТОРЫМ из другого источника, вы можете использовать логику «LEFT-JOIN», если хотите, иначе пропустите эту запись из всех источников.

Псевдокод для объединения нескольких файлов

Read 1st record from every data source; while "record exists from all data sources"; do for A in each Data-Source ; do set cntMissMatch=0 for B in each Data-Source; do if A.field < B.field then cntMissMatch+=1 end if end for if cntMissMatch == count(Data-Sources) then # found record with lowest values, skip it read next record in current Data-source; break; # start over again looking for lowest else if cntMissMatch == 0 then we have a match, process this record; read in next record from ALL data-sources ; break; # start over again looking for lowest else # we have a partial match, you can choose to have # "LEFT-JOIN" logic at this point if you choose, # where records are spit out even if they do NOT # match to ALL data-sources. end if end if end for done

Надеюсь, это поможет.