На написание статьи меня натолкнула другая статья, пользователя @web - Простой граббинг, сложных сайтов. C# (на примере Yvision.kz). В этой статье показано императивное решение задачи, я же задался вопросом - можно ли решить задачу, используя смешанные парадигмы, в частности функциональная (Linq) и императивная (ООП)?
Как оказалось - можно. Для этого я изначально поставил вопрос - что я хочу видеть в итоге? Есть сайт - Yvision, есть БД, куда надо будет сливать список статей, аналогично вышеуказанной статье, остается лишь мелочь - что же всё-таки мы будем сливать в БД?
Для этого был создан POCO-класс Yvision, который и будет характеризовать данные, которые мы хотим получить от сайта и которые мы будем записывать в БД.
-
public class Yvision
-
{
-
/// <summary>
-
/// Идентификатор статьи
-
/// </summary>
-
public string Id { get; set; }
-
/// <summary>
-
/// Наименование статьи
-
/// </summary>
-
public string Name { get; set; }
-
/// <summary>
-
/// Ссылка на статью
-
/// </summary>
-
/// <summary>
-
/// Автор статьи
-
/// </summary>
-
public string Author { get; set; }
-
/// <summary>
-
/// Блог автора
-
/// </summary>
-
public string AuthorUrl { get; set; }
-
/// <summary>
-
/// Изображение статьи
-
/// </summary>
-
public string Image { get; set; }
-
}
Дальше мне понадобился некий перечислитель, скрывающий методы работы с HtmlAgilityPack - так как программисту, пользующему библиотеку парсинга конкретно yvision.kz не всегда интересно знать как она устроена, главное, что она делает.
-
public class YviEnumerable : IEnumerable<Yvision>
-
{
-
public YviEnumerable()
-
{
-
_yvisions = new HtmlWeb()
-
.Load("http://www.yvision.kz")
-
.DocumentNode
-
//Выберем нужную нам ноду
-
.SelectSingleNode("//div[@class=\"mainContent main_page\"]")
-
//Выберем все дивы ноды
-
.Descendants("div")
-
//Где класс - статья
-
.Where(d => d.Attributes.Contains("class") && d.Attributes["class"].Value.Contains("home_article1 big"))
-
//Непосредственно трансформация данных
-
.Select(node => new Yvision
-
{
-
//Id дива с которого берем данные
-
Id = node.Id,
-
//Url новости
-
//Изображение
-
Image = _queryNodes(node, "hold").ChildNodes.Elements("img").First().Attributes["src"].Value,
-
//Наименование
-
Name = _queryNodes(node, "text1").ChildNodes.Elements("a").First().InnerText,
-
//Автор
-
//Блог автора
-
AuthorUrl = _queryNodes(node, "author").Element("a").Attributes["href"].Value
-
});
-
}
-
/// <summary>
-
/// Коллекция новостей Yvision
-
/// </summary>
-
private readonly IEnumerable<Yvision> _yvisions;
-
public IEnumerable<Yvision> Yvisions
-
{
-
get { return _yvisions; }
-
}
-
/// <summary>
-
/// Функция выборки единичного элемента с заданным классом
-
/// </summary>
-
private readonly Func<HtmlNode, string, HtmlNode> _queryNodes = (p, s) => p
-
.Descendants("div")
-
.First(d => d.Attributes.Contains("class") && d.Attributes["class"].Value.Contains(s));
-
/// <summary>
-
/// Возвращает перечислитель коллекции новостей
-
/// </summary>
-
/// <returns></returns>
-
public IEnumerator<Yvision> GetEnumerator()
-
{
-
return _yvisions.GetEnumerator();
-
}
-
IEnumerator IEnumerable.GetEnumerator()
-
{
-
return GetEnumerator();
-
}
-
}
Собственно на этом всё. Дальнейших танцев с бубнами особо не требуется - кое-где необходимо переписать стандартные функции на чистые (выбрасывать исключения нам не нужно) и даже местами можно улучшить код.
Использование перечислителя.
-
private static void Main()
-
{
-
Console.ForegroundColor = ConsoleColor.White;
-
foreach (var item in new YviEnumerable())
-
{
-
Console.WriteLine("postId: {0}, name: {1}, url: {2}, image: {3}, author: {4}, authorUrl: {5}", item.Id,
-
item.Image, item.Author, item.AuthorUrl);
-
}
-
Console.ReadLine();
-
}
На этом всё, жду(или не жду:) без разницы) конструктивных комментариев.
Кстати результат исполнения программы: