Недостатки simple_html_dom или парсинг сложных страниц

Admin PHP Обсудить

Я уже писал о библиотеке simple_html_dom, с помощью которой можно парсить страницы сайтов. В этой заметке поделюсь некоторыми наработками, когда содержимое имеет одинаковые контейнеры.

Что делать когда сложно зацепиться за DOM элементы?

По неизвестной мне причине в библиотеке simple_html_dom не работает несколько классов в html элементе. Например, не получится найти и сделать выборку по строке:

<div class="one two three" kakay-to eshe fignya {{"ochen"}} slojnay>нужное нам содержимое</div>

Очевидным будет использовать конструкции вроде:

foreach ( $html->find('div.one.two.three') as $matches ) {}

или

foreach ( $html->find('div[class=one][class=two][class=three]') as $matches ) {}

но на деле это не работает или почти никогда не работает.

Описание задачи

Задача следующая:

Нужно найти два отдельных блока содержимого. Оба блока имеют одинаковые свойства и потому не цепляются по отдельности.

Сначала preg_match потом simple_html_dom

Для этих целей можно было бы использовать сначала конструкции preg_match, чтобы найти определённое содержимое страницы:

preg_match("/<div class="one-class">Заголовок_первый(.*?)<div class="класс-один класс-два.*?>(.*?)Заголовок_второй/", $html, $matches);

Код выше схватит всё что начинается с «Первого заголовка» и до «второго заголовка».

Иногда приходится так изощрять код, потому что иначе не захватить точный кусок кода. В базовых примерах php часто даются простые и типовые ситуации, которые не работают в сложных случаях.

А вот ещё один код:

preg_match("/<div class="класс-один класс-два.*?>(.*?)Заголовок_второй/s", $html, $matches);

С помощью него мы уже захватим всё что после второго заголовка.

А потом уже из найденного использовать выборку

foreach ( $matches->find('') as $matches ) {}

Но здесь возникает другая проблема. Содержимое в переменной matches отказывается пониматься и выводит разного рода ошибки. Даже, если содержимое конвертировать в массивы или строки, всё равно чего-то не хватает. А что именно, совсем не ясно.

Хотя в обратную сторону работает. Если сначала сделать выборку через simple_html_dom, то потом можно вполне забирать информацию через preg_match.

В ином случае остаётся придерживаться одного стиля, либо использовать только preg_match либо simple_html_dom. В данной статье будем использовать только второе.

Дополнено позже. Проблему описанную выше можно решить с помощью предварительного сохранения результат выборки $matches в отдельных файл, а затем снова использовать функцию file_get_html(). Например, так:

// Важно сохранить данные в файл, иначе повторно библиотека file_get_html работать не будет.
if ( !empty($matches[2]) ) {
    file_put_contents($file_temp, $matches[2]);
 }

if ( file_exists($file_temp) ) {
    $html = file_get_html($file_temp); // Используем библиотеку
}

Только simple_html_dom

Если перед парсингом нужно удалить какие-то данные, воспользуйтесь этой статьёй.

В коде уже привел все пояснения. Первый блок находим так.

// Задаём счет
$i=0;
// Находим общий контейнер
foreach ( $html->find('div.class-best') as $matches ) {
    $i++;
    if($i==2) break; // Останавливаем поиск после первого найденного контейнера

    // Создаём массив из данных
    $j=0;
    // Теперь в общем контейнере будем забирать нужную нам информацию
    foreach ( $matches->find('div[itemtype=""]') as $matches2 ) {
        $j++;
        if($j==6) break; // На 6 одинаковом элементе останавливаем поиск

        // Ищем 3 отдельных элемента.
        // 1 - Простой текст в ссылке, 2 - простой текст внутри тега span,
        // 3 - и ссылку на изображение
        @$one = $matches2->find('a.one', 0)->plaintext;
        @$two = $matches2->find('span.two', 0)->plaintext;
        @$three = $matches2->find('img', 0)->src;

        // На всякий случай избавимся от лишних пробелов с обоих сторон найденного
        $one = trim($one);
        $two = trim($two);
        $three = trim($three);

        // Дальше можно поменять содержимое одного найденного объекта
        $three = preg_replace('/\/\//','https://',$three); // меняем в начале ссылку '//' на 'https://'

        $our_array[] = $one .', ' .$two .', ' .$three;
    }
}

В результате этих манипуляций мы получим массив $our_array, в котором будет содержаться такая информация:

[0]=> 'Простой текст в ссылке', 'Текст внутри span', 'ссылка на фото' [1]=> ...

И так 5 строк в массиве. Дальше с этим массив можно делать всё что угодно.

Но задача ещё не закончена. Помните, выше я говорил о втором блоке, который тоже нужно найти? Проблема в том, что он имеет одинаковые свойства с первым блоком, который нам не надо никак захватывать.

Предположим что первый блок меняется и содержит всегда разное количество информации. И не получится это обойти используя наш первый массив, начиная например с 6-ого значения: our_array[5]. Ведь в другой раз, на другой странице, нужные нам данные могут начинаться с третьей позиции или любой другой.

Мы знаем, что второй блок, к счастью начинается сразу за первым. Или любым другим, но идёт всегда в одинаковом порядке. В приведенном примере всегда после первого блока. Тогда добавляем следующую конструкцию:

$i=0;
// Находим общий контейнер
foreach ( $html->find('div.class-best') as $matches ) {
    $i++;
    if($i==1) continue; // Пропускаем первый найденный объект
    if($i==3) break; // Останавливаем поиск после первого найденного объекта

    Здесь всё тоже самое, что и в первом блоке.

        $our_array2[] = $one .', ' .$two .', ' .$three;
    }
}

Как видно, содержимое второго php блока повторяет первый, за исключением того, что мы находим первый блок и пропускаем его с помощью конструкции if($i==1) continue. И обязательно останавливаем наш поиск на втором блоке. Иначе схватится лишнее. Одинаковых контейнеров может быть очень много.

Привет! Ты находишься на моём сайте. Я разработчик. Здесь я делюсь своими наработками и знаниями. Спрашивай в комментариях, если тебе что-то не понятно или пиши, если есть что добавить.

Если вам пригодилась информация, вы можете поблагодарить автора сайта символическим пожертвованием:

Добавить комментарий

Напишите свой комментарий, если вам есть что добавить/поправить/спросить по теме текущей статьи:
"Недостатки simple_html_dom или парсинг сложных страниц"
Если вам нужно добавить участок кода ставьте его между тегами <code></code>