PHP парсинг HTML, с помощью simple HTML DOM


Содержание

Блог WEB-разработчика о PHP, MySQL, JavaScript и разработке

Парсер на php с использованием Simple_dom_html

Решил написать небольшую вводную статью по парсингу сайтов. Предыдущая статья описывала основы основ парсинга – в этой я опишу один из удобных инструментов для анализа данных полученой HTML страницы. Этот инструмент PHP Simple HTML DOM Parser – его еще называют jQuery на PHP.

Итак задача: необходимо получить данные о всех статьях с первой страницы блога habrahabr и сформировать из них массив годный для сохранения в базу данных либо XML файл

Для анализа данных описания и поиска в тексте картинок можно использовать такой код:

Утечки памяти при использовании Simple html dom

Очень часто приложению нехватает памяти при использовании Simple html dom. Я поборол эту проблему используя метод $dom->clear(); при окончании работы с DOM элементами. Так же нужно очищать все найденные под элементы. К примеру как я по правильному написал бы код выше с учетом утечки памяти:

При парсинге сайтов возникает множество дополнительных трудностей – я описал решение для одной из них. В следующей статье я опишу как спарсить каталог товаров с учетом постраничности.

PHP Simple HTML DOM Parser – библиотека для парсинга сайтов

Те, кто хоть раз писал парсер, знает, что не стоит этого делать с помощью регулярных выражений. Проиллюстрировать это утверждение поможет следующий пример.

Возьмем HTML код:

К примеру, из него нам нужно получить описание и url сайта. Если брать исключительно этот кусок кода, то все решается достаточно просто:

Проблемы начинаются тогда, когда описание сайта заполняют пользователи, и оно не имеет определенного шаблона.

Такой код регулярному выражению не по зубам.

Обычно, в вузах на этот случай учат писать конечный автомат. Суть его в том, что мы перебираем, посимвольно, весь html текст, находим начало тега, и строим дерево документа. Так называемое DOM (Document Object Model)

Сейчас, писать такое самому нет необходимости.

В php, начиная с версии 5, есть встроенные методы работы с деревом документа (класс DOMDocument), но основан он на XML парсере.

А HTML и XML это хоть и очень похожие, но в тоже время абсолютно разные технологии.

К примеру, непременное требование к XML это закрытые теги и отсутствие ошибок.

Отсюда вытекает условие: ошибок в html, который мы парсим с помощью нативных средств php, быть не должно.

К сожалению, на сайтах донорах, ошибки не редки, а значит этот метод отпадает.

Для корректного разбора таких сайтов, на помощь придут php библиотеки PHPQuery, Simple HTML DOM, Zend DOM Query, Nokogiri .

Некоторые из них, после небольших манипуляций скармливают html тому же DOMDocument. Мы не будем их рассматривать.

В этой статье я расскажу про SimpleHTMLDOM. Этой библиотекой я пользуюсь уже несколько лет, и она меня еще ни разу не подводила.

Скачиваем последнюю версию здесь.

Пусть Вас не смущает то, что она не обновлялась с 2008 года, то, что она умеет, полностью покроет Ваши нужды в разборе html текстов.

В архиве, который вы скачали, две папки (примеры работы и документация) и файл simple_html_dom.php.

simple_html_dom.php это и есть вся библиотека, больше ничего для работы не потребуется. Кидаем этот файл в папку с проектом и в своем скрипте просто подгружаем его.

Кроме документации, которую вы скачали с архивом, доступна еще online версия, ее вы найдете здесь

Файл подключен и готов к работе.

Для того, чтобы начать разбирать HTML, его сперва нужно получить. Обычно, я делаю это при помощи библиотеки CURL.

В simplehtmldom есть методы для удаленной загрузки страниц. После подключения файла библиотеки, нам доступны 2 функции для обработки HTML строк.

str_get_html(str) и file_get_html(url)

Они делают одно и тоже, преобразуют HTML текст в DOM дерево, различаются лишь источники.

str_get_html – на вход получает обычную строку, т.е. если вы получили HTML прибегнув к curl, или file_get_contents то вы просто передаете полученный текст этой функции.

file_get_html – сама умеет загружать данные с удаленного URL или из локального файла

К сожалению, file_get_html загружает страницы обычным file_get_contents. Это значит если хостер, выставил в php.ini allow_url_fopen = false (т.е. запретил удаленно открывать файлы), то загрузить что-то удаленно, не получится. Да и серьезные веб сайты таким способом парсить не стоит, лучше использовать CURL с поддержкой proxy и ssl. Однако для наших опытов, вполне хватит и file_get_html.

в результате, в переменной $html будет объект типа simple_html_dom.

При больших объемах данных, в библиотеке происходит утечка памяти. Поэтому после окончания одного цикла надо ее чистить.

Делает это метод clear.

К примеру грузим 5 раз сайт www.yandex.ru с разными поисковыми запросами

Эти две строчки $html->clear(); и unset($html); лучше писать сразу же после того, как Вы создали объект. Иначе забудете, и скрипт отвалится, забив всю память.


После того, как html текст упакован в объект, можно приступать непосредственно к поиску нужных элементов.

Большинство поисковых функций выполняет метод find(selector, [index]). Если второй аргумент не задан, метод возвращает массив элементов. Если же задан то элемент этого массива с индексом index.

Пример: скачаем главную страницу моего блога, и выведем все ссылки, которые встретим на своем пути.

Поиск по названию тега вы уже видели

поиск по классу

или комбинированный вариант

в данном случае, сначала найдется элемент с >

Если метод find ничего не нашел и index не задан, то он возвращает пустой массив. Если же index задан, то метод возвращает null.

Поэтому верным решением будет проверить

Поиск по наличию атрибута

или более конкретный поиск по значению атрибута

Такая нотация позволяет искать по двум и более смежным классам

Поиск нескольких тегов

Поиск вложенных тегов

У каждого найденного элемента также есть метод

если нам нужно найти все li только первого div’а то мы можем написать так

Поиск по значению атрибута не ограничивается только равенством. Вот доступные условия

[атрибут] – проверяет есть ли у элемента данный атрибут

[атрибут=величина] – проверяет, есть ли у элемента данный атрибут и равно ли его значение величине.( div[ >

[атрибут!=величина] – проверяет, есть ли у элемента данный атрибут и не равно ли его значение величине.( div[ >

[атрибут^=величина] – проверяет, есть ли у элемента данный атрибут и начинается ли его значение с величины ( div[ >

[атрибут$=величина] – проверяет, есть ли у элемента данный атрибут и заканчивается ли его значение величиной( div[ >

[атрибут*=величина] – проверяет, есть ли у элемента данный атрибут и содержит ли его значение в себе величину, в любом месте(div[ >

Обычный текст можно искать как тег text

Комментарии находим по тегу comment1

Каждый найденный элемент и сам $html имеют 5 полей

$e->tag Читает или записывает имя тега элемента.

$e->outertext Читает или записывает весь HTML элемента, включая его самого.

$e->innertext Читает или записывает внутренний HTML элемента

$e->plaintext Читает или записывает простой текст элемента, это эквивалентно функции strip_tags($e->innertext). Хотя поле доступно для записи, запись в него ничего не даст, и исходный html не изменит

Как Вы могли догадаться, для удаления ненужного элемента из HTML можно затереть его поле outertext

Тут следует помнить, что хоть элемент и не виден в html, из дерева DOM он никуда не делся

при желании мы даже можем вернуть элемент на место

Для более эффективной навигации по дереву документа доступны методы

$e->children ( [int $index] ) Возвращает объект N-го прямого потомка, если индекс установлен, в противном случае возвращает массив всех дочерних элементов

$e->parent() Возвращает родительский элемент.

$e->first_child() Возвращает первый дочерний элемент, или null, если ничего не найдено

$e->last_child() Возвращает последний дочерний элемент, или null, если ничего не найдено

$e->next_sibling() Возвращает следующий родственный элемент, или null, если ничего не найдено

$e->prev_sibling() Возвращает предыдущий родственный элемент, или null, если ничего не найдено

Все дочерние элементы разные, как-то подобрать к ним селектор проблематично. Поэтому воспользуемся описанными методами.


Данные методы полезны при разборе таблиц, элементы которых, как правило, структурированы, но не имеют идентифицирующих атрибутов.

Ну и последняя фишка это вызов callback функции на найденный элемент

На экране мы увидим

Доступ к атрибутам элементов осуществляется напрямую

Хватит теории, перейдем к практике

Загрузим n фотографий из поисковой выдачи Yandex Картинок. http://images.yandex.ru/

Как быть если нам нужно больше фото, чем лежит на одной странице?

Ответ прост: Код, приведенный выше, заключается в функцию, в html помимо фото находим еще и URLвсех страниц, и рекурсивно вызываем данную функцию для этих страниц.

Илон Маск рекомендует:  Web браузер собственными руками

Все хорошо, 200 картинок лежат в папке data. Но их размер слишком мал.

Поэтому завершающим аккордом нашей практики будет загрузка увеличенной фотографии.

Для этого определим еще одну функцию

и слегка поправим getYandexImages

Вот и все, наслаждаемся фото великолепной Джессики Альбы. Надеюсь меня простит Яндекс, ведь по сути фото грабится не с их серверов, а с прямиком с сайтов, где они лежат.

Кроме того это всего лишь демонстрация работы. Думаю никому в здравом уме, не придет в голову парсить Яндекс с помощью file_get_content. Данную библиотеку можно применять и в мирном программировании. К примеру в качестве шаблонизатора для CMS. Почему нет, с хорошим кешированием будет очень удобная штука.
Исходники
Источник xdan.ru

Вам не хватает дамской доброты и, в частности, тактильных прикосновений? Только-только массажистки с блаженством сделают вам хороший массаж в любое время дня и ночи.

Позвоните на телефон приятной массажистке для того, чтобы получить отличный массаж. Только они частные массажистки будут довольны принять ваше предложение в любое время суток.

Парсинг таблицы с Simple html dom

Всем доброго времени суток
Использую парсера Simple html dom, для парсинга.

Есть такая таблица:

Ее я прохожу через такой код:

Собственно Производитель меняю на названия других ячеек все работает нормально.

все строки парсятся нормально за исключением вот этой:

Подскажите как мне проскочить / исключить этот блок. Возможно его можно удалить через outertext

11.03.2020, 08:45

Парсинг ссылок через PHP Simple HTML DOM Parser
Здравствуйте. Возникла проблема с работой данного скрипта. Batman is our hero
http://site.ru/123.

Simple HTML DOM
$html = str_get_html($content); $content — содержит весь html-код страницы (кодировка utf-8 как и.

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Но здесь возникает другая проблема. Содержимое в переменной 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, в котором будет содержаться такая информация:

И так 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. И обязательно останавливаем наш поиск на втором блоке. Иначе схватится лишнее. Одинаковых контейнеров может быть очень много.

PHP Simple HTML DOM Parser Manual

Get HTML elements

Modify HTML elements

Extract contents from HTML

How to create HTML DOM object?

How to find HTML elements?

Supports these operators in attribute selectors:

Filter Description
[attribute] Matches elements that have the specified attribute.
[!attribute] Matches elements that don’t have the specified attribute.
[attribute=value] Matches elements that have the specified attribute with a certain value.
[attribute!=value] Matches elements that don’t have the specified attribute with a certain value.
[attribute^=value] Matches elements that have the specified attribute and it starts with a certain value.
[attribute$=value] Matches elements that have the specified attribute and it ends with a certain value.
[attribute*=value] Matches elements that have the specified attribute and it contains a certain value.

How to access the HTML element’s attributes?

Парсинг HTML и Скрейпинг с помощью простой библиотеки HTML DOM

Если вам нужно разобрать HTML, регулярные выражения не подходят. В этом руководстве вы узнаете, как использовать открытый, легко изучаемый парсер, чтобы читать, изменять и выводить HTML из внешних источников. Используя в качестве примера nettuts, вы узнаете, как получить список всех статей, опубликованных на сайте, и отобразить их.

Шаг 1. Подготовка

Первое, что вам нужно сделать, это загрузить копию библиотеки simpleHTMLdom, которая свободно доступна на sourceforge.

В загрузке есть несколько файлов, но вам нужен только файл simple_html_dom.php; остальное это примеры и документация.

Шаг 2. Основы парсинга

Эта библиотека очень проста в использовании, но есть некоторые основы, которые вы должны рассмотреть, прежде чем приступить к работе.

Загрузка HTML


Вы можете создать свой начальный объект, загрузив HTML из строки или из файла. Загрузка файла может быть выполнена либо через URL, либо через локальную файловую систему.

Предупреждение: метод load_file() делегирует свою работу PHP функции file_get_contents. Если для allow_url_fopen в файле php.ini не задано значение true, возможно, вы не сможете открыть удаленный файл таким способом. В этом случае вы всегда можете воспользоваться библиотекой CURL для загрузки удаленных страниц, а затем прочитать их с помощью метода load().

Доступ к информации

Получив объект DOM, вы можете начать работать с ним, используя find() и создавая коллекции. Коллекция — это группа объектов, найденных с помощью селектора — синтаксис очень похож на jQuery.

В этом примере HTML мы рассмотрим, как получить доступ к информации во втором абзаце, изменить ее и затем вывести результаты.

Использование метода find() всегда возвращает коллекцию (массив) тегов, если только вы не укажете, что вам нужен только n-й дочерний элемент, в качестве второго параметра.

Строки 2-4: загрузить HTML из строки, как описано выше.

Строка 7: эта строка находит все теги

в HTML и возвращает их в виде массива. Первый абзац будет иметь индекс 0, а последующие абзацы будут проиндексированы соответственно.

Cтрока 10: получает доступ ко второму элементу в нашей коллекции абзацев (индекс 1) и добавляет к его атрибуту innertext. Innertext представляет содержимое между тегами, в то время как externaltext представляет содержимое, включая тег. Мы могли бы полностью заменить тег, используя externaltext.

Мы собираемся добавить еще одну строку и изменить класс нашего второго тега абзаца.

HTML-результат команды save будет выглядеть так:

Другие селекторы

Вот еще несколько примеров селекторов. Если вы использовали jQuery, они покажутся вам очень знакомыми.

Первый пример не совсем интуитивно понятен — все запросы по умолчанию возвращают коллекции, даже запросы идентификаторов, которые должны возвращать только один результат. Однако, указав второй параметр, мы говорим: «вернуть только первый элемент этой коллекции».

Это означает, что $single — это отдельный элемент, а не массив элементов с одним элементом.

Остальные примеры говорят сами за себя.

Документация

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

Шаг 3. Реальный пример

Чтобы запустить эту библиотеку в действие, мы собираемся написать быстрый сценарий для получения содержимого веб-сайта Nettuts и составить список статей, представленных на сайте, по заголовкам и описаниям… только в качестве примера. Скрейпинг — сложная область в Интернете, и ее не следует выполнять без разрешения.

Мы начнем с включения библиотеки и вызова функции getArticles со страницей, которую мы хотели бы начать анализировать. В этом случае мы начинаем ближе к концу и будем добры к серверу Nettuts.

Мы также объявляем глобальный массив, чтобы упростить сбор всей информации о статье в одном месте. Прежде чем приступить к анализу, давайте посмотрим, как описывается краткое описание статьи на Nettuts+.

Это представляет собой основной формат сообщений на сайте, включая комментарии исходного кода. Почему комментарии важны? Они считаются узлами парсера.

Шаг 4. Запуск функции парсинга

Мы начинаем, объявляя нашу глобальную переменную, создавая новый объект simple_html_dom, а затем загружая страницу, которую мы хотим проанализировать. Эта функция будет вызываться сама собой позже, поэтому мы настроим ее на прием URL-адреса в качестве параметра.

Шаг 5. Поиск нужной нам информации

Это основа функции getArticles. Нужно присмотреться, чтобы действительно понять, что происходит.

Строка 1: создает массив элементов — div с классом preview. Теперь у нас есть коллекция статей, хранящихся в $items.

Строка 5: $post теперь относится к одному div класса preview. Если мы посмотрим на исходный HTML, то увидим, что третий дочерний элемент — это H1, содержащий заголовок статьи. Мы берем это и присваиваем его $article[index][0].

Не забудьте начинать с 0 и подсчитывать комментарии при попытке определить правильный индекс дочернего узла.

Line 6: Шестой ребенок $post это

Шаг 6. Пагинация

Первое, что мы делаем, это определяем, как найти нашу следующую страницу. В Nettuts+ URL-адреса легко определить, но мы собираемся притвориться, что это не так, и получим следующую ссылку путем анализа.

Если мы посмотрим на HTML, мы увидим следующее:

Если есть следующая страница (и не всегда будет), мы найдем якорь с классом ‘nextpostslink’. Теперь эта информация может быть использована.

В первой строке мы видим, сможем ли мы найти привязку с классом nextpostslink. Обратите особое внимание на второй параметр для find(). Это указывает, что мы хотим, чтобы возвращался только первый элемент (индекс 0) из найденной коллекции. $next будет содержать только один элемент, а не группу элементов.

Затем мы присваиваем HREF ссылки переменной $URL. Это важно, потому что мы собираемся уничтожить объект HTML. Из-за утечки памяти в циклических ссылках php5 текущий объект simple_html_dom должен быть очищен и сброшен перед созданием другого. Невыполнение этого требования может привести к потере всей доступной памяти.

Наконец, мы вызываем getArticles с URL-адресом следующей страницы. Эта рекурсия заканчивается, когда больше нет страниц для анализа.

Шаг 7. Вывод результатов

Сначала мы собираемся создать несколько основных стилей. Это совершенно произвольно — вы можете сделать вывод таким, каким пожелаете.


Далее мы собираемся добавить небольшой кусочек PHP на страницу для вывода ранее сохраненной информации.

Конечным результатом является одна HTML-страница со списком всех статей, начиная со страницы, указанной первым вызовом getArticles().

Шаг 8. Заключение

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

На таком сайте, как Nettuts, с текущими 78 страницами учебных пособий, это может занять более одной минуты.

Из этого урока вы должны научиться разбирать HTML. Есть и другие методы для работы с DOM, включая встроенный в PHP, который позволяет вам работать с мощными селекторами xpath для поиска элементов. Для простоты использования и быстрого запуска я считаю эту библиотеку одной из лучших. В качестве заключительного замечания всегда помните, чтобы получить разрешение, прежде чем парсить сайт; это важно. Спасибо за чтение!

Пишем php парсер сайтов с нуля

Очень многие из нас хотели бы быстро наполнить сайт контентом. Я покажу вам, как несколько тысяч материалов собрать всего лишь за несколько часов.

Парсер на php — раз плюнуть!

Приветствую вас, наши дорогие читатели. Сегодня решил написать сложную статью про парсеры (сбор информации со сторонних ресурсов).

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

Шаг 1 — PHP Simple HTML DOM Parser

Для парсинга сайтов мы будем использовать простецкую библиотечку под названием PHP Simple HTML DOM Parser, которую вы сможете скачать на сайте разработчика. Данный класс поможет вам работать с DOM-моделью страницы (дерево документа). Т.е. главная идея нашей будущей программы будет состоять из следующих пунктов:

  1. Скачиваем нужную страницу сайта
  2. Разбираем её по элементы (div, table, img и прочее)
  3. В соответствии с логикой получим определённые данные.

Давайте же начнём написание нашего php парсера сайтов.

Для начала подключим нашу библиотеку с помощью следующей строки кода:

Шаг 2 — Скачиваем страничку

На этом этапе мы смогли подключить файл к проекту и теперь пришла пора скачать страничку для парсинга.

В нашей библе есть две функции для получения удалённой страницы сайта. Вот эти функции

    str_get_htm() — получает в качестве параметров обычную строку. Это полезно, если вы стянули страничку с помощью CURL или метода file_get_contents. Пример использования:

После скачивания каждой страницы вам требуется подчищать память, дабы парсеру было легче работать и не так сильно грузился ваш сервер. Эта функция вызовется с помощью данного кода:

Шаг 3 — Ищем нужные элементы на странице

После получения DOM-модели мы можем приступить непосредственно к поиску нужного элемента-блока в полученном коде.

Большая часть функций поиска использует метод find(selector, [index]). Если не указывать индекс, то функция возвратит массив всех полученных элементов. В противном случае метод вернёт элемент с номером [index].

Давайте же приведу вам первый пример. Спарсим мою страничку и найдём все картинки.

Шаг 4 — Параметры поиска

Надеюсь все уже поняли, что в метод find() можно писать как теги (‘a’), так и id’шники (‘#id’), классы (‘.myclass’), комбинации из предыдущих элементов (‘div #id1 span .class’). Таким образом вы сможете найти любой элемент на странице.

Если метод поиска ничего не найдёт, то он возвратит пустой массив, который приведёт к конфликту. Для этого надо указывать проверку с помощью фукнции count(), которую я использовал выше в примере.

Также вы можете производить поиск по наличию атрибутов у искомого элемента. Пример:

Замечу, что у каждого вложенного тега так же есть возможность поиска!

Есть много вариантов поиска по атрибутам. Перечислять не стану, для более полного руководства прошу пройти на сайт разработчиков :)

Обычный текст, без тегов и прочего, можно искать так find(‘text’). Комментарии аналогично find(‘comment’).

Шаг 5 — Поля элементов

Каждый найденный элемент имеет несколько структур:

  1. $seo->tag Прочитает или запишет имя тега искомого элемента.
  2. $seo->outertext Прочитает или запишет всю HTML-структуру элемента с ним включительно.
  3. $seo->innertext Прочитает или запишет внутреннюю HTML-структуру элемента.
  4. $seo->plaintext Прочитает или запишет обычный текст в элементе. Запись в данное поле ничего не поменяет, хоть возможность изменения как бы присутствует.

Эта возможность очень просто позволяет бегать по DOM-дереву и перебирать его в зависимости от ваших нужд.

Если вы захотите затереть какой-либо элемент из дерева, то просто обнулить значение outertext, т.е. $div->outertext = «» ; Можно поэксперементировать с удалением элементов.

P.S. Я обнаружил проблему с кодировками при очистке и всяческими манипуляциями с полем innertext . Пришлось использовать outertext и затем с помощью функции strip_tags удалял ненужные теги.

Шаг 6 — Дочерние элементы


Разработчики данной библиотеки позаботились так же и о том, чтобы вам было легко перемещаться по дочерним и родительским элементам дерева. Для этого ими были любезно созданы следующие методы:

  1. $seo->children ( [int $index] ) Возвращает N-ый дочерний элемент, иначе возвращает массив, состоящий из всех дочерних элементов.
  2. $seo->parent() Возвращает родительский элемент искомого элемента.
  3. $seo->first_child() Возвращает первый дочерний элемент искомого элемента, или NULL, если результат пустой
  4. $seo->last_child() Возвращает последний дочерний элемент искомого элемента, или null, если результат пустой
  5. $seo->next_sibling() Возвращает следующий родственный элемент искомого элемента, или null, если результат пустой
  6. $seo->prev_sibling() Возвращает предыдущий родственный элемент искомого элемента, или null, если результат пустой

Я особо не пользовался этими возможностями, потому что они ещё ни разу не пригодились мне. Хотя один раз при разборе таблицы использовал, потому что они структурированы, что делает разбор очень простым и лёгким.

Шаг 7 — Практика

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

Парсинг от А до Я

Блог о программировании парсеров и web-автоматизации

Поиск по блогу

четверг, 20 мая 2010 г.

PHP Simple HTML DOM Parser

Сегодня я немного расскажу про библиотеку для парсинга HTML под названием PHP Simple HTML DOM Parser . В последнее время частенько ей пользовалась: нравятся ее возможности и простота. Скачать библиотеку можно со страницы. В комментарии к сказано:

A simple PHP HTML DOM parser written in PHP5+, supports invalid HTML, and provides a very easy way to handle HTML elements.

То есть нам обещают массу плюсов, основные из которых скорость и поддержка невалидного html-кода. Изучив документацию, можно вдохновиться на подвиги: возможности библиотеки действительно впечатляют. Кроме всего прочего — удобный, интуитивно понятный синтаксис.

Еще в числе плюсов, которые я отметила, — отсутствие проблем с кодировками. Часто бывает, что, получив содержимое страницы с помощью, например, file_get_contents, кодировку данных на промежуточном этапе приходится преобразовывать. Здесь же такой надобности у меня пока что не возникало.

С помощью этой библиотеки вы можете обращаться к элементам и атрибутам элементов, искать определенного уровня вложенные элементы, фильтровать их, искать текст и комментарии(!).

Приведу примеры из документации:

Ну и, конечно, стандарто — в библиотеку заложена возможность перемещения по списку элементов объектного дерева. Для этого используются:

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

Для теста возьмем элементарную задачку — выведем на экран ссылки с анкорами с главной страницы Гугля. Для сравнения буду использовать работу через стандартный domDocument.

Вот так будет выглядеть код с использованием Simple HTML DOM Parser :

А вот код с использованием domDocument :

Проведя по 5 запусков каждого варианта получила среднее время выполнения скрипта:

  • для Simple HTML DOM — 0,8096
  • для domDocument — 0,7326

Тут, хотя и Simple HTML DOM проигрывает, результаты различаются не сильно. Думаю, что надо будет потом протестировать скорость на чем-нибудь более сложном, с поиском и перемещением по узлам DOM-дерева.

Зато я визуально смогла оценить работу библиотеки Simple HTML DOM Parser с «невалидным» html. Если перед строкой

не поставить «@», то при работе скрипта с domDocument вывалится куча варнингов типа:

Warning: DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: Tag nobr invalid in Entity, line: 5
Warning: DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: htmlParseEntityRef: expecting ‘;’ in Entity, line: 5

Кроме того видна разница и в выводе результатов (кодировка):

В общем, библиотека мне нравится, кто еще не пользовался — советую попробовать.

Чтобы быть в курсе обновлений блога, можно подписаться на RSS.

PHP Simple HTML DOM Parser: выберите только DIVs с несколькими классами

Я искал, как сумасшедший, и не нашел решения. Проблема проста.

Скажем, у меня есть 3 DIVs:

Итак, очень просто. Я просто хочу найти TEXT3, у которого есть BOTH class1 и class2. Используя простой HTML DOM Parser, я не могу заставить его работать.

Вот что я пробовал:

Проблема заключается в том, что

он находит все из них, поскольку запятая, как OR, если я оставляю запятую, она ищет вложенный класс2 внутри класса1. Я просто ищу И.

ИЗМЕНИТЬ

Благодаря 19greg96 я узнал, что

работает, проблема в том, что он ищет именно те два в этом порядке. Скажем, у меня есть

тогда он работает, и если у меня есть

он работает, когда я помещаю asterix, поскольку он ищет подстроку:


ПРОБЛЕМА

Я знаю только, что класс1 и класс3 есть, но, возможно, другие и в случайном порядке. Это все еще не работает. Любая идея, как просто искать A и B в любом случайном порядке? Так что

работает с этим примером?

РЕДАКТИРОВАТЬ 2: Поскольку это ошибка в анализаторе dom (протестировано на версии 1.5), не существует простого способа сделать это. Решение, которое я мог придумать:

в основном вы находите все элементы с классом один, затем перебираете те элементы, которые имеют класс два (в данном случае три).

Простой ответ (должен работать в соответствии со спецификацией HTML):

это будет искать любой тип элемента (div, img, a и т.д.), который имеет и class1, и class2. Если вы хотите указать тип элемента для добавления, добавьте его в начало без . лайк:

Если между двумя указанными классами есть пробел, он будет сопоставлять элементы как с классами, так и с элементами, вложенными в элемент, с первым классом:

редактировать: я попробовал ваш код и обнаружил, что решения выше не работают. Решение, которое работает, однако, заключается в следующем:

Блог рядового веб-разработчика

PHP Simple HTML DOM Parser пример использования

Ноя. 17, 2010| 22:49

Наверное, любой php-разработчик рано или поздно сталкивается с задачей написания html парсера. Задачи разнообразные : вытащить телефоны и адреса поставщиков с сайта желтых страниц, расписание кинотеатра на день или даже просто распарсить большой справочник (двухколоночную таблицу), который почему-то прислали в html, а не в excel-файле. Способов море : с использованием регулярных выражений, строковых функций (strpos, strstr и т.д.), с помощью DOM и так далее. Один из моих парсеров загружал страницу, обрезал начало и конец, конвертил из cp1251 в utf8, присоединял в начало и конец xml узлы и дальше все парсилось с помощью SimpleXML и XPath.

А потом в один рабочий день я нашел PHP Simple HTML DOM Parser универсальный (пока не было задач, с которыми он бы не справился), удобный в использовании, хороший парсер. На сайте есть хорошая и понятная документация, а я покажу пример всего из 60-70 строк кода, который вытащит нам 1000 последних добавленных машин в Астане с сайта kolesa.kz (1000 потому что сайт выдает 1000 машин максимум на запрос).

Итак, в БД вам потребуется три таблички для хранения данных:

CREATE TABLE IF NOT EXISTS `kolesa_brands` (
`id` tinyint(3) NOT NULL,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

Таблица для хранения марок автомобилей : уникальный идентификатор и наименование. Марки мы возьмем из выпадающего списка «Марка» с поисковой панели Колес

CREATE TABLE IF NOT EXISTS `kolesa_models` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`brand_id` tinyint(3) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `brand_id` (`brand_id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Таблица для хранения моделей : уникальный идентификатор, идентификатор марки, наименование. Уникальный ключ по идентификатору марки и наименованию нужен для заполнения этой таблицы. Можно было сделать и по другому, но так мы заполним все таблицы за один проход.

CREATE TABLE IF NOT EXISTS `kolesa_cars` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`model_id` int(11) NOT NULL,
`price` int(11) NOT NULL,
`year` year(4) NOT NULL,
`region` tinyint(2) NOT NULL,
`link` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Таблица для хранения автомобилей : идентификатор, идентификатор модели, цена, год выпуска, код региона, ссылка.

Собственно сам исходный код :

require ( ‘simple_html_dom.php’ );

$dbn = mysql_connect ( ‘localhost’ , ‘user’ , ‘password’ );
mysql_select_db ( ‘parser’ , $dbn );
mysql_query ( ‘SET NAMES utf8’ );

$brands = array();

$html = str_get_html ( file_get_contents ( ‘http://kolesa.kz’ ));
$insert = ‘INSERT INTO kolesa_brands (id, name) VALUES ‘ ;
$select = $html -> find ( ‘select[ > , 0 );
foreach( $select -> find ( ‘option’ ) as $opt )
<
if( $opt -> value == » ) continue;
$insert .= ‘(‘ . $opt -> value . ‘, \» . $opt -> plaintext . ‘\’),’ ;
$brands [ $opt -> value ] = $opt -> plaintext ;
>
mysql_query ( rtrim ( $insert , ‘,’ ));

$page = file_get_contents ( $link );
if( strpos ( $page , ‘Увы, нет таких объявлений’ ) !== false )
<
break;
>
$html = str_get_html ( $page );

$div = $html -> find ( ‘div.lcol’ , 0 );

foreach( $div -> find ( ‘div[ > ) as $ob )
<
if(! is_object ( $ob )) continue;

$name = $ob -> find ( ‘a.mm’ , 0 )-> plaintext ;

foreach( $brands as $k => $v )
<
if( strpos ( $name , $v ) !== false )
<
$name = trim ( str_replace (array( $v , ‘ ‘ ), » , $name ));
mysql_query ( ‘INSERT IGNORE INTO kolesa_models (brand_id, name) VALUES (‘ . $k . ‘, \» . $name . ‘\’)’ );
break;
>
>
$model_id = mysql_insert_id ();
if( $model_id == 0 )
<
$res = mysql_query ( ‘SELECT > . $k . ‘ AND name = \» . $name . ‘\» );
$model_id =(int) mysql_result ( $res , 0 );
>

$price = $ob -> find ( ‘span.price’ , 0 )-> plaintext ;
$price = trim ( str_replace (array( ‘$’ , ‘ ‘ ), » , $price ));

$year = $ob -> find ( ‘span.year’ , 0 )-> plaintext ;
$year = trim ( str_replace (array( ‘г.’ , ‘ ‘ ), » , $year ));

$link = $ob -> find ( ‘a.mm’ , 0 )-> href ;

mysql_query ( ‘INSERT INTO kolesa_cars (model_id, price, year, region, link) VALUES (‘ . $model_id . ‘, ‘ . $price . ‘, ‘ . $year . ‘, 1, \» . $link . ‘\’)’ );
>
>

Сильно не ругайте, для подсветки использовал функцию highlight_file()

  • Подключаем файл с парсером, подключаемся к БД
  • Загружаем страницу kolesa.kz, отдаем строку парсеру. Главная функция парсера find(), она всегда возвращает массив. Поэтому при поиске выпадающего списка марок вторым аргументом указываем 0, чтобы получить первый найденный элемент. Конструкция «select[ (марка) заполняем массив brands и записываем в таблицу марок. Обратите внимание, что можно обратиться к атрибуту $opt->value.
  • Далее в цикле дергаем странички, находим там div класса lcol, в котором содержится список объявлений. Каждое объявление хранится в div класса good. Но есть еще выделенные объявления, они выводятся в div классах типа good mark_2. В любом случае конструкция div[ .
  • Вытаскиваем наименование марки и модели, цену, год. Производим чистку полученных значений, чтобы положить в БД. Разделяем марку и модель. INSERT IGNORE INTO kolesa_models ложит в БД модели автомобилей, IGNORE нужен, чтобы не дублировались модели. Специально для этого делали уникальный ключ в таблице, если бы не было IGNORE то БД бы падала от ошибки, а так всего лишь предупреждение и продолжает работать.

Ну в общем и все. У парсера еще есть куча классных фич, таких как: навешивание callback функций, перемещение по DOM-дереву, добавление узлов и т.д.