Как написать парсер страниц за 5 минут

в 17:17, , рубрики: .net, data mining, html, парсинг контента, метки: , , ,

Зачем?

У меня возникла необходимость построить графики статистики игроков c iccup.com
Когда разбирался с библиотекой искал на хабре, но ничего подходящего мне не нашел.
Поэтому попутно написал эту статью.

Технические детали

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

Реализация

Я решил не придумывать велосипеды, и сделал все максимально просто.
Скачиваем страницу с помощью класса WebClient, и с помощью библиотеки htmlagilitypack и xpath выражений парсим ее.

Собственно код

Основная функция

Тут мы читаем файл и вызываем парсинг

private static void Main(string[] args)
        {
            //Create timer
            Stopwatch sw = Stopwatch.StartNew(); // Таймер я использую для измерения производительности

            //Copyright
            Console.WriteLine("iCCup Fetcher");
            Console.WriteLine();

            //Read
            var lines = new List<string>(); // Тут мы читаем файлик в список.
            using (var r = new StreamReader("users.txt"))
            {
                string line;
                while ((line = r.ReadLine()) != null)
                {
                    lines.Add(line);
                }
            }

            //Write time taken
            sw.Stop();
            Console.WriteLine("Инициальзация заняла: {0}ms", sw.Elapsed.TotalMilliseconds);
            Console.WriteLine();

            //Write stuff
            foreach (string line in lines)
            {
                Console.WriteLine(GetStats(line)); // Тут для каждого аккаунта вызывается функция которая собственно все и делает.
            }

            //Stop at end
            Console.WriteLine("Нажмите любую кнопку чтобы выйти...");
            Console.ReadKey();
        }

GetStats()

Тут самое интересное, использование библиотеки, и парсинг.
SelectNodes() возвращает коллекцию нод подходящих под переданное xpath выражение. В данном случае элемент всегда один, поэтому [0] жестко вшито.

private static string GetStats(string nickname)
        {
            var doc = new HtmlDocument();
            var wc = new WebClient();
            var sb = new StringBuilder();
            var sw = Stopwatch.StartNew();

            string page = wc.DownloadString("http://ru.iccup.com/dota/gamingprofile/" + nickname + ".html");

            //Select team-profile or not
            int profileType = page.Contains(@"<div class=""profile-team"">") ? 1 : 0; // Тут решение примитивной, но важной проблемы. Если не учитывать это, то будут либо NullRefenceException, либо статистика будет не верной.
            //0 - Generic
            //1 - With team profile

            doc.LoadHtml(page); //Загружаем страницу из строки
            switch (profileType) //Тут нам 
            {
                case 0:
                    //Игрок
                    sb.Append("Игрок: ");
                    sb.Append(nickname);
                    sb.AppendLine();
                    //П-П-Л
                    sb.Append("Игры: ");
                    sb.Append(
                        doc.DocumentNode.SelectNodes(@"//*[@id=""profile""]/div[4]/div[2]/div[4]/div[2]")[0].InnerText); //Тут мы получаем текст из отдельного элемента найденного по его XPATH. Про нахождение XPATH чуть позже.
                    sb.AppendLine();
                    //Процент
                    sb.Append("Процент: ");
                    sb.Append(
                        doc.DocumentNode.SelectNodes(@"//*[@id=""profile""]/div[4]/div[2]/div[9]/div[2]")[0].InnerText);
                    sb.AppendLine();
                    //Очки
                    sb.Append("Очки: ");
                    sb.Append(
                        doc.DocumentNode.SelectNodes(@"//*[@id=""profile""]/div[4]/div[2]/div[3]/div[2]")[0].InnerText);
                    sb.AppendLine();
                    break;
                case 1:
                    //Игрок
                    sb.Append("Игрок: ");
                    sb.Append(nickname);
                    sb.AppendLine();
                    //П-П-Л
                    sb.Append("Игры: ");
                    sb.Append(
                        doc.DocumentNode.SelectNodes(@"//*[@id=""profile""]/div[5]/div[2]/div[4]/div[2]")[0].InnerText);
                    sb.AppendLine();
                    //Процент
                    sb.Append("Процент: ");
                    sb.Append(
                        doc.DocumentNode.SelectNodes(@"//*[@id=""profile""]/div[5]/div[2]/div[9]/div[2]")[0].InnerText);
                    sb.AppendLine();
                    //Очки
                    sb.Append("Очки: ");
                    sb.Append(
                        doc.DocumentNode.SelectNodes(@"//*[@id=""profile""]/div[5]/div[2]/div[3]/div[2]")[0].InnerText);
                    sb.AppendLine();
                    break;
            }
            sb.Append("Затраченно времени: " + sw.Elapsed.TotalMilliseconds.ToString() + "ms");
            sb.AppendLine();
            return sb.ToString();
        }

XPATH

Здесь все очень просто в любом WebKit браузере.
image
Иногда HAP не признает выражения, пока такое поведение выявил только с таблицами.

Заключение

Это быстрый и удобный способ быстро собирать данные с сайтов без сложной и/или не валидной табличной верстки. Надеюсь эта статья поможет в написании легких и быстрых парсеров.

Автор: l0cal

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js