Урок 6 — парсим HTML

Урок 6 — парсим HTML

Часто нам бывает нужно вытащить из веб-страницы какую-то информацию, например профиль из социальной сети. Многие до сих пор делают это с помощью регулярных выражений (или вообще с помощью поиска подстроки). В простейших случаях это работает, но в целом подход неправильный. Тут мне сразу же вспоминается известное обсуждение на Stackoverflow. Кстати говоря, полностью спарcить HTML с помощью регулярных выражений невозможно в принципе, т.к. это языки разных уровней в иерархии Хомского. К счастью в наш движок уже встроен парсер HTML, остается только им воспользоваться. И в этой статье я собираюсь показать как это делается на примере профиля Павла Дурова ВКонтакте.

Итак, начнем. Воспользоваться парсером довольно просто:

var doc = VkRequest.Request("http://vkontakte.ru/durov").GetHtmlDocument();

Все. Теперь у нас в переменной doc полностью спарсенный документ, представляющий собой древовидную структуру тегов. Все классы, связанные с парсингом находятся в пространстве имен ViKing.Engine.Html, но чтобы не прописывать его через using мы будем использовать ключевое слово var. Давайте для примера найдем адрес аватарки. Открываем страницу Дурова в Firefox и включаем Firebug. Смотрим на код для аватарки:

<div id="profile_avatar"><a id="profile_photo_link" onclick="бла-бла-бла" href="/photo1_205600277"><img src="http://cs1495.vkontakte.ru/u00001/a_a806ae7c.jpg" alt="" /></a></div>

Отсюда видно, что изображение аватара, находится внутри тега a (ссылка) c id="profile_photo_link". Вот так выглядит код для получения адреса картинки:

string avatar = doc.GetElementbyId("profile_photo_link").ChildNodes[0].Attributes["src"].Value;

Сначала мы находим элемент с id=profile_photo_link, затем обращаемся к массиву дочерних элементов. Он содержит 1 элемент — img. После этого мы берем значение атрибута src, который и указывает на адрес изображения. Если нужно получить саму картинку, то ее придется скачать:

Image avatarImage = VkRequest.Request(avatar).GetImage();

Следующий пример — получение всех постов со стены Павла. Текст поста на стене выглядит примерно так:

<div class="wall_post_text">Текст на стене</div>

Код для их получения:

var postNodes = doc.GetElementsByClassName("wall_post_text");List posts = new List();foreach (var item in postNodes){posts.Add(item.InnerText);}

Тут все очень просто. Мы находим на странице все элементы класса wall_post_text и берем у них свойство InnerText. Если вам нужно получить html-код поста, используйте InnerHtml. Тот же самый код можно гораздо короче записать через Linq:

var posts = doc.GetElementsByClassName("wall_post_text").Select(a => a.InnerText).ToList();

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

А теперь самая важная функция в парсере — XPath. Он позволяет строить запросы, с помощью которых можно очень быстро найти нужные теги. Подробнее о его синтаксисе можно прочитать на w3schools или по русски, но на 20 страницах вместо одной. Для примера давайте найдем адреса фотографий из раздела фотографии на странице профиля. Их код выглядит вот так:

<tr class="photos"><td><a onclick="бла-бла" href="/photo1_222740876?all=1"><img src="http://cs871.vkontakte.ru/u00001/127202895/s_d5bb4880.jpg"></a></td><td>... </td> ... </tr>

В принципе здесь тоже можно было бы применить GetElementsByClassName("photos"), а затем пройтись по массиву ChildNodes. Но здесь еще возникает дополнительная проблема, что на странице есть еще и ссылка с таким же классом, пришлось бы еще писать дополнительное условие, что название тега должно быть tr. С помощью XPath это делается гораздо проще:

var photoNodes = doc.DocumentNode.SelectNodes("//tr[@class='photos']/td/a/img");List photos = new List();foreach (var item in photoNodes){photos.Add(item.Attributes["src"].Value);}

Или через Linq:

var photos = doc.DocumentNode.SelectNodes("//tr[@class='photos']/td/a/img").Select(a => a.Attributes["src"].Value).ToList();

Первой строкой мы получаем список нужных тегов img, а затем берем из них атрибут src. Сначала стоит отметить, что запрос применяется к DocumentNode, то есть к корневому тегу документа. Запросы можно также применять и к любому дочернему тегу. Давайте теперь разберемся в том, как устроен запрос. «//» означает, что нужно искать тег по всему документу, независимо от расположения. Дальше идет имя тега «tr». Затем условие, что атрибут ‘class’ должен равняться ‘photos’
«[@class=’photos’]». Для обозначения имени атрибута всегда используется «@». Потом идет «/td/a/img», Путь от найденного тега tr, к тегу, который мы хотим получить.

И последний пример — давайте спарсим город, в котором живет Павел. Вот исходный html:

<div class="clear_fix "><div class="label fl_l">Город:</div><div class="labeled fl_l"><a onclick="..." href="...">Санкт-Петербург</a></div></div>

Это можно сделать вот такой командой:

string city = doc.DocumentNode.SelectSingleNode("//div1/../div[contains(@class, 'labeled')]").InnerText;

Мы находим на странице div, текст которого равняется ‘Город:’, затем поднимаемся на уровень выше, к родительскому тегу. Тут все как с папками и файлами: «..» означает родительский элемент, «.» — текущий. После этого мы ищем тег, класс которого содержит текст ‘labeled’, полный класс этого тега равняется «labeled fl_l». Обратите внимание, что здесь я использовал функцию SelectSingleNode, которая возвращает первый найденный тег. Кстати говоря, вместо doc.DocumentNode.SelectNodes можно писать просто doc.SelectNodes.

На этом все, подробнее про XPath читаем по указанным выше ссылкам. Архив с исходником: HtmlParser.rar

Comments are closed.