Глава 9
Веб-скрейпинг
Дарья Моисеева
Автор
Интернет — это содержание веб-страниц, метрики (количество просмотров и посещений), а также метаданные размещённых материалов. Но цифровое золото для эффективного использования нужно извлечь и обработать: в этой главе мы постараемся дать читателю представление, что такое парсинг (или скрейпинг) и с какими трудностями может столкнуться начинающий аналитик.
1. Основы html
HTML — язык гипертекстовой разметки документов для просмотра страниц в браузере. В определении возникает термин «гипертекст», который можно очень просто объяснить — это электронный вид текста. В качестве основополагающих особенностей можно отметить наличие гиперссылок — объектов, позволяющих переходить от документа к документу по одному щелчку мыши.
CSS — язык стилей веб-страницы. CSS с помощью особых идентификаторов позволяет применять форматирование и стили к элементам, создавая понятные и приятные глазу современные сайты.
HTML-элементы — основа языка, служат для создания элементов документа. Элементы обозначаются специальными пометками — тегами, которые имеют общую структуру — название, заключённое в <>. Если указан тег <div>, значит, перед нами контейнер и последующие теги будут принадлежать данному элементу. Для обозначения окончания используют закрывающий тег, отличающийся от открывающего наличием наклонной черты после <. То есть завершение контейнера будет обозначено как </div>.
Теги могут содержать аргументы — дополнительные параметры, которые меняют форматирование, функционал или содержание. Например, тег <a>, создающий гиперссылку, содержит аргумент href — ссылку на ресурс, который откроется после нажатия. На иллюстрации ниже показан пример использования <a> и реализация аргументов тегов.
Структура html иерархична — элементы принадлежат друг другу, то есть функционируют по принципу матрёшки, поэтому их можно разделить на материнские и дочерние, причём элемент может быть одновременно материнским и дочерним, то есть принадлежать кому-то и содержать какие-то теги внутри себя. В коде иерархия отображается с помощью отступа.
Язык html не ограничен простыми текстовыми объектами, современная версия языка разнообразна и способна создавать списки, таблицы, изображения, видео. Для извлечения информации не нужно знать все теги, достаточно овладеть основополагающими.
<div>
Тег, позволяющий создавать контейнеры. В чистом варианте без примененных стилей <div> никак не влияет на содержание страницы. После использования форматирования, задающего размер, отступ, положение и цвет, тег позволяет создавать сложные структуры веб-страниц, без которых сложно представить современный сайт: ленту новостей, навигационное меню, галерею и т. д.
На рисунке ниже показано, как с помощью <div> на странице https://www.mid.ru/ реализован архив выступлений Министра иностранных дел С. В. Лаврова.
<p>
Структурные теги позволяют нам обращаться к нужным узлам и извлекать из них информацию, но следует познакомиться и с информационными тегами. Обычный текст чаще всего помещают в <p> и <span>. Тег <p> определяет текстовый. Иногда текст дополнительно помещают в <span> для добавления форматирования.
Существуют стилистические элементы, меняющие облик текста. К таким тегам относят: <b> (bold, то есть жирный), <i> (italics, то есть курсив) и <u> (underline, то есть подчеркнутый).
Теперь освоим теги заголовков — они позволяют создавать многоуровневую систему заглавий. Группа этих тегов начинается с «h», после чего следует порядковый номер, определяющий иерархию титулов. Ниже на иллюстрации можно увидеть примеры текстовых тегов с исходным кодом.
<ul>, <ol>
Важными элементами веб-страницы являются списки. В html существует несколько видов: маркированные, нумерованные и списки определений, и каждому соответствует свой тег.
Маркированный список состоит из вложенных элементов, каждый отмечен графическим символом (чаще всего точка). Маркированному списку соответствует тег <ul>, а каждому его элементу тег <li>. В html списки служат не только для структурирования информации, но и для создания структур веб-страниц. С помощью списков удобно конструировать меню, информационные панели, наборы элементов.
Нумерованный список работает идентичным образом, нужно лишь изменить тег с <ul> на <ol>.
Список определений создается с помощью тега <dl>, а элементы обозначаются двумя видами тегов — <dt> и <dd>, которые используют для терминов и определений соответственно.
<table>, <tr>, <td>
Рассмотрим табличные теги — одной из самых востребованных задач при парсинге является извлечение форм и табличных данных. Для создания таблицы с помощью html используют набор тегов: <table>, <tr>, <td>.
<table> указывает на табличный объект, тогда как <tr> и <td> создают основную структуру — ряды и ячейки. Тег <td> создает ячейки, а <tr> указывает на границы ряда и позволяет регулировать их количество. Ниже продемонстрировано, как можно создать свою таблицу и показан пример с реального веб-сайта.
Система классов и id
Для стилизации объектов в HTML используется система классов и id, что продиктовано простой потребностью в кастомизации элементов. Не очень удобно прописывать стиль для каждого элемента, гораздо практичнее присвоить тегам идентификатор, который автоматически задаст стиль элементу. Классы и id, как и аргументы, идут после названия элемента.
Элементу может быть присвоено несколько классов, а также комбинация классов и id. Тег не может иметь несколько id в силу особенностей данного аргумента (id позволяет переписывать стили и особенности, присвоенные классом). Класс представляет название, id помимо текста содержит символ решетки. Классы и id представлены ниже.

2. Запросы к html-элементам. Особенности синтаксиса XPath
Мы освоили базовый html — познакомились с основными элементами, узнали, как работают классы и id. Читатель может задаться вопросом: зачем все это было нужно? Ответ достаточно прозаичен и прост: чтобы ориентироваться в html-коде страниц и уметь писать запросы к нужным элементам.
Запросы являются важной частью аналитики данных, ведь они позволяют получать нужную информацию из различных источников. Веб-браузеры, которыми пользуется каждый пользователь сети Интернет, для получения веб-страницы, отправляют запрос на сервер, а в качестве ответа получают код страницы. Конечно же, браузер устроен сложнее, но нам важнее понять саму природу запроса и его важность.
Для структурирования данных html-страницы требуется отправить запрос, конкретизирующий нужную нам информацию. Распространенным методом является XPath — язык запросов к элементам XML-документа. Он позволяет точно указать путь к элементам, атрибутам и текстовым данным, которые нужно найти в структуре XML-файлов и получить данные при необходимости. Таким образом, можно получать не весь код страницы, а только нужную нам часть.
XPath — это путь до элемента. Он отображает положение нужного нам элемента в древе html. Например, /html/body/div[1]/div[2]/div[2] /div/div[1]/div[2]/div[1]/ul/li[1]/a — это абсолютный XPath, являющийся адресом конкретного элемента. Для упрощения попробуем проиллюстрировать на примере.
Представим гипотетического Васю, проживающего в прекрасном городе Москва на улице Международной в доме номер 2127. Нам недостаточно знать, что он Вася — в таком крупном мегаполисе количество Вась на один квадратный километр может достигать десятков или даже сотен, а нас интересует конкретный человек.
Для уточнения запроса мы вводим его адрес, начиная с крупных пространственных объектов: Россия / Москва / улица Международная / дом 2127 / Вася. Сочетание подобной информации с сохранением иерархии объектов позволяет выбрать нужного Васю и не промахнуться.
Подобная форма указания нахождения элемента во многом и есть XPath. Но можно также выбирать множество тегов, тогда путь будет выглядеть несколько иначе: //a[@class='announce__link']. Относительный XPath позволяет выбирать все элементы, отвечающие условию — в нашем случае будут выбраны все гиперссылки <a> с классом 'announce__link'.
Если у гиперссылки другой класс, она не появится в результатах нашего запроса. Обратите внимание: значение всегда указывается в одинарных кавычках, а атрибут — в квадратных скобках. Ниже проиллюстрирована схема синтаксиса XPath.
/ — поиск с начала веб-страницы. Нужен для указания абсолютного пути элемента (то есть точного адреса);
// — оператор, выполняющий поиск по всей странице. Позволяет вычленить группу нужных элементов;
[@arg='value'] — условие, выбирает элементы с нужными значениями атрибута.
XPath позволяет использовать различные логистические операторы, которые позволяют комбинировать условия выбора элементов. Например, нам нужно получить все теги div, обладающие классами «container» и «main».
Следует использовать логический оператор «И», то есть AND — запрос примет такой облик: //div[@class='container' AND @class='main']. XPath работает со следующим списком логических операторов:
И это не все возможности XPath — можно использовать функции, которые позволяют искать значения аргумента по частичным совпадениям и ключевым словам, что весьма удобно для некоторых сайтов. Основные функции:
Дополнительные возможности XPath включают выбор элементов по их иерархии, что именуется осями. Для иллюстрации возможностей осей попробуем найти Васю, проживающему в Москве на улице Международной в доме номер 2127. Допустим, нам не нужен сам Вася, но нужен его адрес.
Мы можем сформулировать запрос следующим образом: извлеки все родительские узлы Васи. Тогда из всего пути к Васе, то есть Россия / Москва / улица Международная / дом 2127 / Вася мы получим последовательность Россия, Москва, улица Международная, дом 2127 — все элементы, которым Вася принадлежит. Если нас интересует только дом, то тогда можно запросить последний родительский контейнер, и результатом будет дом 2127. Таким образом, общий синтаксис осей можно представить, как на рисунке ниже.
Перейдем к более подробному рассмотрению навигации по узлам — каждому методу будет представлена соответствующая визуализация. Обратите внимание: красным цветом отмечен текущий узел, зеленым — выбранные в результате использования оси XPath. Для упрощения задачи все контейнеры считаем тегом <div>, а подписи — значениями id.
XPath — весьма сложная конструкция, но очень полезная, она позволяет эффективно выбирать нужное множество элементов html-кода для дальнейших манипуляций. Но во многих ситуациях XPath является избыточным, на современных сайтах гораздо проще использовать CSS-селектор.

3. Особенности CSS-селектора
CSS-селекторы выбирают нужное множество объектов по их иерархии или параметрам. СSS-селекторы достаточно универсальны, поддерживают возможность комбинирования условий, использования классов и id, но обладают иным синтаксисом чем XPath.
Как и XPath, CSS-селекторы поддерживают выбор элементов по их иерархии, положению в древе узлов. Существуют отдельные фильтры, позволяющие задавать условия выборов только для элементов с тем же тегом. На иллюстрациях ниже будут визуализированы результаты применения тех или иных CSS-селекторов, красным цветом будет отмечена точка отсчета, зеленым — все выбранные элементы. Рядом с фигурой, обозначающей элемент, будет указано, каким тегом она является.

4. Обращение к элементам с использованием возможностей браузера и сторонних плагинов
Теперь мы уверенно ориентируемся в синтаксисе XPath и CSS-селекторов, но у читателей, скорее всего, возникает резонный вопрос: неужели нужно прописывать запросы вручную? Нет, можно получить почти готовые XPath или CSS-селекторы, используя встроенные возможности браузера или дополнительные программы.
Этап 1
Откройте нужную вам страницу. Визуально рассмотрите сайт, найдите нужные вам элементы. Наведите курсор мыши на нужный вам элемент и нажмите правую кнопку. В открывшемся меню выберите пункт «Исходный код». Откроется дополнительное меню, в котором будет код страницы. Обратите внимание, что цветом будет выделен код нужного вам элемента.
Этап 2
Достаточно ли нам узнать код элемента? Не совсем, ведь структура html иерархична, как мы уже выяснили в обзорной части. На практике это означает, что для нахождения конкретного элемента нужно указать путь до него, то есть описать, каким тегам принадлежит интересующая нас информация.
При нажатии правой кнопкой мыши на код элемента появляется меню с нужной нам вкладкой «копировать». При нажатии раскроется дополнительное меню с опциями, но нас интересует два окошка: «копировать XPath» и «копировать селектор». Мы успешно скопировали адрес элемента и в дальнейшем сможем использовать его для парсинга.
Данный процесс проиллюстрирован выше на примере веб-страницы информационных бюллетеней МИД России. Для демонстрации был скопирован путь до бюллетеней от 24−30 октября 2022 года, полученный XPath в следующем виде: /html/body/div[1]/div[2]/div[2]/div/div[1]/div[2]/div[1]/ul/li[1]/a.
Очень похоже на пример с Васей, не правда ли? Таким же образом возможно использовать CSS-селектор: body > div.container.container-ru > div.main.inner > div. page > div > div. page-content.page-content9 > div. page-body > div. news-articles > ul > li: nth-child (1) > a. И XPath, и CSS-селектор представляют собой абсолютный путь, то есть указывают на конкретный элемент. Чтобы выбрать все подобные элементы, следует генерализировать запросы.
Получать XPath и CSS-селекторы можно с помощью различных утилит и плагинов. SelectorGadget, доступный как расширение для ряда браузеров, относится к таким программам. При клике на элемент появляется минимальный селектор, позволяющий выбрать нужный узел. Желтым будут выделены все элементы, которым подходит этот селектор. При повторных кликах можно исключить элемент из запроса (будет подсвечен красным) или включить его (будет выделен зелёным). Удерживаемая клавиша Shift в сочетании с кликом способствует выбору элементов внутри других узлов.

5. Парсинг: нединамические страницы
Наконец-то настал момент истины: извлечение данных с веб-страниц. Для этого будет использован язык программирования R, работать будем в IDE RStudio, поэтому необходимо установить язык и среду разработки на свой компьютер. Для работы рекомендуется создать проект и открыть новый скрипт.
Ознакомимся с базовым синтаксисом. Нужно загрузить библиотеку «rvest», которая позволит читать html-код и с помощью селекторов отбирать данные. Если вы первый раз открыли RStudio, необходимо установить библиотеки — это можно сделать в меню «Packages», нажав кнопку «Install» и набрав название нужной библиотеки в открывшемся диалоговом окне, или же с помощью команды install_packages (название_библиотеки).
Далее при каждом открытии RStudio или создании нового проекта необходимо загрузить библиотеку в сессию, что реализуется командой library (название_библиотеки).
Операция присвоения является базовой — позволяет создавать переменные с указанными значениями. Переменная способна сохранять результаты операций, без чего написание программ в принципе невозможно. Присвоение в R выполняется с помощью символа < – (сочетание клавиш Alt и –). Мы используем присвоение для сохранения ссылки на сайт Министерства иностранных дел России.
Следующий шаг — получение исходного кода страницы. Обычно для этого используют команду read_html (), но многие современные сайты, использующие защиту от роботов, не возвращают в ответе нужную информацию. Вместо этого лучше использовать функционал библиотеки polite и функцию bow. Данный метод позволяет отправить идентификатор пользователя (User-agent), что существенно снижает количество заблокированных запросов со стороны веб-ресурсов.
Для чтения результатов запроса применим функцию из той же библиотеки scrape (), которая наконец-то выдаст желаемый html-код, и мы сможем перейти к структурированию данных.
Использовать будем функции библиотеки rvest, а точнее html_elements (), html_text () и html_attr (). Мы не зря изучали XPath и CSS-селекторы, ведь именно на данном этапе, используя любой из освоенных методов выбора узлов, мы с помощью html_elements () сможем их извлечь из общего массива кода.
Обратите внимание на аргументы функции: сначала следует указать исходный код страницы, а затем селектор или XPath. В нашем случае используется комбинация имени тега и его класс, то есть a. announce_link. Функция выберет нужные нам элементы, и останутся только теги, в которых содержатся ссылки и названия пресс-релизов Госдепартамента США.
Остался финальный этап — извлечение текста и ссылок. Для этого воспользуемся функциями html_text () для сохранения текста тега и html_attr (), отбирающего значение указанного атрибута. Аналогичную процедуру выполняем с датами, только меняем класс в html_elements () на .announce_date. Ссылки из дат извлекать не нужно, их нет там вовсе. В результате получаем все новостные заголовки с датами и ссылками.
Тем не менее, работать с таким массивом данных не очень удобно, поэтому объединим их в таблицу, что можно сделать с помощью data. frame (). Это встроенная функция, не требующая загрузки дополнительных библиотек. Помещаем массивы в качестве аргументов и задаем им новые имена.
Таблицу нужно сохранить на компьютер для возможности обработки в сторонних программах и сохранения результата трудов. Воспользуемся функциями write_csv () и write_xlsx () — они позволят сохранить данные в форматах .csv и .xlsx соответственно. Работа с библиотеками и итоговая таблица представлены ниже.
Остался последний нерешенный вопрос нашего алгоритма — как извлечь сразу несколько новостных страниц? На многих сайтах существует лимит на количество отображаемых элементов, а последующие элементы распределены по новостной галерее — многостраничной конструкции с переключателем между страницами. Откройте сайт, с которым мы работаем, и нажмите внизу на интерактивную кнопку «Вперед».
Обратите внимание, как изменилась ссылка, она приобрела вид «https://www.mid.ru/ru/foreign_policy/news/?PAGEN_1=2». Если нажать повторно, последняя цифра изменится на «3» и так далее. Основание ссылки остается неизменным, меняется лишь идентификатор новостной страницы. Мы можем воспользоваться этим в наших скрейперских целях: напишем код, который циклично выполняет одинаковую процедуру парсинга, но при каждом выполнении меняет последний элемент ссылки.
В этом нам поможет универсальная для всех языков программирования конструкция — цикл. Цикл служит для многократного выполнения инструкций, а особый подвид — цикл for — позволяет вносить изменения в инструкции с помощью итерируемой переменной.
Попробуем проиллюстрировать данную концепцию на всем знакомом Васе, которому нужно переложить в холодильник молоко, сок и газировку. Вася будет выполнять одно и тоже действие — брать напиток, открывать холодильник, ставить туда упаковки — и так три раза. В этом наборе инструкций отличаться будут только напитки, поэтому цикл будет выглядеть примерно так:
#transportation program
library(fridge)
for i in c(«молоко», «сок», «газировка») {
​возьми i
​открой холодильник
​положи i в холодильник
​закрой холодильник
}
В примере был использован синтаксис языка R для цикла for, мы наглядно видим инструкции для Васи, но он не берет конкретный напиток, а берет загадочное i. Но посмотрите на счетчик цикла — вот там и находится наше i, которое принимает значение элементов вектора, от молока и до газировки.
Аналогично мы поступим и в процедуре парсинга сайта МИДа, только итерации будут менять конечное число ссылки «https://www.mid.ru/ru/foreign_policy/news/?PAGEN1=». Счетчик цикла зададим от 1 и до 10 — таким образом сможем извлечь данные сразу десяти страниц.
Далее нужно создать новую переменную с модифицированной ссылкой — для этого воспользуемся функцией paste (), позволяющей объединять текстовые значения. Обратите внимание на аргумент sep — это разделитель объединяемых значений, его мы оставляем пустым. Если задать в качестве сепаратора пробел, то запрос не получится и в консоли появится ошибка — ссылки «https://www.mid.ru/ru/foreign_policy/news/?PAGEN_1= 2» просто не существует, страница не откроется.
Остальные процедуры, кроме создания таблицы, оставляем без изменений. Дело в том, что если сохранять данные, как на иллюстрации создания таблицы из векторов выше, то каждый раз в ходе итерации будет создаваться таблица, а значение переменной будет обновляться. Нам же нужно объединить собранные данные со всех страниц.
Для этого до цикла создадим пустую таблицу — это легко выполняется функцией data. frame (). Затем в цикле объединим наши данные и приобщим их к пустой таблице с помощью функции rbind (), которая присоединяет таблицы друг к другу по рядам — одну над другой. Далее мы сохраним результаты работы с помощью уже изученных функций записи в файл.

6. Парсинг: динамические страницы
Динамический контент представляет особую сложность для извлечения. Фактически такие страницы не обладают статичным html-кодом: используя java-script или другие языки, они способны менять контент в процессе загрузки страницы, поэтому описанный в предыдущей части метод не будет работать.
Конечно, библиотека rvest обладает функцией session (не путать с session в библиотеке polite), но ее возможности сильно ограничены. Поэтому для извлечения данных динамических страниц чаще всего используют особый инструмент Selenium.
Selenium — это инструмент автоматизаций действий в браузере. С помощью языка программирования можно отправлять драйверу команды, которые должен совершить браузер. Например, нажать на ссылку, перейти на другую страницу, дождаться загрузки и т. д. Именно Selenium позволяет обрабатывать динамический контент веб-страниц. Для работы с данным инструментом через R потребуется дополнительное программное обеспечение — Java Development Kit.
Продолжим настройку нашего инструмента. Selenium для функционирования требует использование вебдрайвера — при установке библиотеки они устанавливаются автоматически, однако не самые последние версии. Поэтому дальнейшая настройка вариативна: можно загрузить более новую версию драйвера, или же использовать старую. Начнем с загрузки новой версии драйвера. Для этого необходимо перейти на сайт Google Chrome в раздел разработчиков, скачать нужную версию драйвера и распаковать ее в папку «C:\Users\Имя_пользователя\AppData\Local\binman\binman_chromedriver\win32».
Данные папки являются системными, поэтому для возможности редактирования следует включить отображение скрытых папок (в проводнике нажмите кнопку «Вид» и поставьте галочку возле пункта «Скрытые элементы»). В распакованной папке (я использую версию драйвера 114.0.5735.90) удалите файл с названием «LICENSE.chromedriver». Теперь мы готовы к парсингу. Если вы не хотите использовать новую версию драйвера, все равно нужно открыть папку с драйверами и удалить файл «LICENSE.chromedriver».
Скрейпинг с помощью Selemium начинается с создания сессии браузера. Для этого укажем, какой браузер хотим использовать, и выберем соответствующую нашим настройкам версию вебдрайвера. В результате в переменной driver сохраняются наши настройки, и можно запускать браузер командой driver[["client"]]. Не забудьте сохранить данную функцию в переменную, так как это позволит менять параметры сессии и ставить Selenium различные задачи.
Выполним первую такую задачу, перейдем в браузере по ссылке. Для этого выберем метод сессии navigate () и укажем ссылку на сайт Госдепартамента США. В открывшемся окне браузера появится желанный сайт (смотрите пример ниже). В рамках сессии можно не только переходить по ссылкам, но и выполнять другие действия. Вот основной список возможностей:
К полученным последним методом элементам можно применять различные действия: наводить курсор, кликать и т. д. Для этого элемент нужно сохранить в переменную и применить к нему нужную функцию. Так как мы нашли сразу много элементов, а кликнуть на все сразу не получится, нужно выбрать порядковый номер ссылки для нажатия. Сделать это можно с помощью двойных квадратных скобок с порядковым номером элемента elems[[1]].clickElement ().
Важным методом является elem$sendKeysToElement (list («как-то текст», «\uE007»)) — он позволяет отправить текст в диалоговое окно и эмулировать нажатие клавиши Enter. «\uE007» — символ Enter (можно заменить содержание параметра на «enter»). Отметим также возможность перемещать курсор к элементу: elem$mouseMoveToLocation (). Дело в том, что нельзя кликнуть на элемент, если он не поместился в окне браузера, поэтому предварительно нужно навести на него мышь.
После получения исходного кода страницы мы можем вернуться к использованию потенциала библиотеки rvest — найти нужные нам элементы, сохранить их в переменные и создать таблицу.

7. Заключение
В этой главе вы познакомились с основами веб-скрепинга — важным инструментом для сбора данных из открытых источников. Мы рассмотрели, как устроены веб-страницы, какие технологии лежат в их основе (HTML, CSS), а также как можно извлекать данные с помощью XPath и CSS-селекторов. Благодаря этим знаниям вы научились ориентироваться в структуре HTML-документа и находить нужные элементы на странице.
Вы узнали, что веб-скрепинг может быть как простым, так и сложным процессом: от парсинга статических страниц до обработки динамического контента, загружаемого с помощью JavaScript. Для этого мы использовали современные библиотеки программирования на языке R — rvest для работы с статичными и RSelenium для взаимодействия с динамическими страницами. Эти навыки открывают перед вами огромные возможности: от мониторинга и анализа новостей до создания собственных баз данных для исследований и проектов.