Страницы

Поиск по вопросам

пятница, 21 февраля 2020 г.

Simple HTML DOM Parser найти элемент с двумя классами одновременно

#php #html #парсер #dom


Парсю архив Ленты.ру. Нужно выбрать только новости за день, без категорий,
то есть нужны все div, у которых присутствуют одновременно классы item и news, внутри
всех этих div'ов нужно выбрать элемент a и, условно говоря, вывести/получить href.

Версия, которая работает, НО работает неправильно, выглядит так:

$news = $html->find( 'div[class=news] a' );
foreach( $news as $element ):
    echo $element->href . "\n";
endforeach;


Неправильно потому что выборка идёт только по div'ам с классом news, а надо выборку
по div'ам с классами item и news вместе.

Пробовал так:

$news = $html->find( 'div[class=item news] a' );


...и так:

$news = $html->find( 'div.item.news a' );


...и так:

$news = $html->find( 'div[class="item news"] a' );


Не работает. Варианты эти нашёл соответственно здесь и здесь. Принципиально ли использовать
Simple HTML DOM Parser? Нет, не принципиально. Но с ним я уже знаком и имел опыт использования,
поэтому выбор пал на него.

Была ещё такая мысль: сначала найти все div с class=item, потом среди них найти все
div с class=news, потом в каждом из них найти a, но у меня не получилось. Как я понял,
цепочка методов а-ля

$news = $html->find( 'div[class=item]' )->find( 'div[class=news] a' );


не сработает (у меня не работает). Как быть?

P.S. Добавлю. Сейчас глянул ещё здесь, да, решение рабочее, всё нормально работает.
Если полностью прописать все классы:

$news = $html->find( 'div[class=item news b-tabloid__topic_news] a' );


Но дело в том, что последний класс b-tabloid__topic_news там присутствует не везде,
то есть его наличие не обязательно. Да, можно наговнокодить алгоритм с массивами, ищем
сначала такие, потом другие, склеиваем, сортируем и т.д, но это ИМХО криво. В общем,
вопрос можно переформулировать так: как найти все элементы, у которых среди классов
есть нужные?
    


Ответы

Ответ 1



В целом понятно, что данная библиотека не умеет корректно обрабатывать селекторы, где перечисляются два и более класса подряд. Тем не менее, вы в целом смотрите в нужном направлении, используя форму записи div[class=...]. Рассмотрим исходный тестовый пример: $txt = <<
HTML; $html = str_get_html($txt); пусть наша задача найти div.news.items и вывести значение текста ссылки zxc123. Как вы и написали, вызов вида $html->find('.news.items a); Возвращает пустой набор. Однако, в целом это эквивалентно записи div[class="news items"]. Как известно помимо непосредственного равенства атрибута =, возможны и другие формы записи, например, *=. Если глянуть во внутренности Simple HTML DOM, то вы обнаружите там следующую функцию тестирования селекторов (код приведен не полностью): protected function match($exp, $pattern, $value) { switch ($exp) { case '=': return ($value===$pattern); ...... case '*=': if ($pattern[0]=='/') { return preg_match($pattern, $value); } return preg_match("/".$pattern."/i", $value); } return false; } Отсюда видно, что при указании непосредственного равенества, используется обычное сравнение строк (они ранее приводятся к одному регистру). А вот при указании вхождения *= используются регулярные выражения. Следовательно, использование подобного селектора решит искомую проблему. Более того, мы можем указать непосредственно регулярное выражение в селекторе. Поэтому код $html->find("div[class*=news items] a"); для данного исходного примера успешно найдет экземлпяр ссылки zxc123. Как было упомянуто выше, допустимо использование регулярного выражения, поэтому, если исходный текст будет иметь более сложный набор классов zxc news asd items qwe, но подходящий нам: то написание следующего селектора разрешит эту ситуацию: $html->find("div[class*=news.+items] a"); либо если порядок следования классов items и news может изменится, то возможно следующее выражение: $html->find("div[class*=news.+items|items.+news] a"); зы: такое поведение, вроде, недокументировано, но вроде как и хаком-багом не является, ибо в коде явно прописано условие, проверки первого символа регулярки.

Комментариев нет:

Отправить комментарий