Недостатки 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 или парсинг сложных страниц"