очень прошу помочь мне оптимизировать скрипт (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, если нет - выходим, если есть - имеет имя поля для запроса.
Ещё - запросы все формируются вручную, а значения не экранируются. А это может быть сильно больно.
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости