HtmlAgilityPack c# Спарсить расписание с сайта

177
28 января 2022, 06:50

Вопрос заключается в том как именно спарсить расписание.


                string url = "https://rsue.ru/raspisanie/";
                using (var webClient = new WebClient())
                {
                    var pars = new NameValueCollection();
                    pars.Add("f", "3");
                    pars.Add("k", "3");
                    pars.Add("g", "6");
                    var response = webClient.UploadValues(url, pars);
                    string str = System.Text.Encoding.UTF8.GetString(response);
                    HtmlDocument html = new HtmlDocument();
                    html.LoadHtml(str);
                    //с этого момента дописать
                    HtmlNodeCollection nodes = html.DocumentNode.SelectNodes("//div[@class=\"col-lg-2 col-md-2 col-sm-2\"]");
                    if(nodes != null)
                    {
                        foreach(var node in nodes)
                        {
                            var link = node.SelectSingleNode(".//div[@class=\"col-lg-12\"]");
                            Console.WriteLine(link);
                        }
                    }
                    else
                    {
                        Console.WriteLine("Empty");
                    }

                    Console.ReadKey();
Answer 1

Ну для начала, вашей "неточностью" (хоть некоторые пишут как и вы), является то, что вы экранируете двойные кавычки, HtmlAgilityPack (HAP) очень хорошо дружит с одинарными. По этому вы смело можете писать .SelectSingleNode(".//div[@class='col-lg-12']").

Далее по самому вопросу. Откройте инструменты разработчика и найдите нужную "ноду", через какое-то время вы увидите всю структуру сайта:

Что мы тут видим?

  • <div class="container"> - основная нода с нужным нам контентом (их там несколько, учтите это!).
    • <h1> - название группы.
    • <h1 class="ned"> - вид/тип недель.
    • <div class="row"> - контент этой категории недель.
      • <div class="col-lg-2 col-md-2 col-sm-2" id="" - отдельно расписание каждого дня.
        • <div class="col-lg-12" id="nedelya-select"> - название дня недели.
        • <div class="col-lg-12 day"> - контент дня.

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

Хорошо, давайте теперь по порядку будет получать нужные нам данные:

  • Получаем основную ноду:

    var container = html.DocumentNode.SelectSingleNode("//div[@class='container' and child::h1[@class='ned']]");
    

Тут из за того, что сайт имеет несколько <div class="container">, я добавил дополнительную проверку, которая проверяет наличие в дочернем элементе h1 с нужным классом, в итоге мы получим ту ноду, которая:

  1. Является div с классом container.
  2. Внутри себя содержит <h1 class="ned">.

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

  • Получаем названия категорий (черные/нечетные):

    var categories = container.SelectNodes("./h1[@class='ned']");
    
  • Получаем контент каждой категории:

    foreach (var category in categories)
    {
        var content = category.SelectSingleNode("./following-sibling::div[@class='row']");
    }
    

Мы перебираем полученные заголовки и берем следующую, после них ноду, div с указанным классом. Вообще я думаю это все можно преобразовать в Dictionary для удобства:

var categories = container.SelectNodes("./h1[@class='ned']").ToDictionary(k => k.InnerText, v => v.SelectSingleNode("./following-sibling::div[@class='row']"));

Эта строчка объединяет в себе предыдущие запросы на получение четных и нечетных, а также получения их содержимого. Ключом будет название категории, а значением ключа нужная нам div.

Чтож, можем двигаться дальше...

  • Получим все дни недели:

    foreach (var category in categories)
    {
        Console.WriteLine(category.Key);
        var weekday = category.Value.SelectNodes("./div[@id]");
    }
    

Тут все просто, из каждой полученной категории, забираем все div, которые имеют id (нам сайт его выдает пустой). Поняв принцип, смело можем переработать строку формирования Dictionary, дописав туда необходимый XPath:

var categories = container.SelectNodes("./h1[@class='ned']").ToDictionary(k => k.InnerText, v => v.SelectSingleNode("./following-sibling::div[@class='row']").SelectNodes("./div[@id]"));

Тут я дописал .SelectNodes("./div[@id]") для получения всех дней недели. Наверно у вас появился вопрос "почему не в одну XPath?", отвечу - я без понятия как заставить его забирать только дни недели внутри указанного div, если объединить, то первая категория будет иметь 11 дней недели (все, включая нечетные), а вторая будет иметь только свои.

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

Напоследок пример вывода названия категории и дней недели:

foreach (var category in categories)
{
    Console.WriteLine(category.Key);
    foreach (var weekday in category.Value)
    {
        var name = weekday.SelectSingleNode("./div[starts-with(@id, 'nedelya')]")?.InnerText;
        Console.WriteLine($"- {name}");
    }
}

Ну и весь код:

private static async Task<string> GetSchedule(int faculty, int course, int group)
{
    var url = "https://rsue.ru/raspisanie/";
    var postContent = new Dictionary<string, string>
    {
        ["f"] = $"{faculty}",
        ["k"] = $"{course}",
        ["g"] = $"{group}",
    };
    using var client = new HttpClient();
    var response = await client.PostAsync(url, new FormUrlEncodedContent(postContent));
    return await response.Content.ReadAsStringAsync();
}
public static async void LoadData()
{
    var str = await GetSchedule(3, 3, 6);
    HtmlDocument html = new HtmlDocument();
    html.LoadHtml(str);
    var container = html.DocumentNode.SelectSingleNode("//div[@class='container' and child::h1[@class='ned']]");
    var categories = container.SelectNodes("./h1[@class='ned']")
        .ToDictionary(k => k.InnerText, v => v.SelectSingleNode("./following-sibling::div[@class='row']").SelectNodes("./div[@id]"));
    foreach (var category in categories)
    {
        Console.WriteLine(category.Key);
        foreach (var weekday in category.Value)
        {
            var name = weekday.SelectSingleNode("./div[starts-with(@id, 'nedelya')]")?.InnerText;
            Console.WriteLine($"- {name}");
        }
    }
}

Если планируете и дальше работать с XPath, то советую эту шпаргалку, очень часто меня выручала.

READ ALSO
Границы для UI объектов в Unity

Границы для UI объектов в Unity

У меня есть CanvasНа нем есть некое поле Field и в нем объект Aim

146
C# граница TabControl и Panel

C# граница TabControl и Panel

Имеется проблема с визуализацией в MetroUIЕсли в MetroTabControl создать MetroPanel, и в параметре Dock поставить заполнение, то появляются такие полоски

167
Telegram Bot API не отправляет файл?

Telegram Bot API не отправляет файл?

С наступающим! (для всех, читающих в 2020 - с наступившим!) )

134
Обновление mysql

Обновление mysql

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

101