() translation by (you can also view the original English article)
Если вам нужно разобрать HTML, регулярные выражения не подходят. В этом руководстве вы узнаете, как использовать открытый, легко изучаемый парсер, чтобы читать, изменять и выводить HTML из внешних источников. Используя в качестве примера nettuts, вы узнаете, как получить список всех статей, опубликованных на сайте, и отобразить их.
Шаг 1. Подготовка
Первое, что вам нужно сделать, это загрузить копию библиотеки simpleHTMLdom, которая свободно доступна на sourceforge.
В загрузке есть несколько файлов, но вам нужен только файл simple_html_dom.php; остальное это примеры и документация.

Шаг 2. Основы парсинга
Эта библиотека очень проста в использовании, но есть некоторые основы, которые вы должны рассмотреть, прежде чем приступить к работе.
Загрузка HTML
1 |
$html = new simple_html_dom(); |
2 |
|
3 |
// Load from a string
|
4 |
$html->load('<html><body><p>Hello World!</p><p>We're here</p></body></html>'); |
5 |
|
6 |
// Load a file
|
7 |
$html->load_file('http://net.tutsplus.com/'); |
Вы можете создать свой начальный объект, загрузив HTML из строки или из файла. Загрузка файла может быть выполнена либо через URL, либо через локальную файловую систему.
Предупреждение: метод load_file() делегирует свою работу PHP функции file_get_contents. Если для allow_url_fopen в файле php.ini не задано значение true, возможно, вы не сможете открыть удаленный файл таким способом. В этом случае вы всегда можете воспользоваться библиотекой CURL для загрузки удаленных страниц, а затем прочитать их с помощью метода load().
Доступ к информации

Получив объект DOM, вы можете начать работать с ним, используя find() и создавая коллекции. Коллекция - это группа объектов, найденных с помощью селектора - синтаксис очень похож на jQuery.
1 |
<html>
|
2 |
<body>
|
3 |
<p>Hello World!</p> |
4 |
<p>We're Here.</p> |
5 |
</body>
|
6 |
</html>
|
В этом примере HTML мы рассмотрим, как получить доступ к информации во втором абзаце, изменить ее и затем вывести результаты.
1 |
# create and load the HTML
|
2 |
include('simple_html_dom.php'); |
3 |
$html = new simple_html_dom(); |
4 |
$html->load("<html><body><p>Hello World!</p><p>We're here</p></body></html>"); |
5 |
|
6 |
# get an element representing the second paragraph
|
7 |
$element = $html->find("p"); |
8 |
|
9 |
# modify it
|
10 |
$element[1]->innertext .= " and we're here to stay."; |
11 |
|
12 |
# output it!
|
13 |
echo $html->save(); |
Использование метода find() всегда возвращает коллекцию (массив) тегов, если только вы не укажете, что вам нужен только n-й дочерний элемент, в качестве второго параметра.
Строки 2-4: загрузить HTML из строки, как описано выше.
Строка 7: эта строка находит все теги <p> в HTML и возвращает их в виде массива. Первый абзац будет иметь индекс 0, а последующие абзацы будут проиндексированы соответственно.
Cтрока 10: получает доступ ко второму элементу в нашей коллекции абзацев (индекс 1) и добавляет к его атрибуту innertext. Innertext представляет содержимое между тегами, в то время как externaltext представляет содержимое, включая тег. Мы могли бы полностью заменить тег, используя externaltext.
Мы собираемся добавить еще одну строку и изменить класс нашего второго тега абзаца.
1 |
$element[1]->class = "class_name"; |
2 |
echo $html->save(); |
HTML-результат команды save будет выглядеть так:
1 |
<html>
|
2 |
<body>
|
3 |
<p>Hello World!</p> |
4 |
<p class="class_name">We're here and we're here to stay.</p> |
5 |
</body>
|
6 |
</html>
|
Другие селекторы
Вот еще несколько примеров селекторов. Если вы использовали jQuery, они покажутся вам очень знакомыми.
1 |
# get the first occurrence of id="foo"
|
2 |
$single = $html->find('#foo', 0); |
3 |
|
4 |
# get all elements with class="foo"
|
5 |
$collection = $html->find('.foo'); |
6 |
|
7 |
# get all the anchor tags on a page
|
8 |
$collection = $html->find('a'); |
9 |
|
10 |
# get all anchor tags that are inside H1 tags
|
11 |
$collection = $html->find('h1 a'); |
12 |
|
13 |
# get all img tags with a title of 'himom'
|
14 |
$collection = $html->find('img[title=himom]'); |
Первый пример не совсем интуитивно понятен - все запросы по умолчанию возвращают коллекции, даже запросы идентификаторов, которые должны возвращать только один результат. Однако, указав второй параметр, мы говорим: «вернуть только первый элемент этой коллекции».
Это означает, что $single - это отдельный элемент, а не массив элементов с одним элементом.
Остальные примеры говорят сами за себя.
Документация
Полная документация по библиотеке может быть найдена на странице документации проекта.

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

1 |
include('simple_html_dom.php'); |
2 |
|
3 |
$articles = array(); |
4 |
getArticles('http://net.tutsplus.com/page/76/'); |
Мы начнем с включения библиотеки и вызова функции getArticles со страницей, которую мы хотели бы начать анализировать. В этом случае мы начинаем ближе к концу и будем добры к серверу Nettuts.
Мы также объявляем глобальный массив, чтобы упростить сбор всей информации о статье в одном месте. Прежде чем приступить к анализу, давайте посмотрим, как описывается краткое описание статьи на Nettuts+.
1 |
<div class="preview"> |
2 |
<!-- Post Taxonomies -->
|
3 |
<div class="post_taxonomy"> ... </div> |
4 |
<!-- Post Title -->
|
5 |
<h1 class="post_title"><a>Title</a></h1> |
6 |
<!-- Post Meta -->
|
7 |
<div class="post_meta"> ... </div> |
8 |
<div class="text"><p>Description</p></div> |
9 |
</div>
|
Это представляет собой основной формат сообщений на сайте, включая комментарии исходного кода. Почему комментарии важны? Они считаются узлами парсера.
Шаг 4. Запуск функции парсинга
1 |
function getArticles($page) { |
2 |
global $articles; |
3 |
|
4 |
$html = new simple_html_dom(); |
5 |
$html->load_file($page); |
6 |
|
7 |
// ... more ...
|
8 |
}
|
Мы начинаем, объявляя нашу глобальную переменную, создавая новый объект simple_html_dom, а затем загружая страницу, которую мы хотим проанализировать. Эта функция будет вызываться сама собой позже, поэтому мы настроим ее на прием URL-адреса в качестве параметра.
Шаг 5. Поиск нужной нам информации

1 |
$items = $html->find('div[class=preview]'); |
2 |
|
3 |
foreach($items as $post) { |
4 |
# remember comments count as nodes
|
5 |
$articles[] = array($post->children(3)->outertext, |
6 |
$post->children(6)->first_child()->outertext); |
7 |
}
|
Это основа функции getArticles. Нужно присмотреться, чтобы действительно понять, что происходит.
Строка 1: создает массив элементов - div с классом preview. Теперь у нас есть коллекция статей, хранящихся в $items.
Строка 5: $post теперь относится к одному div класса preview. Если мы посмотрим на исходный HTML, то увидим, что третий дочерний элемент - это H1, содержащий заголовок статьи. Мы берем это и присваиваем его $article[index][0].
Не забудьте начинать с 0 и подсчитывать комментарии при попытке определить правильный индекс дочернего узла.
Line 6: Шестой ребенок $post это <div class="text">. Нам нужен текст описания изнутри, поэтому мы берем внешний текст первого дочернего элемента - он будет включать тег абзаца. Отдельная запись в статьях теперь выглядит так:
1 |
$articles[0][0] = "My Article Name Here"; |
2 |
$articles[0][1] = "This is my article description" |
Шаг 6. Пагинация
Первое, что мы делаем, это определяем, как найти нашу следующую страницу. В Nettuts+ URL-адреса легко определить, но мы собираемся притвориться, что это не так, и получим следующую ссылку путем анализа.

Если мы посмотрим на HTML, мы увидим следующее:
1 |
<a href="http://net.tutsplus.com/page/2/" class="nextpostslink">»</a> |
Если есть следующая страница (и не всегда будет), мы найдем якорь с классом 'nextpostslink'. Теперь эта информация может быть использована.
1 |
if($next = $html->find('a[class=nextpostslink]', 0)) { |
2 |
$URL = $next->href; |
3 |
|
4 |
$html->clear(); |
5 |
unset($html); |
6 |
|
7 |
getArticles($URL); |
8 |
}
|
В первой строке мы видим, сможем ли мы найти привязку с классом nextpostslink. Обратите особое внимание на второй параметр для find(). Это указывает, что мы хотим, чтобы возвращался только первый элемент (индекс 0) из найденной коллекции. $next будет содержать только один элемент, а не группу элементов.
Затем мы присваиваем HREF ссылки переменной $URL. Это важно, потому что мы собираемся уничтожить объект HTML. Из-за утечки памяти в циклических ссылках php5 текущий объект simple_html_dom должен быть очищен и сброшен перед созданием другого. Невыполнение этого требования может привести к потере всей доступной памяти.
Наконец, мы вызываем getArticles с URL-адресом следующей страницы. Эта рекурсия заканчивается, когда больше нет страниц для анализа.
Шаг 7. Вывод результатов
Сначала мы собираемся создать несколько основных стилей. Это совершенно произвольно - вы можете сделать вывод таким, каким пожелаете.

1 |
#main { |
2 |
margin:80px auto; |
3 |
width:500px; |
4 |
} |
5 |
h1 { |
6 |
font:bold 40px/38px helvetica, verdana, sans-serif; |
7 |
margin:0; |
8 |
} |
9 |
h1 a { |
10 |
color:#600; |
11 |
text-decoration:none; |
12 |
} |
13 |
p { |
14 |
background: #ECECEC; |
15 |
font:10px/14px verdana, sans-serif; |
16 |
margin:8px 0 15px; |
17 |
border: 1px #CCC solid; |
18 |
padding: 15px; |
19 |
} |
20 |
.item { |
21 |
padding:10px; |
22 |
} |
Далее мы собираемся добавить небольшой кусочек PHP на страницу для вывода ранее сохраненной информации.
1 |
<?php
|
2 |
foreach($articles as $item) { |
3 |
echo "<div class='item'>"; |
4 |
echo $item[0]; |
5 |
echo $item[1]; |
6 |
echo "</div>"; |
7 |
}
|
8 |
?>
|
Конечным результатом является одна HTML-страница со списком всех статей, начиная со страницы, указанной первым вызовом getArticles().
Шаг 8. Заключение
Если вы анализируете большое количество страниц (скажем, весь сайт), это может занять больше времени, чем максимальное время выполнения, разрешенное вашим сервером. Например, запуск с моего локального компьютера занимает около одной секунды на страницу (включая время для извлечения).
На таком сайте, как Nettuts, с текущими 78 страницами учебных пособий, это может занять более одной минуты.
Из этого урока вы должны научиться разбирать HTML. Есть и другие методы для работы с DOM, включая встроенный в PHP, который позволяет вам работать с мощными селекторами xpath для поиска элементов. Для простоты использования и быстрого запуска я считаю эту библиотеку одной из лучших. В качестве заключительного замечания всегда помните, чтобы получить разрешение, прежде чем парсить сайт; это важно. Спасибо за чтение!