12 posts tagged

Сделал

Анализ блога Ильи Бирмана. Часть 3: визуализация.

В двух предыдущих заметках я рассказал, как собирал данные и приводил базовый анализ на самые-самые заметки:

  1. Сбор данных.
  2. Анализ данных.
  3. Визуализация.

Доступные данные

Скрипт собрал данные о 4565 заметках. По каждой есть: заголовок, дата, длина, количество картинок, просмотры, комментарии, тэги и ссылка на саму заметку.

Первые заметки в блоге датированы 2002 годом, последняя заметка — от 26 сентября 2019. Активность в 2002-2004 годах отличается от последующих: два месяца в 2002 (заметки импортированы в 2005), ещё восемь супер-активных месяцев в 2003, чуть менее бурная активность в 2004. С 2005 года и дальше более-менее равномерно.

Общий вид

Первое, что заметил — это стабильность Ильи: за всё время не было ни периодов тишины, ни каких-то взрывов активности.

Напоминает концепцию «20-мильного марша» из книги «Великие по собственному выбору». Авторы книги нашли общую особенность у великих людей и компаний: они выбирали темп и всегда его придерживались. В неблагоприятных условиях это закаляло дисциплину, а в хороших — удерживало от неконтролируемого роста.

Эту регулярность можно увидеть на тепловой карте ниже. Слева количество заметок в каждый месяц из 17 лет, а справа — средняя длина заметки.

Чтобы видеть выбросы, за «среднее» брал именно арифметическое среднее, а не медиану.

в 2003 году Илья писал в блог по 2-3 заметки в день или 365 заметок за 7 месяцев

Аналитика уровня «пальцем в небо»: заметны относительно спокойные периоды и хочется найти в закономерность. Илья часто путешествует, а в путешествиях обычно столько всего интересного, что времени на блог остаётся меньше. Предположу, что «спокойные периоды» блога связаны именно с путешествиями: февраль..апрель и август..сентябрь в 2019, август в 2017, декабрь 2016..январь 2017.

Заметна тенденция: чем больше постов за месяц, тем больше их средняя длина. То есть Илья обычно пишет либо коротко и немного, либо и много, и длинно. Эту корреляцию можно увидеть на диаграмме рассеяния.

Динамику по годам количества заметок и их длины видно на диаграмме «ящик с усами»:

в 2019 в среднем заметки стали короче, но стало больше очень длинных заметок — «выбросов»

Интересные детали

Просмотры начали считаться с января 2018. Видимо, в это время движок блога научился их считать и показывать. На более ранних заметках счётчик просмотров может отсутствовать или показывать единицы просмотров.

На графике видны «выбросы» — месяцы с аномально высокими просмотрами. Заметки с самыми большими просмотрами я приводил в предыдущей заметке об анализе блога.
Вот они:

год просмотры тэги
О запятой после «С уважением» 2006 87974 русский язык
Переплата по кредиту 2013 39296 жизнь, общество, экономика
Числа π и e 2012 14387 математика
Война 2015 13601 красная таблетка, общество
Почему люди платят налоги 2014 9310 красная таблетка, общество, философия, экономика

Комментарии. До 2012 заметки в блоге набирали большое количество комментариев, а с января 2012 они почти исчезли. Потом появлялись только для отдельных заметок.

общее количество комментариев за месяц

Заметки после 2012 года с наибольшим количеством комментариев:

В предыдущих сериях

  1. Cбор данных: заметка и код на ГитХабе
  2. Анализ данных: заметка и код на ГитХабе
 47   1 mon   Python   Накодил   Сделал

Индекс джедая

У агентства IT-Agency есть план обучения сотрудников — он открыт и опубликован на сайте.

Материалы в плане обучения разбиты по категориям:

I. Система организации работы V. Построение отчётности IX. Управление людьми
II. Тексты VI. Дизайн X. Управление проектами
III. Яндекс.Директ и Google Adwords VII. Разработка XI. Работа с клиентами
IV. Аналитика VIII. SEO XII. Английский

Плюс бонус — XIII. На что подписаться — позволяет сходу получить 27 баллов к индексу.

Всего 290 материалов — чтобы всё изучить потребуется часов 300-400. Прикинул, сколько это займёт времени:

Сколько тратить в день Сколько займёт времени
режим «Илон Маск» три недели
40 часов как на работе всего два с половиной месяца
по часу каждый день 13 месяцев
по часу по вечерам в будни полтора года
по часу только по выходным чуть меньше четырех лет

Понятно, что с наскоку такой объём не взять. Надо разделить слона на кусочки. А чтобы через два дня не забыть, что у меня на кухне разделанный слон, сделал гугл-таблицу со списком материалов и ссылок.

расписал порядок действий, конкретные формулы и приёмы оформления таблицы в заметке «Как спарсить веб-страницу гугл-таблицей»

Обобщенные данные автоматически собираются в аккуратный дешборд. Который можно поставить себе в блог.

Мой индекс джедая — 70 баллов из 290

Посчитать свой индекс джедая

  1. Скопировать таблицу на свой гугл-драйв: файл → сохранить копию.
  2. Сбросить прогресс в чекбоксах → на второй странице выделить столбцы G и H, нажать пробел.
  3. Пройтись по всем пунктам и проставить чекбоксы у изученных материалов.
  4. Поменять имя в А1 на первом листе.
 3   1 mon   IT-Agency   гугл таблицы   Сделал

Как спарсить веб-страницу гугл-таблицей

У агентства IT-Agency есть план обучения для сотрудников — он открыт и опубликован на их сайте.

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

Три способа собрать данные в таблицу:

  • Ручной. Можно скопировать всё руками: текст сюда, достать ссылку, поставить рядом, указать номер.
  • Автоматический. Написать парсер на Питоне (как я делал с блогом Бирмана). Но потом придется всё равно как-то копировать данные в гугл-таблицы, где надо будет отмечать прогресс.
  • Полуавтоматический. Как-то сразу получить данные с сайта в таблицы. Видел в гугл-таблицах формулы для импорта HTML.

Выбираю третий вариант — будут парсить сразу в гугл-таблицы.

Парсинг

Через внутреннюю справку ищу подходящую формулу для парсинга. Нахожу IMPORTHTML:

Imports data from a table or list within an HTML page.

Синтаксис формулы: IMPORTHTML(url, query, index). Здесь query это либо список, либо таблица. Удобно для узкой задачи, но у нас текст и заголовки — не подходит.

Смотрю похожие формулы, нахожу IMPORTXML:

Imports data from any of various structured data types including XML, HTML, CSV, TSV, and RSS and ATOM XML feeds.

Синтакс IMPORTXML(url, xpath_query), где

  • url — ссылка,
  • xpath_query — запрос на языке XPath.


Вроде похоже. Для теста запускаю формулу с ссылкой на веб-страницу IT-Agency с параметром по умолчанию:

=IMPORTXML("https://www.it-agency.ru/academy/jedi-plan/","//a/@href")

Получаю такой результат. Формула работает, остаётся отладить детали.

Иду на веб-страничку с планом и изучаю как выглядят нужные мне элементы через хром девтулс:

<h2 class="fix-after-p">I. Система организации работы</h2>

<p>1.1. <a href="/academy/solo_on_the_keyboard">Пройти Соло на&nbsp;клавиатуре</a>, чтобы&nbsp;экономить время, печатать быстро и&nbsp;без&nbsp;ошибок даже&nbsp;в&nbsp;темноте. Будет полезно, если <a href="https://nabiraem.ru/test/">в&nbsp;тесте</a> покажете скорость ниже&nbsp;250 или&nbsp;допустите ошибок больше десяти.</p>

Значит, мне нужны тэги h2, h3, p и атрибут href у тэгов a (адреса ссылок). Лезу в мануал изучать синтаксис языка XPath.

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

=IMPORTXML("https://www.it-agency.ru/academy/jedi-plan/",
    "//div[@*]//h2 | //div[@*]//h3 | //div[@*]//p | //div[@*]//a/@href")

И весь текст с сайта, разбитый на колонки в таблице.

Обработка

С парсингом разобрался, теперь есть данные. Пока что выглядит не очень — здесь сложно отслеживать прогресс и изучать материалы. Оформим всё как надо.

Некоторые тэги p разбиты на несколько ячеек — соберём текст в одну ячейку через формулу JOIN.

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

Делаю ячейку True / False с детектором ссылок через простое регулярное выражение:

=REGEXMATCH(G20, "^((http.)|(/.))")

Добавляю в формулу join проверку на условие True / False из ячейки с детектором ссылки:

=if(NOT(C20), join(" ", G20:AL20), "")

В соседнюю колонку собираю отдельно ссылки по тому же условию только инвертированному

=if(NOT(C23), "", G23)

Получаю две колонки: отдельно весь текст и все ссылки

Эти две колонки удобно взять и скопировать на новый лист, чтобы оформить.

Оформление

Копирую колонки с текстом и ссылками на новый лист. Использую копирование, а не автоматические ссылки, чтобы зафиксировать ячейки. Дальше будет оформление и не хочу, чтобы оно потом «поползло». На этом этапе теряется автоматизация — если страница на сайте агентства поменяется, то контент в таблице останется прежним.

Заменил дефолтный шрифт Arial на приятный Proxima Nova, который чем-то похож на шрифт Gerbera на сайте агентства.

Взять фирменный цвет агентства с их сайта — #D82C2C

Добавим условное форматирование цвета заголовков через регекс:

=regexmatch(B1,"^[I, V, X]")

Оформить заголовок, добавить туда ссылку на оригинал.

Через ручной поиск и замену исправить внутренние ссылки типа /academy/… до полных www.it-agency.ru/academy/…

Сделаем ссылки great again — добавим каждой название. Для этого спарсим название страницы по каждоый ссылке:

=join(" — ",IMPORTXML(J14, "//h1"))

Так, например из ссылки http://vsevolodustinov.ru/blog/all/lyubite-kritiku-ischite-kritiku/ получим «Блог Всеволода Устинова — Любите критику, ищите критику»

Из ссылки и спарсенного названия составляем красивую ссылку через формулу HYPERLINK

=hyperlink(J9,IF(iserror(K9), J9,K9))

Получаем название со ссылкой Блог Всеволода Устинова — Любите критику, ищите критику

В таблицах есть какое-то системное ограничение на парсинг, поэтому только часть из 290 формул смогли спарсить название страницы. Для этого в формулу HYPERLINK добавил проверку на ошибку парсинга — если есть ошибка, то название ссылки будет самой ссылкой.

У каждой ссылки ставим чекбокс, чтобы не забыть, что уже прочитал. Чтобы разделить ссылки на открытые и внутренние, я ставил чекбоксы в два разных столбца для каждого типа.

Группируем строки по темам, чтобы было легче перемещаться по всей длине документа.

Скрываем рабочие поля, получаем опрятную страницу.

Дешборд с прогрессом

Теперь eсть список материалов по теме с чекбоксами у каждой ссылки. Хочу видеть общую информацию о прогрессе по каждой области на отдельной странице. Такие страницы со сводкой называют дешбордами (dashboard).

Чтобы понять прогресс по каждому разделу, нужно знать, сколько чекбоксов отмечено — считаем по формуле:

=COUNTIF(G9:G73, True)

Рядом так же считаем столбец с чекбоксами внутренних ссылок. И считаем общее количество ссылок в разделе.

Здесь на каждый раздел пришлось руками ставить границы формулы. Не придумал, как можно автоматизировать.

Делаю новый лист. С помощью SQL-подобного запроса соберём с листа названиями разделов. Повезло, что они пронумерованы римскими цифрами — это упрощает дело. Есть всего три знака, с которых может начинаться искомые строки: ’I’, ’V’ или ’X’.

=QUERY('The Path'!B:B,
  "select B WHERE B IS NOT NULL
    AND (B LIKE 'I%' OR B LIKE 'V%' OR B LIKE 'X%')")

Через VLOOKUP собираем счётчики по каждому разделу

=VLOOKUP($B6,'The Path'!$B:$I,6,FALSE)

Получается 4 числа:

  1. сколько пройдено открытых материалов,
  2. сколько пройдено закрытых,
  3. сколько всего материалов,
  4. и ещё сколько из них открытых.

Чтобы понимать прогресс из этих цифр собираем строку вида «25 / 63» формулой

=sum(F6:G6)&" / "&H6

Рядом тот же прогресс в процентах.

Подбиваем «Итого» — в числах и в процентах.

Выводим прогресс в заголовок: собираем строку из имени и процента.

=A1&" — джедай на "&ROUND(E19*100)&" %"

Получается такой дешборд-сертификат-грамота

 3   1 mon   IT-Agency   гугл таблицы   Сделал

Календарь жизни

Если взять 90 лет по 52 недели и разложить их на одном листе — получится календарь жизни.

Я заморочился и сделал такой календарь в гугл таблицах. Получилось так:

Календарь помог мне понять пару вещей:

  • увидеть, сколько времени прошло с момента условной самостоятельности — например, выпуска из универа;
  • сравнить, как это время относиться к оставшемуся до «пенсии». В кавычках потому что это условность, просто удобное число для точки отсчета;
  • понять, что жизнь в 30 не заканчивается, а только начинается: впереди ещё лет 30 трудоспособности;
  • просто для интереса переложить годы на возраста. Типа 35 лет мне будет в 2022 году. В голове эти две шкалы были никак не связаны. (Место для шутки, что 90-е были не 10 лет назад).

Ссылка на таблицу. Слева сверху год рождения — можно поставить свой (скопировав себе табличку). Годы переучиваются, ячейки перекрасятся. Автоматизация неидеальная, считает ДР как 1 января ¯\_(ツ)_/¯

Ещё по теме:

  1. Timestripe — красивый проект ребят из студии Лебедева
  2. Рассуждения и иллюстрации Тима Урбана на эту тему.
  3. Восприятие времени и фильм «Прибытие»

UPD: чтобы вносить изменения в таблицу, сохраните копию к себе на диск.

Табличка открыта только для просмотра, иначе все будут редактировать один и тот же календарь.

Файл → сохранить копию → выбрать папку на своём диске
 1 comment    14   2 mon   время   гугл таблицы   Сделал

Анализ блога Ильи Бирмана с помощью Python

Это вторая заметка из серии «Тренируем Python на блоге Ильи Бирмана».

  1. Сбор данных
  2. Анализ данных
  3. Визуализация результатов (coming soon)

В предыдущих сериях

Этап сбора данных закончился тем, что все данные собрали в одну большую табличку — dataframe. Чтобы данные не потерялись, выгрузил их в файл .csv

Подготовка данных

Чтобы начать работу, загружаем данные.

birman = pd.read_csv("birman.csv", sep = ";", engine = "python")

На всякий случай проверяем количество строк и столбцов.

birman.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4487 entries, 0 to 4486
Data columns (total 8 columns):
title       4487 non-null object
datetime    4487 non-null object
views       4487 non-null int64
comments    4487 non-null int64
length      4487 non-null int64
images      4487 non-null int64
tags        4392 non-null object
link        4487 non-null object
dtypes: int64(4), object(4)
memory usage: 280.5+ KB

Оу! Куда-то потерялись 95 тэгов. Надо проверить. Смотрим у каких строк тэги null.

birman[birman["tags"].isnull()][["title","link"]].head(3)
title link
Вильнюс https://ilyabirman.ru/meanwhile/all/vilnius/
Продам чехол «Люкса-2» для 11-дюймового Эйра https://ilyabirman.ru/meanwhile/all/avito-luxa2-11/
Продам сумку Инкейс для 11-дюймового Эйра https://ilyabirman.ru/meanwhile/all/avito-incase-11/

Переходим по ссылкам и видим, что действительно у некоторых заметок нет тэгов. Но у нескольких тэги почему-то потерялись.
Делаем ещё один проход по таким ссылкам и проверяем тэги. Добавляем у кого есть, у кого нет — ставим тэг «без тэга».

branch = birman

for item in branch[branch.tags.isnull()].itertuples():
    #print(item.link)
    
    blog_page = requests.get(item.link)
    blog_page_soup = BeautifulSoup(blog_page.content, "html.parser")

    string_with_tags = ""
                   
    for tag in blog_page_soup.select(".e2-tag"):
        string_with_tags += tag.get_text() + ", "

    if len(string_with_tags) > 0:
        birman.loc[item.Index, "tags"] = string_with_tags[0:-2]
    else:
        birman.loc[item.Index, "tags"] = "без тэгов"

Проверяем

birman.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4487 entries, 0 to 4486
Data columns (total 8 columns):
title       4487 non-null object
datetime    4487 non-null datetime64[ns]
views       4487 non-null int64
comments    4487 non-null int64
length      4487 non-null int64
images      4487 non-null int64
tags        4487 non-null object
link        4487 non-null object
dtypes: datetime64[ns](1), int64(4), object(3)
memory usage: 280.5+ KB

Всё на месте — все null заменены на нормальные данные. Можно работать.

Анализ данных — первое приближение, birdview

Для начала посмотрим общие цифры по годам, чтобы понять общую картину. Группируем данные по годам и складываем значения.

by_years = df.groupby(df.datetime.dt.strftime("%Y")).sum()

Добавляем количество заметок за год

by_years["count"] = df.groupby(df.datetime.dt.strftime("%Y")).views.count()

Результат:

year posts views comments length images
2002 11 36 0 2511 7
2003 365 597 398 23826 3
2004 505 3398 1125 39507 0
2005 375 1672 995 40191 3
2006 236 102054 1351 38190 7
2007 266 2766 2355 33408 4
2008 210 6003 2676 30365 7
2009 225 17182 4617 34210 22
2010 145 10541 2849 26297 15
2011 142 9294 3193 26091 96
2012 246 20573 10 43734 122
2013 280 48444 38 49875 342
2014 287 31730 535 63677 322
2015 199 23430 96 44668 288
2016 268 18869 286 54088 372
2017 339 19484 395 63107 986
2018 295 280784 723 70380 1210
2019 93 84838 228 34654 378

Посмотрим на каждый показатель отдельно:

  • Количество. Первые 11 заметок в блог Илья написал ещё в 2002 году. Далее с небольшими отклонениям было примерно по 300 заметок каждый год. Удивительное постоянство для такого большого периода.
  • Комментарии. До 2011 было в среднем 2000 комментариев в год с пиком в 4617 комментариев в 2009 году. Но в 2012 комментариев было только 10 штук. И все к первой заметке в году, дальше — ноль. Надо бы спросить у Ильи что случилось.
  • Длина . Средняя длина заметок росла: с 78 слов на одну заметку в 2004 году до 239 в 2018.
  • Картинки. Видно, что до 2010 картинок в заметках почти не было. В 2011-2012 годах уже примерно 100 картинок за год. Дальше — больше: в 2013-2016 уже по ~300 в год. С 2018 в среднем по тысяче.
  • Просмотры. С самого начала Эгея не показывала просмотры. До марта 2018 года у заметок в основном единицы просмотров. Были исключения, но, если я правильно понял механизм, эти просмотры Эгея посчитала уже после марта 2018.

Ориентироваться на просмотры можно за 2018 и половину 2019 года:

year posts views views_mean views_median
2018 295 280784 951 807
2019 93 84838 912 841

В 2018 году в среднем одну заметку посмотрели 951 раз, в 2019 — 912 раз. Значение медианы близко к среднему, значит — данные без сильного перекоса.

Самые-самые заметки за всё время

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

Самые просматриваемые

title date views tags
О запятой после «С уважением» 2006 87974 русский язык
Переплата по кредиту 2013 39296 жизнь, общество, экономика
Числа π и e 2012 14387 математика
Война 2015 13601 красная таблетка, общество
Почему люди платят налоги 2014 9310 красная таблетка, общество, философия, экономика

Получился хороший список для общеобразовательного чтения. Русский язык, математика и философия. Если дочитали заметку до сюда, рекомендую прочитать и заметки Ильи (и приведённые, и все остальные).

Самые комментируемые

title date comments tags
Ремонетизация 2009 200 реклама, этот сайт
Бананотехнология 2009 199 еда, жизнь
Почему люди платят налоги 2014 193 красная таблетка, общество, философия, экономика
Опенсос 2010 174 идиоты, опенсорс, софт
Кто на чём 2007 150 браузеры

Выборка постов получилась неслучайной. Во всех есть приглашение к комментированию. Особенно примечательна заметка из 2014: в том году комментарии были разрешены только к 12 заметкам (из них половина — рубрика «Дискуссии по понедельникам»).

Самые длинные

Здесь четыре рецензии на книги с большими выдержками и один «полный гид».

Если не засчитывать вставки из книг и пересчитать рейтинг без заметок с тэгом «книги», получится тоже интересная подборка:

title date length tags
Полный гид по клубу Бергхайн 2018 3493 Бергхайн
SynSUN — Phoenix 2006 1714 музыка, обзоры
Кофейные места 2017 1657 кофе
Голосовые объявления в лондонском метро 2013 1619 …навигация в общественных местах…
Как выучить иврит — 2 2018 1568 иврит

Самые… заметки, где больше всего картинок

title date images tags
Процесс создания логотипа Драйвинг-тестов. Часть 1 2018 36 портфолио, процесс
Телеграм за неделю 5—11 февраля 2018 2018 32 телеграм-канал
Телеграм за неделю 12—18 февраля 2018 2018 30 телеграм-канал
Санкт-Петербург: Гранд-макет 2018 29 мир, музеи и выставки, Санкт-Петербург
Тель-Авив: прогулка по Флорентину 2018 28 мир, Тель-Авив

Воспользуюсь правом, данным мне библиотекой pandas, и уберу заметки с тэгом «телеграм-канал». Получилось интереснее, как мне кажется.

title date images tags
Процесс создания логотипа Драйвинг-тестов. Часть 1 2018 36 портфолио, процесс
Санкт-Петербург: Гранд-макет 2018 29 мир, музеи и выставки, Санкт-Петербург
Тель-Авив: прогулка по Флорентину 2018 28 мир, Тель-Авив
Музей БМВ в Мюнхене 2016 26 автомобиль, Германия, музеи и выставки, фото
Регистрация на рейс «Аэрофлота» 2017 24 полёты, пользовательский интерфейс, студентам

Получилась уже длинна заметка. На этом пока всё. Далее по плану анализ тэгов:

  • общее количество,
  • самые долгоживущие,
  • самые популярные и т.д.

И, конечно, хочется все эти данные и выводы красиво визуализировать. К тому же, у Python есть классные библиотеки типа Seaborn и Bokeh. Но это уже в следующих сериях.

 24   4 mon   Python   Накодил   Сделал

Тренируем Python: веб-скрэпинг на примере блога Ильи Бирмана

Веб-скрэпинг — это автоматизированный сбор данных с сайтов в интернете.

Часто применяется, например, интернет-магазинами, чтобы следить за ценами и ассортиментом конкурентов. Ещё можно получить с сайта NASA данные об орбитах всех космических тел в Солнечной системе.

Это первая заметка из серии «Тренируем Python на блоге Ильи Бирмана».

  1. Сбор данных
  2. Анализ данных
  3. Визуализация результатов (coming soon)

Зачем всё это

На майских праздниках я начал изучать язык программирования Python.
Пайтон привлёк меня своей универсальностью: можно быстро что-нибудь автоматизировать, написать скрипт для веб-скрэпинга или проанализировать пару миллионов записей в базе данных. А если прокачаться в математическом анализе, теории вероятностей и линейной алгебре, можно писать код для машинного обучения.
На платформе Code Academy я изучил основы языка, прошёл несколько курсов, сделал много учебных примеров. По итогам решил сделать выпускной проект (бывает выпускной через месяц?), чтобы потренироваться на живом примере и на практике освоить полученные знания.

В теории нет разницы между теорией и практикой. А на практике есть

Йоги Берра — американский бейсболист и менеджер бейсбольной команды.

Выбор объекта исследования

Для объекта исследования выбрал блог Ильи Бирмана — дизайнера и арт-директора, диджея и музыканта, преподавателя и писателя, философа и математика, фотографа и путешественника, автора блога и видео-заметок.

3 причины такого выбора:

  1. Илья — интересный человек. Это, кажется, главное в личных проектах — чтобы было интересно и безудержно пёрло от процесса;
  2. большой массив данных для изучения: Илья регулярно ведёт блог с 2003 года;
  3. в блоге есть отдельная страничка со всеми-всеми записями — идеально для первого скрэпинга (ведь я ещё не знаю, как автоматически листать страницы);
  4. БОНУС! — передать привет Илье и сгенерировать ему просмотров на сайт (два или, может, даже три).

Инструменты

  • Python. Плюс дополнительные библиотеки:
    • BeautifulSoup + Requests — для, собственно, веб-скрэпинга
    • RE — для очистки данных с помощью регулярных выражений
    • Pandas — для хранения и обработки полученных данных
    • Matplotlib и Seaborn — для визуализации результатов
  • Jupyter Notebook — визуальная среда для кодинга
  • Chrome DevTools — чтобы понять как устроен искомый сайт внутри
  • Google и StackOverflow — чтобы понять, мой идеальный код не работает.

Знакомство с объектом исследования

Итак, есть блог Ильи Бирмана — https://ilyabirman.ru/meanwhile/

Блог работает на движке Эгея — кстати, тоже проект Ильи. У Эгеи есть отдельная страница со всеми записями. Для блога Ильи она доступна по адресу https://ilyabirman.ru/meanwhile/all/

Все 4486 заметок на одной странице — ух!

Страница отдельной заметки:

Какие параметры для исследования здесь можно добыть:

  • заголовок заметки, куда без него;
  • количество просмотров — можно найти самые просматриваемые;
  • количество комментариев — найти самые обсуждаемые заметки;
  • тэги заметки — может пригодиться;
  • дату публикации — чтобы привязать данные ко времени;
  • количество картинок — для общей картины;
  • количество слов в заметке — интересно будет поискать корреляцию длины с просмотрами или комментариями;
  • URL — чтобы потом включить в отчёт и быстро находить нужные заметки.

К делу — расчехляем Python

Писать код удобно в Jupyter Notebook. Это визуальная среда для программирования, где можно сразу посмотреть результат.

Импортируем нужные библиотеки

from bs4 import BeautifulSoup
import requests
import re

Чтобы разобраться, как добывать данные из веб-страницы, начнём с простого: возьмём одну заметку и попробуем добыть информацию из неё.
С помощью requests обращаемся к странице блога со всеми записями. И с помощью BeautifulSoup делаем «мыло» — soup объект.

webpage = requests.get("[https://ilyabirman.ru/meanwhile/all/chernobyl-podcast/](https://ilyabirman.ru/meanwhile/all/)")
soup = BeautifulSoup(webpage.content, "html.parser")

Заголовок заметки

С объектом soup уже можно работать: например, достать оттуда заголовок — тэг h1:

soup.h1

На это но выдаст весь тэг h1:

<h1 class="e2-published e2-smart-title">Про подкаст «Чернобыля»</h1>

Достать только видимую часть — текст — можно через метод .get_text():

title = soup.h1.get_text()

На выходе получили «\r\nПро\xa0подкаст «Чернобыля» \r\n». Похоже на правду, но не совсем.

\xa0, \r и \n — это специальные символы. На сайте браузер их не показывает, но после конвертации в строку они «проявились». Почистим данные с помощью регулярных выражений — библиотека re.

title = re.search("[^(\n|\r)]+", title).group()

На выходе строка: ‘Про\xa0подкаст «Чернобыля» ’

Функция search библиотеки re возвращает объект match. Чтобы получить на выходе обычную строку, надо добавить метод .group()

На все попытки засунуть в регулярное выражение ещё и \xa0, Пайтон упорно выдавал ошибку. Не понял как сделать поиск всё в одном, пришлось пройтись по тексту ещё раз простой заменой через .replace().

Меняем неразрывный пробел на обычный. Заодно удаляем пробелы в начале и конце.

title = title.replace("\xa0"," ").strip()

На выходе нужная строка: ‘Про подкаст «Чернобыля»

Просмотры

С заголовком заметки было просто — элемент h1 обычно только один на странице. Мы обратились к нему по типу, без дополнительного поиска.

С более простыми элементами сложнее: обычно их больше одного на странице. Чтобы с помощью BeautifulSoup найти нужный элемент, нужно о нём что-нибудь знать.

Здесь поможет встроенный инструмент браузера Chrome — DevTools — с его помощью можно залезть сайту под капот и посмотреть как там всё устроено.

Ищем на странице заметки счётчик просмотров:

Вот он — рядом с глазиком. Заметку посмотрели 632 человека.

Смотрим, как он выглядит в html коде. В Хроме наводим на элемент и нажимаем inspect

Или можно горячими клавишами shift+command+c открыть DevTools и навести на нужный элемент.

Искомая цифра 632 находится внутри тэга с классом e2-read-counter :

<div class="e2-note-meta">
  <span class="e2-read-counter">
  <span class="e2-svgi"> … </span> 
  632</span>
  <…>
</div>

С помощью BeautifulSoup обращаемся к элементу по его классу:

views_span = soup.select(".e2-read-counter")[0].get_text()

На выходе получаем « 632 ». ПРЯМОЕ ПОПАДАНИЕ! — не перестаю удивляться мощи программирования.

Выглядит как цифры но на самом деле это текст (да ещё и с пробелом!). Тип переменной проверяется через функцию type():

>>> print(type(views_span))
<class 'str'>

Через уже знакомые регулярные выражения добудем из этого текста только цифры:

views_span = re.search("\d+", views_span).group()

И переведём текст в цифры — тип integer:

views_span = int(views_span)
>>> print(type(views_span))
<class 'int'>

Итого: у нас уже есть заголовок заметки и количество просмотров.

Обработка исключений

Забегая вперёд, расскажу об обработке исключений. Написанный код отлично работает на одной свежей заметке из блога. Как показала практика, это вовсе не означает, что он будет работать на всех 4486 страницах.

На заметках старше 2017 года код начал выдавать ошибку. Я добавил отладчик в код, чтобы он показывал адрес страниц, где была ошибка. Заходил туда и пытался понять, в чём проблема. На вид эти страницы ничем не отличались от других. Кроме того что просмотров у них было 1.

Видимо, Эгея не сразу умела отображать просмотры страницы, но в какой-то момент научилась. На страницах, опубликованных до этого момента, просто нет тэга с классом e2-read-counter. Зато при первом заходе на такую страницу через браузер (но не через парсер!) Эгея его автоматически добавляет прямо налету.

Не знаю, как матёрые питонисты обрабатывают исключения, мне пришло в голову проверять наличие нужного элемента через его длину:

if len(soup.select(".e2-read-counter")) > 0:
	…сбор данных…
else: 
	views = 0

Если такого элемента на странице нет, то считаем, что просмотров ноль.

Комментарии

По примеру счётчика просмотров повторяем всю процедуру. Ищем элемента на странице. Видим, что комментарии в коде идут так:

<span id="e2-comments-count">4 комментария</span>

Здесь у тэга указан айди, а не класс (как у тэга с просмотрами). Пришлось воспользоваться другим методом BeautifulSoup — .find()

Комментарии — это тебе не просмотры. Здесь даже мне было очевидно, что у заметки их может и не быть, поэтому обработчик я добавил сразу.

if soup.find(id="e2-comments-count") != None:
  comments_span = blog_page_soup.find(id="e2-comments-count").get_text()
  comments = (int(re.search("\d+", comments_span).group(0)))
else:
  comments = 0

Всё по аналогии с просмотрами: ищем элемент, забираем текст, выуживаем число, переводим в интеджер.

Если элемента с комментариями нет, значит, их ноль.

Тэги

Повторяем знакомую процедуру для тэгов заметки.

<div class="e2-note-meta">
  …
  <a href="https://ilyabirman.ru/meanwhile/tags/movies/" class="e2-tag">кино</a> 
  &nbsp; 
  <a href="https://ilyabirman.ru/meanwhile/tags/podcast/" class="e2-tag">подкаст</a>
</div>

Новый челендж: в отличие от счётчиков, тэгов может быть несколько. Удобно, что к них есть отдельный класс, по которому можно обратиться. Делаем цикл по всем элементам с нужным классом и добавляем все элементы в заготовленный список:

tags = []
for tag in soup.select(".e2-tag"):
    tags.append(tag.get_text())

Проверяем, что получается:

>>> print(tags)
['кино', 'подкаст']

Всё норм!

Картинки

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

В нашей подопытной заметке про подкаст картинок нет, поэтому берём заметку про Черногорию. Смотрим в DevTools на первую картинку. HTML выглядит так:

<div class="e2-text-picture-imgwrapper" style="padding-bottom: 66.67%">
	<img src="https://ilyabirman.ru/meanwhile/pictures/kotor-DSCF1378.jpg" width="1200" height="800" alt="">
</div>

Решил получить нужную цифру через див: находим на странице все дивы с нужным классом: soup.find_all(“div”, class_=“e2-text-picture-imgwrapper”). Метод .find_all() возвращает список найденных элементов. То есть количество нужных элементов на странице — это длина этого списка.

images = len(soup.find_all("div", class_="e2-text-picture-imgwrapper"))

Проверяем:

>>> print(images)
16

На всякий случай пойдём и посчитаем пальчиком все фотографии Черногории в заметке — получилось 16. Работает.

Длина текста

Для полноты данных соберём длину текста заметки. Через DevTools ищем элемент, который содержит в себе только заметку, без прочего «обвеса». Находим элемент article. Отдельный и единственный — удобно будет к нему обращаться.

Подумал, что длину хорошо быть мерять в словах, а не символах, — будет нагляднее.

Чтобы получить длину в словах, достанем текст элемента article и разделим его на слова методом .split(). В качестве параметра функция принимает любой разделитель. Если параметр не указан — считает разделителем пробельные символы.

Функция .split() возвращает список слов. Количество слов — длина этого списка. Сохраняем в переменную.

words = len(soup.article.get_text().split())

Дата публикации

Оставил самое сложное на десерт. С датой возился дольше всего, в итоге получился большой кусок кода.

Начнём стандартно: через DevTools ищем в коде страницы дату. Находим такое:

<div class="e2-note-meta">
  <span class="e2-read-counter">
  <span class="e2-svgi">
    <svg…> … </svg>
  </span> 
  624
</span>
  <span title="10 июня 2019, 16:17, GMT+03:00">3 дн</span>
  <a…> … </a>
</div>

То есть дата публикации спрятана в title элемента span без какого-либо класса внутри элемента div с классом e2-note-meta. Все предыдущие данные мы доставали, обращаясь к элементу через его уникальный class или id. С датой такой подход не прокатит — у элемента нет ни того, ни другого. Зато у него есть title, он и он у каждой заметки свой.

Решил добраться до нужного элемента через его родителя — элемент див с уникальным классом. И обратиться напрямую к его четвертому дочернему элементу:

>>> soup.find("div", class_="e2-note-meta").contents[3]
<span title="10 июня 2019, 16:17, GMT+03:00">3 дн</span>

Код сработал!

Но, как и в случае с просмотрами, меня ждал сюрприз, когда выкатил этот код на весь объём блога. Оказывается-то, блок с просмотрами тоже дочерний элемент дива, через который я обращаюсь к элементу с датой. И, когда у заметки нет просмотров, порядок дочерних элементов меняется, и span с датой уже не четвёртый (и даже не третий — это я сразу проверил!).

Решение пришло на прогулке, куда я вышел после долгих попыток подгадать новый порядковый номер нужного элемента и чтобы он подходил для всех заметок. Оказалось, что в номере просто нет смысла, можно же вложить поиск внутри поиска и найти `span` с датой по другому его уникальному свойству, которое однозначно отличает его от соседей — полному отсутствию класса.

>>> date = soup.find("div", class_="e2-note-meta").find("span", class_="")
<span title="10 июня 2019, 16:17, GMT+03:00">3 дн</span>

Е-е-е-е!

Дело за малым — вытащить оттуда дату. Так, применяем уже стандартный подход с .get_text():

>>> date.get_text()
3 дн

Отлично, блин! — получили возраст публикации. Сама дата-то не внутри элемента, а в его title. Начинаем ~~изобретать велосипед~~ применим регулярные выражения.

>>> date_str = re.search("\".+\"", str(date)).group(0)
"10 июня 2019, 16:17, GMT+03:00"

Уже лучше. Пока ещё просто строка с датой и кавычками, но уже не возраст.

Из этой строки нужно достать: день, месяц, год. Повезло, что каждый из этих данных можно уникально закодировать через регулярные выражения:

  • день — единственные одна или две цифры в строке;
  • месяц — буквы между двумя пробелами;
  • год — единственные четыре цифры в строке.

Для дня и года всё бесхитростно, переводим на язык регулярных выражений:

day = int(re.search("[\d]{1,2}", date_str).group(0))
year = int(re.search("[\d]{4}", date_str).group(0))

С месяцем надо повозиться. Поскольку длина всех месяцев разная (в отличие от дней и годов), то поиск задаём как “буквы без цифр между двумя пробелами”.

В регулярных выражениях буквы можно задавать через последовательности, например, поиск по [a-zA-Z] выдаст все буквы в нижнем и верхнем регистре.

Ещё есть сокращения типа \w, что даст такой же результат как и [a-zA-Z0-9_].

Больше такого в английской документации к библиотеке re или на русской википедии

Воспользуемся вторым примером, но уберём оттуда цифры. И сразу уберём пробелы, по которым искали буквы.

>>> month_str = re.search(" [\w^(\d)]+ ", date_str).group(0).strip()
июня

Гут, идём дальше.

Чтобы потом было легче работать с датами, хорошо бы иметь дату в цифровом виде. Значит, надо перевести месяц в число. Наверняка, есть какие-то элегантные способы, но я поступил топорно: вбил руками список всех месяцев в родительном падеже и искал порядковый номер строки в этом списке:

months = ["января", "февраля", "марта", "апреля", "мая", "июня",
"июля", "августа", "сентября", "октября", "ноября", "декабря"]
month_int = months.index(month_str) + 1

Помните, что индексы идут с нулевого?

Для полной картины ещё соберём часы и минуты публикации. Вдруг захотим построить график средней суточной активности по годам.

time_ = re.search("[\d]{2}:[\d]{2}", date_str).group(0)
hour = int(time_[0:2])
minute = int(time_[3:5])

Для работы со временем и датой в Пайтоне есть специальный объект —datetime. Удобнее всего хранить дату в нём.

date_time = datetime.datetime(year, month_int, day, hour=hour, minute=minute)

Итак, мы собрали все необходимые данные на примере одной заметки:

  • заголовок;
  • количество просмотров;
  • количество комментариев;
  • тэги заметки;
  • количество картинок;
  • количество слов в заметке;
  • дата публикации.

Ещё не хватает URL заметки, добавим позднее.

Теперь надо собрать такие же данные со всех 4486 заметок.

Массовый скрэпинг

Собираем цикл, который пойдёт по по всем заметкам и будет собирать данные по каждой. Скармливаем коду нужную страницу и делаем из неё «мыло».

webpage = requests.get("https://ilyabirman.ru/meanwhile/all/")
soup = BeautifulSoup(webpage.content, "html.parser")

Подготовим пустые списки для каждого типа данных, куда будем собирать результаты.

titles = []
views = []
comments = []
tags = []
datetimes = []
images = []
words = []
links = []

Запускаем цикл для обработки всех ссылок на странице. На языке HTML ссылки обозначаются тэгами a.

for link in soup.find_all("a"):
	…

Проход цикла по всем 4486 заметкам занимает продолжительное время, поэтому сначала лучше ограничить цикл, чтобы отладить код. Я начал с ограничения в 50 записей и по мере улучшения выкатывал на 100-400-1000-2000 записей.

Ограничитель на первые 50 элементов — стандартный синтаксис для работы со списками:

for link in soup.find_all("a")[:50]:
	…

Оказалось, что не все тэги a содержат какие-то ссылки. Добавил в начале соответствующий обработчик: if type(link.get(‘href’)) == type(“string”)

Ещё оказалось, что на странице куча ссылок не на заметки, а на рабочие моменты блога, типа настроек и списка тегов. Пришлось вручную проверять ссылки на наличие в них строк @ajax, /settings/ и /tags/.

Контрольный выстрел: пропускать все ссылки короче 32 знаков (длина строки «https://ilyabirman.ru/meanwhile/», с которой начинаются ссылки всех заметок).

Сначала я отбирал ссылки по наличию в них http://ilyabirman.ru/meanwhile/all/ — адреса всех свежих заметок начинались так. Это дало чистые результаты. Но, когда выкатил цикл на все 4486 заметок, он дал результаты только в две тысячи. Адреса более ранних заметок больше не содержали «/all/». Пришлось внести коррективы.

После обработки всех исключений, цикл доходит до ссылки на очередную заметку. Чтобы её обработать, нужно и из неё сделать объект soup:

for link in soup.find_all("a"):
	post_tags = []
	parse_count += 1
	
	# drop not links (there are some 'None' object in scrapping results)
	if type(link.get('href')) == type("string"):
	    
	    # exclude blog engine settings links         
	    if ("@ajax" in link.get('href'))\
	    | ("/settings/" in link.get('href'))\
	    | ("/tags/" in link.get('href'))\
	    | (len(link.get("href")) <= 32) : # 32 is the length of "<https://ilyabirman.ru/meanwhile/>"
	        continue
	    
	    # drop all except links for blogposts
	    elif "ilyabirman.ru/meanwhile/" in link.get('href'):
	
	        # get a link itself and parse it with with BeautifulSoup
	        blog_page = requests.get(link.get('href'))
	        blog_page_soup = BeautifulSoup(blog_page.content, "html.parser")

Логика немудрёная: иди на страницу со всеми записями и найди там все ссылки, каждую ссылку проверь по условиям, если всё ок — делай из страницы по этой ссылке объект soup.

В итоге наш цикл оказывается на страничке очередной заметки. На этой странице надо собрать все данные с помощью кода, который мы разбирали в начале. Чтобы не повторяться, привожу конечный результат. Разбор есть выше, да и в коде оставил построчные комментарии.

для экономии места уберу отступ; приведённый код внутри цикла, описанного выше;

# get a blogpost title, save to list of titles
titles.append(blog_page_soup.h1.get_text())

# check if a post have a view counter (old posts have no views counter)
if len(blog_page_soup.select(".e2-read-counter")) > 0:

    # get a span block with views count
    views_span = blog_page_soup.select(".e2-read-counter")[0].get_text()

    # get a number from text block and format as integer, save to list of views count
    views.append(int(re.search("\d+", views_span).group(0)))

else:
    views.append(1)

# get comments count
if blog_page_soup.find(id="e2-comments-count") != None:
    comments_span = blog_page_soup.find(id="e2-comments-count").get_text()
    comments.append(int(re.search("\d+", comments_span).group(0)))
else:
    comments.append(0)
    
# get tags for each post
for tag in blog_page_soup.select(".e2-tag"):
    post_tags.append(tag.get_text())
# list of posts' tags
tags.append(post_tags)

# append date and time for each post to the list
append_datetime_from_soup(blog_page_soup, datetimes)

# get images count
images.append(\
	len(blog_page_soup.find_all("div", class_="e2-text-picture-imgwrapper")))

# post's length (words count)
words.append(len(blog_page_soup.article.get_text().split()))

# get link
links.append(link.get('href'))

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

def append_datetime_from_soup(source_soup, list_with_results):
	span_datetime = str(source_soup.find("div", class_="e2-note-meta")\
	                    .find("span", class_=""))
	    
	# get string with date and time from <span> title
	date_str = re.search("\".+\"", span_datetime).group(0)
	
	# get day: one or two digits
	day = int(re.search("[\d]{1,2}", date_str).group(0))
	
	# get month as string
	month_str = str(re.search(" [\w^(\d)]+ ", date_str).group(0))
	month_str = month_str.strip()
	
	# convert string to integer
	months = ["января", "февраля", "марта", "апреля", "мая", "июня",
	          "июля", "августа", "сентября", "октября", "ноября", "декабря"]
	month_int = months.index(month_str) + 1
	
	# get year
	year = int(re.search("[\d]{4}", date_str).group(0))
	
	# get time
	time_ = re.search("[\d]{2}:[\d]{2}", date_str).group(0)
	hour = int(time_[0:2])
	minute = int(time_[3:5])
	
	# make a datetime object
	date_time = datetime.datetime(year, month_int, day, hour=hour, minute=minute)
	
	# append to the result
	list_with_results.append(date_time)

После того как получили результаты — проверьте их правильность. Сколько циклов прошёл парсер? Сколько результатов на выходе? Длина всех списков с результатами одинаковая?

Проверяем результаты:

>>> print(parse_count, len(views), len(images), len(words))
4536 4489 4489 4489

Итого 4536 проходов сделал парсер и собрал списки данных длиной 4489 записей каждая.

Списки с данными одной длины — это хорошо. Но записей больше, чем заметок, но не намного. Видимо, есть лишние или дубликаты. Разберёмся с ними на следующем этапе.

Что дальше?

Чтобы проанализировать собранные данные, воспользуемся библиотекой pandas и запихнём наши списки в одну большую таблицу на стероидах — dataframe.

dict = {"title": titles, 
       "datetime": datetimes,
        "views": views, 
        "comments": comments, 
       "length": words,
       "images": images,
        "tags": tags_as_string,
       "link": links} 

birman_frame = pd.DataFrame(data = dict)

В итоге получаем удобную для анализа структуру. Можно, например, посмотреть заметки с наибольшим количеством просмотров за всё время.

Три самых просматриваемых заметки: о грамматике, об экономике и о матемитике

На этом первая часть отчета закончена. В следующих сериях — анализ собранных данных и визуализация полученных выводов.

 55   4 mon   Python   Накодил   Сделал
 1   2017   bureau   Сделал   школа менеджеров

Итог курса текстописания

Подготовил и защитил свой итоговый проект.

Как проходил курс

Встречались раз в неделю в слаке: проверяли работы за неделю и получали задание на следующую. Задания были в виде отдельных лонгридов — материал для изучения с кучей примеров и ссылок для дополнительного изучения.

На одном из первых уроков затронули тему когнитивных искажений, которая очень меня заинтересовала. Кстати, Тимур сделал интересный блог — примеры когнитивных искажений из художественных произведений.

Тимур предложил посетить «логово» московских рационалистов — антикафе «Кочерга» на Киевской. Это местное представительство клуба LessWrong. Я побывал там на курсе ненасильтсвенного общения (методика от Маршалла Росенберга, в оригинале — nonviolent communication ) и на очередной академической встрече рационалистов.

Примеры уроков

Сейчас трудно описать содержание всех уроков, поэтому напишу коротко: прошли курс молодого, начинающего копирайтера. Обсудили психологические и физические проблемы писательства, разобрали стили письма, узнали основную терминологию. В качестве практики каждый делал продающую страницу для выбранного им предмета (реального или выдуманного).

Так выглядит моя домашняя работа по четырем урокам. Всего было восемь.

Мой проект

На предпоследнем занятии получили задание подготовить стандартный лендинг для выбранного продукта. У меня получился такая заготовка, на которую я планировал накидать мяса при следующем подходе.

Следующим заданием было придумать увлекательную историю про свой продукт. У меня получилось про городского героя в маске — борца за чистоту.

Ребята (и Тимур!) на занятии оценили и посоветовали копать в эту сторону. В итоге получилась история со простой скроллинг-анимацией на редимаге.

Так увлекся анимацией (ну ладно — плохо спланировал время), что совсем забыл про продающую часть. От лендинга остался только почтосборщик.

Выводы

Раньше много читал про это, но теперь на практике понял, что недельный проект начинается не за день до дедлайна, а сразу после согласования задания. В понедельник мы получали очередной урок и начинать изучать его надо было уже во вторник. Иначе ничего не успеешь.

Честно сказать, до конца технику не освоил, потому что всё равно все задания делал за день-два до дедлайна. Нужно больше практики)

Хороший подход к работе — работа итерациями. С первого подхода хороший продукт не сделать. Надо делать несколько подходов. При чем самый первый может быть насколько угодно малым и/или простым — главное начать.

Получил опыт подготовки текста под руководством редактора. Делаешь текст, а потом получаешь по нему замечания. Крайне интересный опыт.

 1   2017   HSE   LessWrong   вспомнить в конце 2017   Сделал

Календарь на рабочий стол

На винде можно было тыкнуть в поле времени и появлялся календарь на месяц. На маке мне этого не хватает.

Еще по работе часто встречаются фразы типа «товар будет готов на 36 неделе» или «ответим не ранее 34-й недели». И приходится напрягаться, заходить в календарь гугла, чтобы там посмотреть когда же настанет эта 34-я неделя.

Как раз изучал символы в скетче и решил сделать себе календарик. Как самый простой вариант сделал его как обои на рабочий стол.

Построен по принципу вечного календаря. Как всегдабрь Ильи Бирмана.

На нижнем слое расположены ряды цифр, которые можно двигать. Маска показывает только числа текущего месяца.

На новый месяц ряды цифр просто сдвигаются в сторону. В июле 2017 первое число месяца приходится на субботу. В августе — на вторник. Чтобы первое число стало вторником, а не субботой, числа смещаются на четыре дня по 90 пикселей.

Кстати, поля с размерами в скетче поддерживают математику.

Стили сделаны через символы: применится сразу ко всем дней недели, номерам недель или числам месяца.

Все объекты названы и сгруппированы в папках

Для разнообразия (яждизайнер) сделал маску по горизонту. Календарь элегантно прячется за горами.

 1   2017   Сделал

Итог второго модуля

Завершился второй модуль курса «Дизайн цифрового продукта» — показывали сделанные проекты.

Поскольку результата у меня не очень много, речь больше пойдет о процессе.

Задача

Постановка задачи

Постановку задачи для себя я определил как знакомство студента с основными принципами, этапами и инструментами разработки цифрового продукта на придуманном примере.

Узнал как работать с ЦА и персонажами, что такое цифровой продукт как таковой. Где искать референсы, как делать макеты и делать из них сайт на редимаге.

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

Аудитория

Целевая аудитория — обладатели миникуперов. Это довольно известная и понятная тусовка. Возраст от 18 до 60, состоятельные, активные, интеллигентные.

Чтобы познакомиться с целевой аудиторией, я нашел форум владельцев мини куперов. Это оказалось активное сообщество, постоянно устраивают совместные движухи. Собираются на воскресные завтраки, совместные автопробеги и даже зарубежные поездки.

мувик

Референс — галереи Референс — типографика Типо логотип Выбранный стиль

Референсы

Как референсы я отталкивался от сайтов татуировщиков или студий аэрографии. Как видно, таким ребятам нужно минимум текста и максимум примеров работ. Никто не выбирает татуировщика по длине его рассказа о себе.

Поскольку текста немного, хотел сделать его максимально воздушным, поэтому искал какие-то минималистические мотивы в типографике.

Из двух таких референсов слепил логотип.

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

Выбрал Фира Санс. Гуманистический гротеск. Неконтрастный, статичный, с открытой апертурой. В начертании Light.

сетка

Сетка

Самый первый вариант главной страницы выглядел как три абзаца текста, равномерно размазанные по странице. Это никуда не шло, нужна был сетка.

Я пытался срифмовать ее с логотипом и взял 3-х колоночную сетку. Не уверен, что из этого получилось.

Первый подход

Как появилась сетка, текст начал складываться. И меня понесло. Набрал из учебника по лендингам всего помаленьку и начал адаптировать к стилу и сетке.

Получился полный франкенштейн из стилей и элементов тысяч на 8 пикселей — большой джипег . Видел где-то обрезанные болдовые цифры — берём. Обязательно у всего должны быть тенюшки. Ну и конечно градиентик — куда же без него? Были разделы о команде, отзывы клиентов, этаж с тарифами и даже частые вопросы.

Я был доволен собой: собрал нормальный такой лендос — пока не показал его Олегу. Когда он смотрел, у него заболели глаза. По крайней мере, мне так показалось.

Оказывается, в миинмалистическом дизайне (на который я заявлялся), нужно всё делать минимально. Без тенюшек и эффектов. Один, максимум два стиля. Иллюстрации в одном стиле. С неярким цветом или совсем без цвета. В общем, полный провал.

Второй подход

Получился сумбурным. Убрал почти все этажи от первого лендинга и пытался сосредоточится на оформлении галереи. Вышло так себе)

Сверстано на редимаге.

На просмотре показывал этот вариант.

Альтернатива

За пару часов до просмотра подумал, что последний вариант тоже слишком избыточный. Не нужно столько текста. Ну или не на главной.

И накидал на редимаге альтернативный прототип.

Минимум текста, максимум картинок. В идеале сайт сразу должен показывать работы. Нужна какая-то система для них. И чтобы их было легко добавлять и заменять без участия программистов.

Сделал свой первый макет (процесс)

Провел его через несколько итераций с замечаниями «технического» и «арт-директора» — кураторов курса.

Обо всем по порядку:

Студия Лебедева объявило весенние вакансии, в том числе и на дизайнера сайтов.

Студии нужен дизайнер сайтов. Претендентам предлагается нарисовать главную страницу сайта для магазина, продающего самогон.

Олег предложил нам поучаствовать.

Группа по настоянию Олега подошла к делу серьезно: начали с проработки персонажей. Метод персонажей помогает представить клиентов разрабатываемого продукта.

Продолжили продуктовой аналитикой: рисовали майнд-мап в сервисе Когл.

Ну и закончили подбором референсов и составлением мудбордов.

В ту неделю я был в отъезде, поэтому успел только к просмотру результатов. Ребята постарались и сделали хорошие работы. Некоторые даже успели в срок и отправили работы в Студию.

Пока я смотрел работы, в голове проносилось мысль, что всё скучно и однообразно. Думал «Одни бутылки с ценниками! Никакого разнообразия!».

Когда приступил к работе, первые черновики делал в сторону тематики бутлегеров ( wiki ). Хотел поразить уникальностью работы, чтобы она выделялась на фоне однообразных бутылок. У меня были не бутылки самогона, а целые ящики с классными баночками бутлегерского самогона. Но все идеи не проходили проверку дальше первых набросков.

Тогда до меня стало доходить, что я копаю не в ту сторону. Михаил объяснил, что в таких заданиях проверяется не креативность, а знание базовых вещей о дизайне стандартной страницы онлайн-магазина.

Тогда получилось накидать каркас, на основе которого в итоге и сделал финальный макет.

Процесс в фотораме

Каждый макет я показывал Олегу и Михаил как последний —уже не знал как сделать лучше. Но каждый раз наши «директора» делали полезные замечания и макет потихоньку улучшался.

И наконец

Очень интересный опыт.

П.С.: графику делал в фотошопе, макеты — в скетче.

Закончили первый модуль

Прошли первые два месяца обучения и на первой неделе апреля у нас был просмотр. Так в Школе Дизайна ВШЭ называют экзамен.

Ученики готовят к показу проекты и один за другим его презентуют преподавателю и гостям. Гостей могут пригласить как преподаватель, так и ученики. Олег пригласил Виктора Меламеда из БВШД и еще на несколько презентаций заходили преподаватели других программ ВШЭ — Алексей Рюмин и Протей Темен.

Проектами на первый модуль у нас были визуализации поэзии. Из предложенного списка каждый выбрал себе по стихотворению.

Я выбрал «Штабик» Владимира Навроцкого. Мне показался он самым адекватным из всех приведенных.

результат

Короткие презентации по проекту ученики заливают в портфолио на сайте Школы. В своей презентации я собрал референсы: доску Олега в Pinterest, видео жанра Kinetic Typography (классные видео, кстати) и до кучи упомянул направление веб-брутализм.

Для общей картины приведу несколько работ других ребят.

Чернеет парус одинокий. Аня Константинова

Тринадцать способов нарисовать дрозда. Василий Бочаров

Тяжело но можно. Вита Хомченко

Гагарин соврал лишь раз в жизни. Константин Шибалков

Любовь и смерть. Маша Коваленко

Ползет. Оля Бровкина

Девочке медведя подарили. Роман Гордеев

Как хорошо когда есть стол. Наталия Тищенко

 2   2017   2017   HSE   вспомнить в конце 2017   Сделал