Warning: Cannot use a scalar value as an array in /home/admin/public_html/forum/include/fm.class.php on line 757

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/include/fm.class.php on line 770
Форумы портала PHP.SU :: Версия для печати :: Как парсить большие XML-файлы
Форумы портала PHP.SU » » XML и его обработка » Как парсить большие XML-файлы

Страниц (2): [1] 2 »
 

1. Faab - 30 Апреля, 2015 - 12:19:18 - перейти к сообщению
Мне дали тестовый XML-файл для ознакомления со структурой - 150 единиц товара. Я под него написал модуль на drupal.

Пока мой парсер справляется с XML-фалом с 150 единицами товара, но я сильно сомневаюсь что он справиться с количеством в 20,000 товаров.


У меня самое слабое звено это параметры товара:
CODE (htmlphp):
скопировать код в буфер обмена
  1.  
  2.             <offer id="tovar_14526" available="true">
  3.                 <name>Товар 14526</name>
  4.                 <vendor>Alfa</vendor>
  5.                 <url>http://xxx.ru/catalog/product/14526g/</url>
  6.                 <currencyId>RUB</currencyId>
  7.                 <categoryId>245</categoryId>
  8.                 <image>http://xxx.ru/tovar_14526_fedf.jpeg</image>
  9.                 <param name="Страна">Италия</param>
  10.                 <param name="Коллекция">Omega</param>
  11.                 <param name="Артикул">14526/G</param>
  12.                 <param name="Высота, мм">400</param>
  13.                 <param name="Диаметр, мм">500</param>
  14.                 <param name="Количество колёс">3</param>
  15.                 <param name="Мощность мотора, W">400</param>
  16.                 <param name="Тип мотора (основной) (новый)">Электрический</param>
  17.                 <param name="Тип кузова (новый)">Пластик</param>
  18.                 <param name="Напряжение, V">24</param>
  19.                 <param name="Степень защиты, IP">20</param>
  20.                 <param name="Виды материалов (новый)">Пластик</param>
  21.                 <param name="Материал арматуры (новый)">Металл</param>
  22.                 <param name="Материал управления (новый)">Пластик</param>
  23.                 <param name="Цвет (новый)">Белый</param>
  24.                 <param name="Цвет арматуры (новый)">Золото</param>
  25.                 <param name="Цвет кузова (новый)">Белый</param>
  26.                 <param name="Место использования (новый)">Внутри</param>
  27.                 <param name="Стиль (новый)">Классика</param>
  28.                 <param name="Форма кузова">Круглый</param>
  29.                 <param name="Дизайн (новый)">Beta</param>
  30.                 <param name="ШтрихКод">150001450060</param>
  31.                 <param name="Базовая единица">шт</param>
  32.                 <param name="Производитель">Производители/Alfa</param>
  33.                 <param name="Остаток поставщика">11</param>
  34.                 <param name="Видимость">Y</param>
  35.                 <param name="Остаток">Больше 10 шт.</param>
  36.                 <param name="Новинка">Да</param>
  37.                 <param name="Акция">false</param>
  38.                 <param name="Раздел на сайте">Машинки</param>
  39.                 <price>7172.85</price>
  40.             </offer>
  41.  


Записав в базу данных объект продукта (без параметров), я начинаю парсить сами параметры (имея уже на руках id продукта):

PHP:
скопировать код в буфер обмена
  1.  
  2.     $elements = new XMLElementIterator($reader, 'offer');
  3.     foreach ($elements as $element) {
  4.         $offer = array();
  5.        
  6.         // id
  7.         $offer['id'] = (string) $element->getAttribute('id');
  8.        
  9.         // available
  10.         if((string) $element->getAttribute('available') == 'true'){
  11.           $offer['available'] = 1;
  12.         }else{
  13.           $offer['available'] = 0;
  14.         }
  15.        
  16.         $offerSimpleXMLElement = $element->getSimpleXMLElement();
  17.        
  18.         // name
  19.         $offer['name'] = (string) $offerSimpleXMLElement->name;
  20.        
  21.         // vendor
  22.         $offer['vendor'] = (string) $offerSimpleXMLElement->vendor;
  23.        
  24.         // url
  25.         $offer['url'] = (string) $offerSimpleXMLElement->url;
  26.        
  27.         // currencyId
  28.         $offer['currencyId'] = (string) $offerSimpleXMLElement->currencyId;
  29.        
  30.         // categoryId
  31.         $offer['categoryId'] = (string) $offerSimpleXMLElement->categoryId;
  32.        
  33.         // image
  34.         $offer['image'] = (string) $offerSimpleXMLElement->image;
  35.        
  36.         // price
  37.         $offer['price'] = (string) $offerSimpleXMLElement->price;
  38.        
  39.         // save offer object in db
  40.         $i_offer_id =  _custom_function_save_current_offer($offer['id'], $offer['available'], $offer['name'], $offer['vendor'], $offer['url'], $offer['image'], floatval($offer['price']), $offer['categoryId'], $offer['categoryId']);
  41.  
  42.         $params = $offerSimpleXMLElement->param;  /// asSimpleXML  getSimpleXMLElement
  43.         foreach($params as $attribute_value){
  44.           $param = array();
  45.           foreach($attribute_value->attributes() as $attribute_name){
  46.             $param['name'] = (string) $attribute_name;
  47.             $param['value'] = (string) $attribute_value;
  48.            
  49.             if(isset($param['name']) && isset($param['value'])){
  50.               // save param obect in db
  51.               _custom_function_save_current_param($param['name'], $param['value'], $i_offer_id);
  52.             }
  53.           }
  54.         }
  55.     }
  56.  


Этот участок кода, у меня занимает полминуты. Ведь в функции _custom_function_save_current_param() я должен сначала сделать проверку, а не существует ли такой param-объект в таблице, записать если надо, потом в отдельной таблице (связь many-to-many) записать связь между offer_id и param_id.

Короче мой парсер точно захлебнётся с большим XML-файлом. Как на практике парсить разумно? Частями?
2. DeepVarvar - 30 Апреля, 2015 - 12:24:22 - перейти к сообщению
Да, читаешь файл кусками, ищешь там метку "<offer", по ней разбиваешь на маленькие файлы, потом натравливаешь свой импортер на эти файлы. После успешного импорта удаляешь эти временные файлы.
3. Faab - 30 Апреля, 2015 - 12:39:42 - перейти к сообщению
DeepVarvar пишет:
Да, читаешь файл кусками, ищешь там метку "<offer", по ней разбиваешь на маленькие файлы, потом натравливаешь свой импортер на эти файлы. После успешного импорта удаляешь эти временные файлы.


Вот не въехал я)))

Вот я начну читать файл:
PHP:
скопировать код в буфер обмена
  1.  
  2.   $o_reader = new XMLReader();
  3.   if (!$o_reader->open($s_url)){
  4.     //  not valid url
  5.   }
  6.  


Мне прочитать каждый кусок файла с offer и затем создать мини-файл, например, в папке tmp? Я пока не знаю как это реализовать в коде, но разве так парсер не захлебнётся?
4. esterio - 30 Апреля, 2015 - 12:52:37 - перейти к сообщению
http://php.net/xml
взято отсюда
http://stackoverflow[dot]com/questio[dot][dot][dot]large-xml-in-php
пример
http://php.net/manual/ru/example...ml-structure.php
ключевым кодом в примере есть

тоесть читать по 4Кб файл.
Если у вас ссилка, то сначала скачать в временный файл, прочитать побайтово, а потом удалить
Чтобы узнать путь к временной директории куда можно скачать файл воспользуйтесь функциями sys_get_temp_dir и tempnam
Для скачивания отлично подойдет cURL
http://php.net/manual/ru/book.curl.php
Скачать большой файл можно так как тут
http://stackoverflow[dot]com/questio[dot][dot][dot]-file-using-curl
PHP:
скопировать код в буфер обмена
  1. $fp = fopen(dirname(__FILE__). '/localfile.tmp', 'w+');//This is the file where we save the    
  2. curl_setopt($ch, CURLOPT_FILE, $fp); // write curl response to file
5. DeepVarvar - 30 Апреля, 2015 - 12:55:13 - перейти к сообщению
Faab пишет:
Вот я начну читать файл

Вот так:
PHP:
скопировать код в буфер обмена
  1. $file = fopen('...', 'rb');
  2. $data = '';
  3. while (!feof($file) && strpos(fread($file, 1024), '<offer') === false) {
  4.     $data .= 'что-то там';
6. MiksIr - 30 Апреля, 2015 - 13:16:15 - перейти к сообщению
При работе с XMLReader не надо ничего читать кусками, он сам все делает. XMLReader достаточно.
7. DeepVarvar - 30 Апреля, 2015 - 13:27:28 - перейти к сообщению
MiksIr пишет:
он сам все делает
У человека проблема с временем выполнения и используемой памятью -- как ни крути, а разбивать на пошагово придется.
8. MiksIr - 30 Апреля, 2015 - 13:43:06 - перейти к сообщению
XMLReader не читает весь файл в память. Он оперирует курсором, указывающим на текущее положение в файле и операции сдвига курсора.

А со временем у него в другом месте проблема, тут хоть кусками, хоть не кусками.
9. Faab - 30 Апреля, 2015 - 13:51:42 - перейти к сообщению
esterio пишет:
http://php.net/xml
взято отсюда
http://stackoverflow[dot]com/questio[dot][dot][dot]large-xml-in-php
пример
http://php.net/manual/ru/example...ml-structure.php
ключевым кодом в примере есть
то есть читать по 4Кб файл.
Если у вас ссылка ...


Ну ты и разложил по полочкам. ) Буду вникать. Будет результат - отпишусь.

DeepVarvar пишет:
Faab пишет:
Вот я начну читать файл

Вот так:
PHP:
скопировать код в буфер обмена
  1. $file = fopen('...', 'rb');
  2. $data = '';
  3. while (!feof($file) && strpos(fread($file, 1024), '<offer') === false) {
  4.     $data .= 'что-то там';


Да, сейчас попробую сделать так, как вы советуете с Эстерио.

MiksIr пишет:
XMLReader не читает весь файл в память. Он оперирует курсором, указывающим на текущее положение в файле и операции сдвига курсора.

А со временем у него в другом месте проблема, тут хоть кусками, хоть не кусками.



Ты же видишь что я читаю offer классом XMLElementIterator - это уже не чистый XMLReader (это я сейчас тыкаю пальцем в небо). Иначе прочитать параметры продукта я не смог.

Так и в чём проблема у человека?
10. MiksIr - 30 Апреля, 2015 - 15:25:32 - перейти к сообщению
Вот этим? https://github[dot]com/hakre/XMLReaderIterator
Нужно смотреть как сделано, но он вроде использует XMLReader, т.е. читать весь файл не должен. Тестировали на больших файлах потребление памяти?

А проблема времени, как я понял, больше от базы данных зависит. Нужно смотреть, что там сделать можно, если время критично. Я бы пошел путем загрузки всего этого во временную таблицу, а потом уже синхронизацию с основной таблицей несколькими SQL запросами.
11. esterio - 30 Апреля, 2015 - 15:37:51 - перейти к сообщению
MiksIr пишет:
а потом уже синхронизацию с основной таблицей несколькими SQL запросами.

или INSERT ... SELECT
12. MiksIr - 30 Апреля, 2015 - 15:45:36 - перейти к сообщению
esterio пишет:
MiksIr пишет:
а потом уже синхронизацию с основной таблицей несколькими SQL запросами.

или INSERT ... SELECT

Нужно еще удалять и обновлять. Если в случае mysql добавление и обновление еще можно совместить, то удаление - все-равно отдельный запрос.
13. Faab - 30 Апреля, 2015 - 16:45:30 - перейти к сообщению
Да, конечно я использую временные таблицы.

Большого файла нет: увеличил свой файл до 3,500 товаров методом копи-паст.

При оригинальном коде, у меня выдало ошибку "Fatal error: Maximum execution time of 30 seconds exceeded" уже когда в базу записался 150-ый товар. Время работы скрипта: 4 минуты.

Когда я подправил свою функцию _custom_function_save_current_param(), а именно убрал проверку, связную таблицу, так что бы напрямую записывался каждый объект параметра при вызове функции, то теперь у меня сгенерировалась ошибка "Fatal error: Maximum execution time of 30 seconds exceeded" только уже когда в таблице было записано 672 товара (это 20102 параметра).

Последний вариант у меня занял 7 минут, и то, учитывая что у меня в локальном php.ini прописано следующее:
CODE (htmlphp):
скопировать код в буфер обмена
  1. memory_limit = "-1"
  2. max_execution_time = 0     ; Maximum execution time of each script, in seconds
  3. max_input_time = 0      ; memory_limit = "-1"
  4. max_execution_time = 0     ; Maximum execution time of each script, in seconds
  5. max_input_time = 0      ; Maximum amount of time each script


На хостинге прописать такое как вы понимаете трудно.

Тут как не крути надо пробовать вариант преложенный Esterio и DeepVarvar.
14. MiksIr - 30 Апреля, 2015 - 16:48:50 - перейти к сообщению
Думаю, сначала стоит заняться профайлингом, что бы понять - где теряется время.
Второе - такие вещи нужно запускать и консоли, а не через веб.
Ну и третье - сохранять состояние после каждой операции, что бы повторный запуск продолжал работу, а не начинал сначала.
15. Faab - 30 Апреля, 2015 - 16:55:38 - перейти к сообщению
MiksIr пишет:
Думаю, сначала стоит заняться профайлингом, что бы понять - где теряется время.
Второе - такие вещи нужно запускать и консоли, а не через веб.
Ну и третье - сохранять состояние после каждой операции, что бы повторный запуск продолжал работу, а не начинал сначала.


На счёт третьего: так может и сделать ограничение на чтение только 50 товаров за один запуск скрипта? После чтения 50 продукта установить cron, что бы через две-три минуты запустить этот же скрипт.

Я пока не знаю как перейти к чтению именно мне нужного продукта, но думаю это можно реализовать.

 

Powered by ExBB FM 1.0 RC1