очень прошу помочь мне оптимизировать скрипт (xml парсер) по памяти
Парсер считывает данные (с 1С выгрузки в xml - каткгории, атрибуты, товары) и кладет в базу магазина на опенкарт, написал его я сам на базе готового класса, (просьба готовые модули не предлагать, это специфичное решение в силу многих причин)
Параметры сервера: Centos 4 Гигабайта оперативы, 4 ядра процессора
xml файл: 70 мб (более 25 000 товаров с атрибутами) более 900 000 строк в xml
Проблема: скрипт не тянет большие выгрузки и сервер ложится с ошибкой Gateway Timeout не дав скрипту завершить полностью работу.
Думаю скрипт будет интересен для обсуждения, так как он затрагивает память, ну и мой опыт (пока очень маленький). Уверен данный скрипт можно значительно сократить, у меня вечно получается все делать обходными путями, просьба сильно не пинать, опыт не большой, по Вашим ответам обязательно внесу правки, рассчитываю на вас.
В скрипте использован готовый авторский класс с харбра Simple XML Riader для парсинга больших xml файлов, ссылки:
описание класса от автора http://dpyatkov.ru/2012/01/12/simplexmlreader/
класс на гитхаб https://github.com/dkrnl/SimpleXMLReader
В моем коде меня смущает несколько раз подключение xml файла в import.xml
например:
$reader2->open($file);
и постоянное создание нового объекта, пытался по другому, но не получилось.
Также наверно куча нелепых подключений которые жрут память, о которых я не знаю, просьба всех прочитавших этот вопрос откликнуться на помощь, заранее всех благодарю. Буду благодарен абсолютно любым советам и предложениям по моему коду.
Итак мой код:
файл import.php
<pre>
//ini_set("display_errors",1);
set_time_limit(0);
//mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$hostname = "localhost";
$username = "юзер";
$password = "пароль";
$dbName = "knigi";/* Таблица MySQL, в которой хранятся данные *//* создать соединение */
$dbu = mysql_connect($hostname,$username,$password) or die("Не могу создать соединение ");
@mysql_select_db("$dbName") or die("Не могу выбрать базу данных ");
mysql_set_charset('utf8',$dbu);
include 'functions.php';
$nowdate = date("Y-m-d H:i:s");
//почистим данные перед запуском на время тестов
//чистим связи
mysql_query("TRUNCATE link_attribute");
mysql_query("TRUNCATE link_attribute_group");
mysql_query("TRUNCATE link_cat");
mysql_query("TRUNCATE link_izdat");
mysql_query("TRUNCATE link_product");
mysql_query("TRUNCATE link_attribute");
//чистим атрибуты
mysql_query("TRUNCATE import_attribute_values");
mysql_query("TRUNCATE os_attribute");
mysql_query("TRUNCATE os_attribute_description");
mysql_query("TRUNCATE os_attribute_group");
mysql_query("TRUNCATE os_attribute_group_description");
//чистим производителей
mysql_query("TRUNCATE os_manufacturer");
mysql_query("TRUNCATE os_manufacturer_description");
mysql_query("TRUNCATE os_manufacturer_to_store");
//чистим данные категорий
mysql_query("TRUNCATE os_category");
mysql_query("TRUNCATE os_category_description");
mysql_query("TRUNCATE os_category_filter");
mysql_query("TRUNCATE os_category_path");
mysql_query("TRUNCATE os_category_to_layout");
mysql_query("TRUNCATE os_category_to_store");
//чистим данные товаров
mysql_query("TRUNCATE os_product");
mysql_query("TRUNCATE os_product_attribute");
mysql_query("TRUNCATE os_product_description");
mysql_query("TRUNCATE os_product_discount");
mysql_query("TRUNCATE os_product_filter");
mysql_query("TRUNCATE os_product_image");
mysql_query("TRUNCATE os_product_to_category");
mysql_query("TRUNCATE os_product_to_layout");
mysql_query("TRUNCATE os_product_to_store");
mysql_query("TRUNCATE os_url_alias");
include "/var/www/www-root/data/www/Сервер/library/SimpleXMLReader.php";
$file = "import3.xml";
$reader = new SimpleXMLReader;
$reader->open($file);
$reader->registerCallback("Группы", function($reader) {
$xml = $reader->expandSimpleXml(); // copy of the current node as a SimpleXMLElement object
$num1 = $xml->count();
for ($i = 0; $i <= $num1-1; $i++) {
//записываем родительские категории в базу
$CatImportId = $xml->Группа[$i]->Ид;
$CatImportName = $xml->Группа[$i]->Наименование;
$CatImportImage = $xml->Группа[$i]->Картинка;
$CatImportSort = $xml->Группа[$i]->Сортировка;
$parent = get_cat($CatImportId,$CatImportName,$CatImportImage,$CatImportSort,0,0);
$CatChildImportName = $xml->Группа[$i]->Наименование;
$num2 = $xml->Группа[$i]->Группы->Группа->count();
for ($i2 = 0; $i2 <= $num2-1; $i2++) {
$CatChildImportId = $xml->Группа[$i]->Группы->Группа[$i2]->Ид;
$CatChildImportName = $xml->Группа[$i]->Группы->Группа[$i2]->Наименование;
$CatChildImportImage = $xml->Группа[$i]->Группы->Группа[$i2]->Картинка;
$CatChildImportSort = $xml->Группа[$i]->Группы->Группа[$i2]->Сортировка;
$parent2 = get_cat($CatChildImportId,$CatChildImportName,$CatChildImportImage,$CatChildImportSort,2,$parent);
$num3 = $xml->Группа[$i]->Группы->Группа[$i2]->count();
for ($i3 = 0; $i3 <= $num3-1; $i3++) {
//if(isset($xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Ид)){
$CatChild2ImportId = $xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Ид;
//}
//if(isset($xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Наименование)){
$CatChild2ImportName = $xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Наименование;
//}
//if(isset($xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Картинка)){
$CatChild2ImportImage = $xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Картинка;
//}
//if(isset($xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Сортировка)){
$CatChild2ImportSort = $xml->Группа[$i]->Группы->Группа[$i2]->Группы->Группа[$i3]->Сортировка;
//}
$parent3 = get_cat($CatChild2ImportId,$CatChild2ImportName,$CatChild2ImportImage,$CatChild2ImportSort,3,$parent2);
}
}
}
unset($xml);
});
$reader->parse();
$reader->close();
$reader2 = new SimpleXMLReader;
$reader2->open($file);
$reader2->registerCallback("Свойства", function($reader2) {
$array2 = $reader2->expandSimpleXml(); // copy of the current node as a SimpleXMLElement object
//парсим базу справочников
$num4 = $array2->Свойство->count();
//создаем новую группу атрибутов
mysql_query("INSERT INTO `os_attribute_group` VALUES (NULL, '0');");
//получаем вставленный id
$AttributeGroupId=mysql_insert_id();
//сохраняем в связи
//mysql_query("INSERT INTO `link_attribute_group` VALUES ('$AttributeGroupId', '$id');");
//задаем название группы
mysql_query("INSERT INTO `os_attribute_group_description` VALUES ('$AttributeGroupId', '1', 'Характеристики');");
//перебираем группы атрибутов
for ($i4 = 0; $i4 <= $num4-1; $i4++) {
$array = $array2->Свойство[$i4]->ВариантыЗначений->Справочник;
$id = $array2->Свойство[$i4]->Ид;
$name = $array2->Свойство[$i4]->Наименование;
if ($name == 'Издательство'){ //издателя кидаем в производители
$num5 = $array->count();
for ($i5 = 0; $i5 <= $num5-1; $i5++) {
$IdImportIzdat = $array[$i5]->ИдЗначения;
$NameImportIzdat = $array[$i5]->Значение;
if ($NameImportIzdat == ''){}else{
mysql_query("INSERT INTO `os_manufacturer` VALUES (NULL, '$NameImportIzdat', 'catalog/demo/htc_logo.jpg', '0');");
$IzdatId=mysql_insert_id();
//сохраняем в связи
mysql_query("INSERT INTO `link_izdat` VALUES ('$IzdatId', '$IdImportIzdat');");
mysql_query("INSERT INTO `os_manufacturer_description` VALUES ('$IzdatId', '1', '$NameImportIzdat', 'Пример текста в описания издателя', '$NameImportIzdat', '$NameImportIzdat', '', '');");
//привязываем производителя к магазину
mysql_query("INSERT INTO `os_manufacturer_to_store` VALUES ('$IzdatId', '0');");
$importId = $array[$i5]->Значение;
}
}
}else{
mysql_query("INSERT INTO `os_attribute` VALUES (NULL, '$AttributeGroupId', '0');");
$AttributeId=mysql_insert_id();
//сохраняем в связи
mysql_query("INSERT INTO `link_attribute` VALUES ('$AttributeId', '$id');");
//задаем имя значения атрибута
mysql_query("INSERT INTO `os_attribute_description` VALUES ('$AttributeId', '1', '$name');");
$num5 = $array->count();
for ($i5 = 0; $i5 <= $num5-1; $i5++) {
$IdImportAtt = $array[$i5]->ИдЗначения;
$NameImportAtt = $array[$i5]->Значение;
if ($NameImportAtt == ''){}else{
mysql_query("INSERT INTO `import_attribute_values` VALUES (NULL, '$IdImportAtt', '$AttributeId', '$NameImportAtt', '')");
//$AttValueId=mysql_insert_id();
}
}
}
//get_attribute ($id, $name, $array);
}
unset($array2);
});
$reader2->parse();
$reader2->close();
$reader3 = new SimpleXMLReader;
$reader3->open($file);
$reader3->registerCallback("Товары", function($reader3) {
$array3 = $reader3->expandSimpleXml(); // copy of the current node as a SimpleXMLElement object
///поехали парсить товары
$num = $array3->Товар->count();
for ($i = 0; $i <= $num-1; $i++) {
$idProduct = $array3->Товар[$i]->Ид;
$groupId = $array3->Товар[$i]->Группы->Ид;
$nameProduct = $array3->Товар[$i]->Наименование;
$titleProduct = $array3->Товар[$i]->Титл;
$descProduct = $array3->Товар[$i]->Описание;
$imgProduct = $array3->Товар[$i]->Картинка;
$array = $array3->Товар[$i];
get_product($idProduct, $groupId, $nameProduct, $titleProduct, $descProduct, $imgProduct, $array);
}
unset($array3);
});
$reader3->parse();
$reader3->close();
$start = microtime(true);
// тело скрипта
echo'All good!!!!!!!!!!!!!!!!!<br>';
echo 'time: '.(microtime(true) - $start).' sec.<br>Memory:';
echo memory_get_usage() . "\n"; // 36640
</pre>
Файл functions.php
function get_cat($id, $name, $img, $sort = 0, $level = 0, $parent = 0){ ///функция парсинга категорий $date = date("Y-m-d"); $nowdate = date("Y-m-d H:i:s"); if($name == ''){}else{ //проверяем есть ли заголовок что не было пустых категорий $img = str_replace('\\', '/', $img); //заменим все обратные слеши в пути картинки на нормальные if($level == 0){ mysql_query("INSERT INTO `os_category` VALUES (NULL, 'catalog/$img', '$parent', '', '0', 'fa none', 'no_image.png', '1', '3', '$sort', '1', '$nowdate', '$nowdate')"); }else{ mysql_query("INSERT INTO `os_category` VALUES (NULL, 'catalog/$img', '$parent', '', '0', 'fa none', 'no_image.png', '1', '1', '$sort', '1', '$nowdate', '$nowdate')"); } $img = str_replace('\\', '/', $img); //заменим все обратные слеши в пути картинки на нормальные $CatId=mysql_insert_id(); mysql_query("INSERT INTO `link_cat` VALUES('$CatId', '$id');"); mysql_query("INSERT INTO `os_category_description` VALUES ('$CatId', '1', '$name', '', '$name', '$name', '$name', '')"); mysql_query("INSERT INTO `os_category_path` VALUES('$CatId', '$CatId', '$level');"); mysql_query("INSERT INTO `os_category_to_layout` VALUES('$CatId', '0', '0');"); mysql_query("INSERT INTO `os_category_to_store` VALUES('$CatId', '0');"); //прописываем seourl категорий $seourl = $CatId; mysql_query("INSERT INTO `os_url_alias` VALUES (NULL, 'category_id=$CatId', '$seourl');"); return $CatId; } } function get_product($id, $groupId, $name, $title, $desc, $img, $array){ ///функция парсинга товаров $date = date("Y-m-d"); $nowdate = date("Y-m-d H:i:s"); $TegTitle = $title; $TegDescription = 'Продажа книг для взрослых и детей. '.$name.' выгодно. Огромный выбор по самым различным тематикам'; //тег дескрипшен $TegKeywords = $name.', купить, выгодно, в магазине, онлайн'; if($name == ''){}else{ //проверяем есть ли заголовок что не было пустых товаров $myrow = mysql_fetch_array(mysql_query("SELECT `idstore`, `idimport` FROM `link_cat` WHERE `idimport` = '$groupId'")); //смотрим связанную категорию по Ид $cat_to_store = $myrow['idstore']; $img = str_replace('\\', '/', $img); //заменим все обратные слеши в пути картинки на нормальные mysql_query("INSERT INTO `os_product` VALUES (NULL, '', '', '', '', '', '', '', '', '100', '7', 'catalog/$img', '', '1', '0', '300', '0', '0', '$date', '0', '1', '0', '0', '0', '1', '1', '1', '1', '1', '0', '$nowdate', '$nowdate')"); $ProductId=mysql_insert_id(); $array1 = $array->ЗначенияРеквизитов->ЗначениеРеквизита; $num1 = $array1->count(); for ($i1 = 0; $i1 Наименование; $importValue = $array1[$i1]->Значение; if($importName == 'Код:'){ mysql_query("UPDATE `os_product` SET `upc`='$importValue' WHERE `product_id` = '$ProductId'"); } if($importName == 'Артикул:'){ mysql_query("UPDATE `os_product` SET `sku`='$importValue' WHERE `product_id` = '$ProductId'"); } elseif($importName == 'ISBN:'){ mysql_query("UPDATE `os_product` SET `isbn`='$importValue' WHERE `product_id` = '$ProductId'"); } elseif($importName == 'Год издания:'){ mysql_query("UPDATE `os_product` SET `model`='$importValue' WHERE `product_id` = '$ProductId'"); } elseif($importName == 'Формат:'){ mysql_query("UPDATE `os_product` SET `ean`='$importValue' WHERE `product_id` = '$ProductId'"); } elseif($importName == 'Переплет:'){ mysql_query("UPDATE `os_product` SET `jan`='$importValue' WHERE `product_id` = '$ProductId'"); } elseif($importName == 'Страниц:'){ mysql_query("UPDATE `os_product` SET `mpn`='$importValue' WHERE `product_id` = '$ProductId'"); }else{} } mysql_query("INSERT INTO `link_product` VALUES('$ProductId', '$id');"); mysql_query("INSERT INTO `os_product_description` VALUES ('$ProductId', '1', '$name', '<p>$desc</p>', '', '', '$TegTitle', '$name', '$TegDescription', '$TegKeywords');"); //пишем атрибуты товара $array2 = $array->ЗначенияСвойств->ЗначенияСвойств; $num2 = $array2->count(); for ($i2 = 0; $i2 Ид; $importValue = $array2[$i2]->Значение; //сначала смотрим Ид в связях атрибутов $result = mysql_query("SELECT `idstore`, `idimport` FROM `link_attribute` WHERE `idimport` = '$importId'"); $number = mysql_numrows($result); if ($number > 0){ $myrow = mysql_fetch_array($result); //смотрим связанный атрибут по Ид $id_store_value_id_att = $myrow['idstore']; //id магазинного атрибута mysql_query("INSERT INTO `os_product_attribute` VALUES ('$ProductId', '$id_store_value_id_att', '1', '$importValue');"); }else{ //смотрим нет ли данного ИД в производителях, если есть то значит это ИД издателя $result = mysql_query("SELECT `idstore`, `idimport` FROM `link_izdat` WHERE `idimport` = '$importValue'"); //выявляем связи с id производиетей магазина $number = mysql_numrows($result); if ($number > 0){ $myrow = mysql_fetch_array($result); $id_izdat = $myrow['idstore']; mysql_query("UPDATE `os_product` SET `manufacturer_id`='$id_izdat' WHERE `product_id` = '$ProductId'"); //записываем id производителя }else{ echo 'Произошла ошибка!!!'; } } } // привязываем товар к категории mysql_query("INSERT INTO `os_product_to_category` VALUES ('$ProductId', '$cat_to_store', '1');"); //смотрим есть ли у категории родители, если есть то их также прикрепляем их к товару $myrow = mysql_fetch_array(mysql_query("SELECT * FROM `os_category` WHERE `category_id`= '$cat_to_store'")); $parent1 = $myrow['parent_id']; if($parent1 == true){ mysql_query("INSERT INTO `os_product_to_category` VALUES ('$ProductId', '$parent1', '0');"); }else{} $myrow = mysql_fetch_array(mysql_query("SELECT * FROM `os_category` WHERE `category_id`= '$parent1'")); $parent2 = $myrow['parent_id']; if($parent2 == true){ mysql_query("INSERT INTO `os_product_to_category` VALUES ('$ProductId', '$parent2', '0');"); }else{} //задаем шаблон mysql_query("INSERT INTO `os_product_to_layout` VALUES ('$ProductId', '0', '0');"); //подключаем товар к магазину mysql_query("INSERT INTO `os_product_to_store` VALUES ('$ProductId', '0');"); //прописываем seourl товаров $seourl = $ProductId; mysql_query("INSERT INTO `os_url_alias` VALUES (NULL, 'product_id=$ProductId', '$seourl');"); return true; } }
Класс библиотеки SimpleXMLReader.php тут
https://github.com/dkrnl/SimpleXMLReader/blob/master/library/SimpleXMLReader.php
Пример xml выгрузки
https://yadi.sk/d/PxIAstlt3ZJ6N6
Самое первое - это надо сделать бенчмарк и понять, что же именно тупит. Так как сущностей здесь не много - парсер и база, то можно попробовать закомментировать вызовы к базе и прогнать скрипт. Если отработает быстро - база, если нет тогда уже ридер xml.
Но есть одна штука - транзакции. База ведь не хочет, что бы у Вас было "ползаписи". И на каждую вставку/обновление она стартует транзакцию. В мускуле это никогда не тестил, но в sqlite старт-стоп транзакции занимает так много времени, что если просто вставлять записи в таблицу, то будет 5-20 в секунду. А если стартануть транзакцию вручную в начале и закрыть в конце, то скорость возрастает до 20-30 тысяч в секунду. На других машинах возможно цифры будут другие, но соотношение сохранится.
Что же делать? В php документации есть описание всего. Если кратко, то перед началом изменения таблиц, нужно выполнить
$dbh->beginTransaction();
в конце, если все хорошо
$dbh->commit();
если в процессе добавления/изменения таблиц произошло что то странное, то транзакцию нужно откатить
$dbh->rollBack();
Самое хорошее в этом, что после отката транзакции, данные таблиц восстановятся по состоянию на начало транзакции. И это будет автоматически и "почти бесплатно" (кода на сохранение писать не нужно, но базе нужно будет немного места на диске).
Еще я вижу, что в коде везде используется набор функций mysql_*
. А он считается устаревшим и рекомендуют использовать mysqli_*
.
Можно ли сократить код? можно конечно. К примеру, есть большой кусок if-else, где проверяются ключи с xml и формируется запрос в базу. И все они отличаются только одним словом. Хорошо будет сделать словарь пар (массив) и тогда этот код сильно сократится - проверяем в словаре ключ с xml, если нет - выходим, если есть - имеет имя поля для запроса.
Ещё - запросы все формируются вручную, а значения не экранируются. А это может быть сильно больно.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
В базе есть строка такого вида a:3:{i:0;s:16:"Женщинам";i:1;s:12:"одежда";i:2;s:14:"Пиджаки";} те функция php serialize нужно осуществить поиск по словам: Женщинам...
Вот простое требованиеЗаходишь на сайт как гость и видишь сообщение типа "авторизуйтесь для просмотра записей", войдя на сайта видны все...
Сохраняю таблицу а б таблице вместо заданых значений Null, хотя значения GET не пустыеПробовал и без даты сохранять, все равно не получается
При работе на локальном сервере все шло отлично(перенес код на Wordpress случились ошибки)Мой JS скрипт не может обратиться к PHP скрипту ,который...