Я создаю приложение php scraping для командной строки, которое использует XPath для анализа HTML – проблема возникает каждый раз, когда новый экземпляр класса DOMXPath загружается в цикле. Я получаю потерю памяти, примерно равную размеру загружаемого XML , Скрипт запускается и запускается, медленно наращивая объем памяти до тех пор, пока он не достигнет предела и не прекратит работу.
Я пробовал форсировать сбор мусора с помощью gc_collect_cycles()
и PHP по-прежнему не возвращает память из старых запросов Xpath. Действительно, определение класса DOMXPath, похоже, даже не включает функцию деструктора?
Поэтому мой вопрос … есть ли способ принудительно удалить мусор на DOMXPath
после того, как я уже извлек необходимые данные? Использование unset в экземпляре класса предсказуемо ничего не делает.
Код ничего особенного, просто стандартный материал Xpath:
//Loaded outside of loop $this->dom = new DOMDocument(); //Inside Loop $this->dom->loadHTML($output); $xpath = new DOMXPath($this->dom); $nodes = $xpath->query("//span[@class='ckass']"); //unset($this->dom) and unset($xpath) doesn't seem to have any effect
Как вы можете видеть выше, я сохранил экземпляр нового класса DOMDocument
за пределами цикла, хотя, похоже, это не улучшает производительность. Я даже попытался вывести экземпляр класса $xpath
из цикла и загрузить DOM в Xpath напрямую, используя метод __constructor
, потеря памяти одинаков.
Увидев этот ответ, она в течение многих лет без заключения, наконец, обновление! Теперь я столкнулся с аналогичной проблемой, и оказалось, что DOMXPath
просто просачивает память, и вы не можете ее контролировать. Я не искал, если об этом сообщается на bug.php.net до сих пор (это может быть полезно для редактирования позже).
«Рабочие» решения, которые я нашел в этой проблеме, – это обходные пути. Основная идея заключалась в замене DOMNodeList
Traversable
возвращаемого DOMXPath::query()
с другим, содержащим одни и те же узлы.
Наиболее подходящая работа с DOMXPathElementsIterator
которая позволяет вам запрашивать конкретное выражение xpath, которое у вас есть в вашем вопросе, без утечек памяти:
$nodes = new DOMXPathElementsIterator($this->dom, "//span[@class='ckass']"); foreach ($nodes as $span) { ... }
Этот класс теперь является частью версии разработки Iterator-Garden, а $nodes
– итератором по всем <span>
DOMElements.
Недостатком этого обходного пути является то, что результат xpath ограничен результатом SimpleXMLElement::xpath()
(это отличается от DOMXPath::query()
), потому что он используется внутренне для предотвращения утечки памяти.
Другой альтернативой является использование DOMNodeListIterator
над DOMNodeList
например, возвращаемый DOMDocument::getElementsByTagname()
. Однако эти итерации медленны.
Надеюсь, что это будет полезно, даже вопрос был действительно старым. Это помогло мне в аналогичной ситуации.
Вызов групп очистки мусора делает смысл, если объекты больше не ссылаются (используются).
Например, если вы DOMXPath
создаете новый объект DOMXPath
для одного и того же DOMDocument
(помните, что он связан с DOMDocument
который все еще существует), похоже, что ваша память «утечка». Вы просто используете все больше и больше памяти.
Вместо этого вы можете просто повторно использовать существующий объект DOMXPath
при повторном использовании объекта DOMDocument
все время. Попробуйте:
//Loaded outside of loop $this->dom = new DOMDocument(); $xpath = new DOMXPath($this->dom); //Inside Loop $this->dom->loadHTML($output); $nodes = $xpath->query("//span[@class='ckass']");
Если вы используете libxml_use_internal_errors(true);
чем причина утечки памяти, потому что список ошибок растет.
Использовать libxml_clear_errors();
или проверьте этот ответ .