@mi_ka

Утечка памяти phpQuery. Как исправить?

Есть функция, которая, используя библиотеку phpQuery, парсит данные со страницы на сайте. Данные на странице обновляются регулярно, поэтому функция вызывается в цикле приблизительно каждые 5-10 секунд (в зависимости от скорости получения страницы). После продолжительной работы скрипта, получаю ошибку

PHP Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 1073741824 bytes) in C:\OSPanel\domains\home-page-parcer.ru\phpQuery.php on line 3494

В php.ini memory_limit = 1536M. Скрипт работает на локальном сервере (OpenServer).

Провел небольшие исследования расставив в различных частях скрипта memory_get_usage. Потребляемая память увеличивается непосредственно после создания объекта phpQuery или после одного из циклов foreach (1-го либо 2-го сверху) , при этом память не уменьшается после вызова phpQuery::unloadDocuments(). Также, память накапливается не после каждого создания объекта phpQuery, а как-то периодически (один раз в несколько итераций, закономерность увеличения не удалось установить). К примеру: было 2097152 после стало 4194304, спустя несколько итераций значение не изменялось, но после снова увеличилось и стало 6291456.

Собственно сама функция. Не могу понять в чем проблема и почему phpQuery::unloadDocuments(); не работает
function parcer($url){
    $ids=[];
    $indexIds=0;
    $content = get_content($url);
     
    $doc = phpQuery::newDocument($content);
     
    foreach ($doc->find('[id^=name_]:input') as $opt) { // получаем товары которые успели преобразоваться в input
            $opt=pq($opt);
            $numTov = preg_replace('~[^0-9]*~','',$opt->attr('id')); // получаем с ID номер товара
            $ids[$indexIds][] = $numTov;
            $ids[$indexIds][] = $opt->attr('value'); // получаем значение input
            $ids[$indexIds][] = $opt->nextAll('input:first-of-type')->prev('a')->text(); //ищем последнюю ссылку каталога
            $ids[$indexIds][] = $opt->nextAll('nobr')->children('[id^=manf]')->text(); //ищем поставщика
            $indexIds++;
    }
     
    foreach ($doc->find('td>[id^=name_]:not(:input)') as $opt) { // получаем товары в span, которые без родителя
            $opt=pq($opt);
            $numTov = preg_replace('~[^0-9]*~','',$opt->attr('id'));
            $ids[$indexIds][] = $numTov;
            $ids[$indexIds][]= $opt->text();
            $ids[$indexIds][] = $opt->nextAll('[id^=cats_]:last')->text();
            $ids[$indexIds][] = $opt->nextAll('nobr')->children('[id^=manf]')->text();
            $indexIds++;
    }
     
    foreach ($doc->find('div>[id^=name_]') as $opt) { // получаем товары в span, которые с родителем
            $opt=pq($opt);
            $numTov = preg_replace('~[^0-9]*~','',$opt->attr('id'));
            $ids[$indexIds][] = $numTov;
            $ids[$indexIds][]= $opt->text();
            $ids[$indexIds][] = $opt->parent()->nextAll('[id^=cats_]:last')->text();
            $ids[$indexIds][] = $opt->parent()->nextAll('nobr')->children('[id^=manf]')->text();
            $indexIds++;
    }
     
    phpQuery::unloadDocuments(); //очистка документа
    gc_collect_cycles(); // принудительный вызов встроенного сборщика мусора PHP
    return $ids;
    }
  • Вопрос задан
  • 537 просмотров
Пригласить эксперта
Ответы на вопрос 3
@caballero
Программист
ну как и любая библиотека в PHP она рассчитывает что после отработки страницы контекст освободится а у вас естественно скрипт работает долго
Надо скрипт перезапускать перидически
Ответ написан
Lobotomist
@Lobotomist
Software Developer
Насколько я понимаю, это известная проблема данного парсера: https://github.com/pein0119/phpquery/issues/192

Так что если хочется использовать именно его нужно
* либо найти утечку в самом парсере (видимо, метод phpQuery::unloadDocuments не все указатели удаляет, где-то они остаются) и исправить;
* либо время от времени перезапускать скрипт;
* либо использовать другую библиотеку, например nokogiri, как вам посоветовал Immortal_pony;

Причина утечки, я предполагаю, в том, что phpQuery::$documents свойство публичное и когда элементы массива удаляются внутри unloadDocuments на эти объекты все еще ссылаются какие-то из ваших переменных. Ради эксперимента вы можете сделать unset всех переменных (не только doc) и потом unloadDocuments - просто чтобы проверить мое предположение.
Ответ написан
Комментировать
TommyV888
@TommyV888 Куратор тега PHP
-
Попробуйте запускать gc_collect_cycles после очистки переменных и unloadDocuments
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы