📊 Инструменты дата-журналиста #2: веб-скрапинг, парсинг и визуализация данных
Узнаем, как сгенерировать из датафрейма страницу HTML, напишем парсер и научимся визуализировать данные с помощью шести библиотек: pandas, matplotlib, plotly, seaborn, bokeh и altair.
Большинство туториалов по парсингу становятся неактуальными через несколько месяцев, так как код сайта меняется и парсер нужно переписывать. Специально для этой публикации на основе датасета Google trends сгенерирована HTML-страница со всеми данными из датасета. Мы научимся создавать HTML-страницу, напишем парсер, соберем всю информацию со странички в датафрейм и проверим, совпадает ли он с оригинальным датафреймом. После этого займемся визуализацией данных с помощью библиотек pandas, matplotlib, plotly, seaborn, bokeh и altair. Ссылку на блокнот Jupyter вы найдете в конце статьи.
Для генерации HTML-страницы воспользуемся библиотекой yattag. Установим библиотеки pandas и yattag:
1.1. Библиотека yattag
Разберемся как библиотека yattag генерирует HTML-код. Если инструкции with записываются последовательно, то теги вкладываются друг в друга:
Если инструкции with записываются параллельно/на одном уровне, то теги следуют друг за другом:
Здесь:
tag()– создает тег. text()– создает текстовую запись. h1иh2– теги заголовков первого и второго уровня соответственно. doc.getvalue()– выдает результат.
1.2. Структура HTML-страницы
Откроем датафрейм, чтобы узнать названия столбцов, которые станут полями нашей странички:
Нам нужно создать пять полей: location, year, category, rank и query. Стили возьмем из CSS-фреймворка Bootstrap:
Здесь:
<!DOCTYPE html>– указание типа текущего документа, то есть HTML. <html>– контейнер для всего содержимого веб-страницы. <head>– контейнер для заголовка и технической информации. В него мы запишем ссылку на Bootstrap. <link href= />– ссылка на CSS-файл. <body>– содержит контент веб-страницы. В нашем случае – значения из датафрейма. <h2>– заголовок второго уровня со значением поляrankиlocation. <li>– два элемента списка: значения столбцовqueryиcategory. <span>– строковый контейнер со значением из столбцаyear.
doc.asis()– методasis()не экранирует символы, без него мы получим<<!DOCTYPE html> />. tag(html)– создает тег<html></html>. doc.stag("link href=...")– создает самозакрывающийся тег<link href= ... />. line('h2')– создает тег<h2></h2>после инструкцииwith tag():. klass = ''– класс CSS. Классы берутся из Bootstrap. for i in range(len(df.index))– цикл, создающий HTML-блоки с содержимым из датафрейма. rank_and_location_line,query_line,category_line– создают строчки со значениями из столбцов датафреймаrank,location,query,category. df['rank'][i].astype(str)– тип переменной меняется с числового на строчный для конкатенации с другими строчками. with open("googletrends.html", "w", encoding='utf-8')– создает файлgoogletrends.htmlи записывает в него в режимеwHTML-код. Кодировкаutf-8, чтобы избежать ошибкиUnicodeEncodeError.
Теперь напишем парсер для этой странички.
2. Пишем парсер
2.1. Библиотека BeautifulSoup
Для получения HTML-кода воспользуемся библиотекой requests, а для парсинга разметки HTML библиотекой beautifulsoup4.
Установим библиотеки:
Напишем парсер:
Здесь:
r = requests.get()– отправляет GET-запрос сгенерированной в предыдущей главе веб-странице и извлекает из нее код. soup = BeautifulSoup(r.text, 'html.parser')– создает объектBeautifulSoupиз полученных данных. С помощью этого объекта можно найти разные элементы страницы. Например, содержимое тега<head>черезsoup.headи так далее. soup.find_all('span', {'class': 'rounded-pill'}) – методfind_allищет содержимое в указанном теге с заданным классом и возвращает все совпадения в виде списка. В нашем случае – содержимое в тегеspan, который имеет классrounded-pill.
locations,years,categories,ranks,queries– списки, в которые добавляются соответствующее содержимое тегов.
Значение rank и location находятся на одной строчке. Мы точно знаем, что rank – всегда второй символ, а location – все символы, начиная с седьмого. Воспользуемся индексами и срезами, чтобы получить нужные значения из строчки: [1] для rank и [6:] для location. Значения query и category так же получаем через срезы, а year берем как есть.
Cоздадим пустой датафрейм со столбцами locations, years, categories, ranks, queries:
и заполним столбцы значениями из соответствующих списков:
Узнаем количество строк и столбцов:
и типы данных в столбцах:
Значения в столбцах year и rank имеют тип object, а должны иметь тип целое число int. Изменим их тип на int64, как в оригинальном датасете:
Сравним полученный нами датасет с оригинальным методом equals():
Мы получили True – датасеты совпадают. Переходим к визуализации данных.
В библиотеке pandas есть встроенные методы визуализации данных. Они не такие продвинутые как в matplotlib и других библиотеках, но тоже могут пригодиться.
Выделим цветом максимальные значения в столбце rank:
и минимальные значения:
Здесь:
highlight_max,highlight_min– функции, принимающие значение из столбцаrank. Если значение максимальное, то ячейка выделяется желтым цветом. В противном случае цвет не меняется. df.style.apply()– изменяет визуальное оформление значений в столбце/строке/всей таблице.
Библиотекеmatplotlibбольше десяти лет и она до сих пор развивается. Построим с ее помощью столбиковую диаграмму, на которой отобразим ТОП-10 песен, продержавшихся в чарте The Billboard Hot 100 максимальное количество недель. Оригинал датасета доступенздесь.
Установимmatplotlib:
Создадим датафрейм df_wob и запишем в него ТОП-10 песен с максимальным значением weeks-on-board (количество недель в чарте):
Здесь:
nlargest(80, ['weeks-on-board'])– возвращает 80 максимальных значений в столбцеweeks-on-board.
drop_duplicates(subset=['song'])– убирает все дубликаты в столбцеsong.
Здесь:
list(df_wob['song'])– создает список из элементов столбцаsong.
list(df_wob['weeks-on-board'])– создает список из элементов столбцаweeks-on-board.
fig– создает контейнер для визуальных объектов. ax– объявляет визуальные объекты. xlabel,ylabel,title– метки на осяхx,yи название диаграммы соответственно. barh(song, weeks_on_board)– создает диаграмму со значениями из столбцовsongиweeks_on_board.
На основе датасета «Отчет по уровню счастья в мире» (оригинал) построим график зависимости уровня счастья в разных странах от качества социальной поддержки.
Здесь:
px– модульplotly.expressявляется частью библиотекиplotly. Он ускоряет создание графиков за счет своего лаконичного синтаксиса. scatter()– инициирует создание пузырьковой диаграммы. x='',y=''– значения по осиx, иyсоответственно. color– добавляет градацию по цвету в зависимости от значенияFreedom to make life choices. hover_name– добавляет всплывающее окно при наведении на метку. size– задает размер круга в зависимости от значенияSocial support.
Размер круга определяется величиной социальной поддержки. Цветом выделен уровень свободы в принятии решений: желтый цвет – максимум свободы. В лидерах Финляндия, Дания, Норвегия и Исландия.
Seaborn– обертка над библиотекойmatplotlib. Она представляет высокоуровневый интерфейс для рисования красивых и информативных графиков.
Установим библиотекуseaborn:
Построим тепловую карту цен на газ в зависимости от года и месяца. На тепловой карте значения выделяются цветом, но мы так же подключим численное отображение:
Посмотрим какие у нас типы в столбцах:
Столбец Month имеет тип object. Чтобы получить значения года и месяца преобразуем его тип в datetime:
Создадим из столбца Month отдельные столбцы с годом Год и месяцем Месяц:
Нарисуем тепловую карту:
Здесь:
set_theme()– устанавливает стиль диаграмм.
df.pivot()– создает сводную таблицу.
figsize=(x, y)– задает размеры ячейки:x– ширина,y– высота.
heatmap()– создает тепловую карту.
annot=True– добавляет численные значения к каждой ячейке.
Библиотека Bokeh помогает создавать графику, начиная с простых графиков и заканчивая сложными инструментальными панелями с большими наборами данных.
Установим библиотеку Bokeh:
Построим диаграммы по гендерному распределению главных ролей в фильмах и сериалах Netflix за последние пять лет. За основу возьмем датасет «Оригинальный датасет Нетфликса».
Нарисуем график:
Здесь:
sex– пол протагониста: мужской, женский, вместе (главные герои мужчина и женщина). data– словарь со значением пола, и количеством протагонистов у каждого пола с 2015 по 2019 годы . df.loc[(df['Year Released'] == 2017) & (df['Main Actor'] == 'Male')]['Main Actor'].count()– фильтрация датасета по 2017 году и мужскому полу;count()– подсчет сколько раз мужчины были протагонистами. x_range– определяет значение метки деления. toolbar_location=None– скрывает панель инструментов. tools="hover"– включает подсказку при наведении на диаграмму. tooltips=""– данные подсказки. vbar_stack()– генерирует снизу наверх несколько уровней/контейнеров в диаграмме. y_range.start– определяет с какого уровня по оси y строить диаграмму. x_range.range_padding– задает начальное смещение диаграмм вправо по осиx. legend.locationсиlegend.orientation– определяют место и ориентацию описания диаграмм соответственно.
Altair– это библиотека визуализации для Python, основанная на инструментах визуализацииVegaиVega-Lite.
УстановимAltair:
Построим графики изменения цен криптовалют за последние семь лет. Импортируем датасеты (оригиналы):
Объединим датафреймы:
Здесь:
concat([df1, df2, ... ], axis=0)– методconcat()добавляет к датафреймуdf1строчки из других датафреймов.
Ошибка MaxRowsError
MaxRowsError: The number of rows in your dataset is greater than the maximum allowed (5000). For information on how to plot larger datasets in Altair, see the documentation.У нас получилось 33969 строк. По умолчанию altair визуализирует датасеты максимум с 5000 строк. Чтобы обойти это ограничение добавим следующую запись в код: alt.data_transformers.disable_max_rows().
Нарисуем графики:
Здесь:
Chart(df1).mark_line()– создает график из линий на основе датасетаdf1. Если поставитьmark_circle(), то график строится из кружков. Больше вариантов вдокументации.
x='Date'иy='Marketcap'– значения на осяхxиyиз столбоцовDateиMarkercapсоответственно. interactive()– добавляет возможность передвигать графики курсором мыши и изменять масштаб колесиком мыши.
Блокнот Jupyter
Блокнот лежит на Гитхабе. Первая часть цикла доступна по ссылке.
***
Мы научились:
генерировать из датафрейма HTML-страницу;
парсить данные с веб-страницы;
визуализировать данные с помощью библиотек pandas, matplotlib, plotly, seaborn, bokeh и altair.
Надеемся, полученные знания помогут вам в проведении журналистских исследований.