Пусть за нас все делают боты. Парсинг сайтов на C# и Anglesharp
Изучение технологии для автоматизации процесса который изначально не был сложным.
Проблема
Начало третьего семестра, и новый предмет "Алгоритмы и структуры данных" ведет тот же препод что прошлые два семестра вел у нас предмет "Алгоритмизация и программирование". Так как название последнего не предполагает чего либо конкретного то на нем нас вводили в специальность с помощью паскаля и лабораторных работ на чтение файлов, сортировок и вывода математических графиков на экран. Алгоритмы и структуры данных звучит более конкретно, нас будут учить различным алгоритмам и ничего лишнего быть не должно верно? Почти верно.
На первой лабораторной работе мы изучаем методы сортировки, входные данные даются нам в файле мы должны его отсортировать разными методами и сравнить их, все логично. И тут есть маленькое "но", мы сортируем два вида данных, числа и массив записей, в моем варианте это были фильмы с полями "Название" "Режиссер" "Жанр" "Год выпуска". Препод не понятно по какой причине заставляет нас выполнять дополнительную работу по реализации этих записей и потом их парсинг из файла по не совсем понятной мне причине. К сожалению, задание дано - нужно делать.
Это было небольшое вступление и ворчание о жизни, теперь то из за чего я на самом деле благодарен этому дополнительному геморрою с записями. Нам нужно заполнять файлы самостоятельно. Файл на 5000 тысяч чисел создать не сложно, а файл из 100 записей? Можно было бы написать скрипт и поля будут содержать значения "gdheno", но я решил сделать свою жизнь чуть интересней и заодно выучить полезную технологию. "А что если написать парсер который будет брать эти данные с imdb?"
Подготовка
Так как у меня есть год опыта работы на C# делать парсер я решил на нем. Небольшой гугл поиск и я понял что есть два способа сделать это. Первый - ничего кроме встроенных фич .Net, достать нужную инфу с помощью регулярных выражений и методов System.LINQ. И второй - использовать AngleSharp. Насмотревшись мемов про регулярные выражения и имея нежелание точить колесо и потом собирать велосипеды я остановился на Anglesharp'е. Работать с этой библиотекой оказалось очень просто, я думал что начав работу над проектом я буду изучать технологию дня 3, и потом засяду с кодом еще на 5 так как с вебом раньше я не работал. Но вышло так что сохранение данных в .json файл отняло у меня больше сил.
Приложение в итоге выглядит так:
В этом окне видно текст бокс с превью тех данных что мы достанем с imdb, дроп даун с количеством фильмов которые мы хотим достать, кнопку "Get" по нажатии которой происходит парсинг, и кнопку "Save" которая сохранит это в файл. Я согласен что создавать графический интерфейс для проги которая нужна только для одной простой задачи это лишнее усложнение, но Windows Forms Designer встроенный в Visual Studio делает этот процесс намного проще. К тому же, я решил делать лабораторную с графическим окном, почему бы немного не потренироваться? Итак, я хочу запарсить данные с сайта на Anglesharp, с чего мне начать?
Экшен
Чтобы запарсить страницу, необходимо ее получить, скачать html код этой страницы. В Anglesharp веб страницу представляет интерфейс IDocument который реализует методы необходимые нам для парсинга этой страницы. Чтобы скачать страницу с нужного url в формате этого интерфейса нужно действовать как предлагает нам этот туториал:
Теперь перейдем к самому вкусному мясу - парсинг этого html кода. Сейчас у нас есть переменная doc реализующая интерфейс IDocument и представляющая веб страницу которую мы только что достали. В этом интерфейсе есть много чего интересного, мы можем достать всю информацию о веб странице, включая head, body, title, url, cookie, мы можем добавить элементы или выполнить команды, это все очень круто, но нас сейчас интересуют методы GetElementsByClassName, GetElementsByName и GetElementsByTagName возвращающие коллекцию html элементов которые содержат переданный класс, имя или тег. Класс имя и тег нужны чтобы идентифицировать элемент на странице и показать его как то по особенному. Я уверен что на веб странице которую вы хотите запарсить практически каждый html элемент будет иметь какой либо из этих атрибутов.
Теперь давайте сделаем шаг назад и поговорим о том как вообще html элементы представлены в Anglesharp. Здесь классические отношения Parent и Child, у каждого элемента страницы который представлен интерфейсом IElement есть свои поля с Id, ClassName, Html и прочим, и также знакомые методы GetElementsByClassName и GetElementsByTagName которые дают нам доступ к детям этого элемента.
Давайте теперь применим эти знания на практике, на том что делал я. Заходим на страницу релизов imdb, нажимаем F12 и высматриваем способ получить список фильмов на этой странице. Замечаем что все фильмы находятся в элементе с классом list detail и в нем у каждый код принадлежит классу list_item odd или list_item even. То есть чтобы иметь лист из фильмов нам достаточно сделать так:
Хочу заметить что когда мы вызываем например метод GetElementsByClassName, то он вернет лист html элементов и они будут отсортированы в порядке "Кого я нашел первым". В моем примере я достаю лист элементов с классом list detail и потом беру детей первого элемента в этом листе, того элемента который в html странице расположен выше.
У нас есть список фильмов, нам нужно достать название, жанр, режиссера и год каждого элемента. Достать имя просто, оно находится внутри h4 атрибута в таблице и его можно достать с помощью метода QuerySelector:
Замечу что в атрибуте h4 у нас джек пот, мы имеем и имя и год выпуска. Делить их будем чуть позже, сейчас у нас чуть более интересная ситуация с жанром. Они раскиданы в span атрибутах класса cert-runtime-genre и в них замешаны разделители |, избавиться от них не сложно:
Достаем все span атрибуты и затем из них отбираем те чей текст не равен |, изи. И тут есть но, может проблема только моя, но мой браузер не показывает мне что там есть возрастной рейтинг и моя строка жанров выглядит как "Sci-Fi Action 13+". Пришлось идти на костыли:
До тех пока не появится жанр содержащий в себе плюсы или цифры я в безопасности. С режиссером полегче, это первая ссылка внутри блока с классом txt-block. Можно сказать мы закончили, осталось заполнить наш массив с фильмами:
Film это моя структура содержащая поля для названия, режиссера, жанра и года выпуска фильма. Ее я и записывал в .json и позже читал из него, но это не тема данной статьи так что говорить об этом не буду.
Заключение
Вот и все что нужно знать для парсинга сайтов. Dead simple and straightforward. Если знать технологию то можно очень быстро выполнять задания на парс данных и бежать на фриланс)) Хотел бы еще обратить внимание на то что я решил парсить список скорых релизов, там у всех фильмов год релиза 2021 и может будет один с 2020 годом, не очень подходящие данные для сортировки, но препод принял) И конечно логичный вопрос "Оно того стоило?". Однозначно нет, сделать скрипт который писал бы рандомные строки в файл заняло бы минут 10 и для сортировки они подходили бы больше, но я сделал это по фану так что не считается. На этом у меня все, всем удачи и спасибо за внимание ><
Для тех кто хочет покопаться в проекте вот ссылка проекта на гитхабе. Поддерживайте со мной связь на github, вк или пишете на почту languidbasil@gmail.com