Жизненный цикл статьи на Хабре: пишем хабрапарсер

    Привет Хабр!

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



    Для сбора статистики воспользуемся Python, Pandas, Matplotlib и Raspberry Pi.

    Тех кому интересно, что из этого получилось, прошу под кат.

    Сбор данных


    Для начала определимся с метриками — что мы хотим узнать. Тут все просто, у каждой статьи есть 4 основных параметра, отображаемых на странице — это количество просмотров, лайков, закладок и комментариев. Их мы и будем анализировать.

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

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

    link = "https://habr.com/ru/post/000001/"
    f = urllib.urlopen(link)
    data_str = f.read()
    

    Теперь из строки data_str (она разумеется, в HTML) нужно извлечь данные. Открываем в браузере исходный код (непринципиальные элементы убраны):

    <ul class="post-stats post-stats_post js-user_" id="infopanel_post_438514">
            <li class="post-stats__item post-stats__item_voting-wjt">
                <span class="voting-wjt__counter voting-wjt__counter_positive  js-score" title="Общий рейтинг 448: ↑434 и ↓14">+420</span>
            </li>
    
            <span class="btn_inner"><svg class="icon-svg_bookmark" width="10" height="16"><use xlink:href="https://habr.com/images/1550155671/common-svg-sprite.svg#book" /></svg><span class="bookmark__counter js-favs_count" title="Количество пользователей, добавивших публикацию в закладки">320</span></span>
    
        <li class="post-stats__item post-stats__item_views">
          <div class="post-stats__views" title="Количество просмотров">
            <span class="post-stats__views-count">219k</span>
          </div>
        </li>
    
          <li class="post-stats__item post-stats__item_comments">
              <a href="https://habr.com/ru/post/438514/#comments" class="post-stats__comments-link"                     <span class="post-stats__comments-count" title="Читать комментарии">577</span>
              </a>
          </li>
    
              <li class="post-stats__item">
    <span class="icon-svg_report"><svg class="icon-svg" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true" version="1.1" role="img"><path d="M0 0h32v32h-32v-32zm14 6v12h4v-12h-4zm0 16v4h4v-4h-4z"/></svg>
    </span>
              </li>
     </ul>
    

    Нетрудно видеть, что нужный нам текст находится внутри блока '<ul class=«post-stats post-stats_post js-user_>', а нужные элементы находятся в блоках c названиями voting-wjt__counter, bookmark__counter, post-stats__views-count и post-stats__comments-count. По названиям все вполне очевидно.

    Наследуем класс str и добавим в него метод извлечения подстроки, находящейся между двух тегов:

    class Str(str):
        def find_between(self, first, last):
            try:
                start = self.index(first) + len(first)
                end = self.index(last, start)
                return Str(self[start:end])
            except ValueError:
                return Str("")
    

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

    votes = data_str.find_between('span class="voting-wjt__counter voting-wjt__counter_positive  js-score"', 'span').find_between('>', '<')
    
    bookmarks = data_str.find_between('span class="bookmark__counter js-favs_count"', 'span').find_between('>', '<')
    
    views = data_str.find_between('span class="post-stats__views-count"', 'span').find_between('>', '<')
    
    comments = data_str.find_between('span class="post-stats__comments-count"', 'span').find_between('>', '<')
    

    Но это еще не все. Как можно видеть, число комментариев или просмотров может храниться в виде строки типа „12.1k“, которая напрямую в int не переводится.

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

    def to_int(self):
        s = self.lower().replace(",", ".")
        if s[-1:] == "k":
            # "1.23k" => 1.23*1000 => 1230
            return int(1000*float(s.replace("k", "")))
        return int(self)
    

    Осталось только добавить timestamp, и можно сохранять данные в csv:

    timestamp = strftime("%Y-%m-%dT%H:%M:%S.000", gmtime())
    str_out = "{},votes:{},bookmarks:{},views:{},comments:{};".format(timestamp, votes.to_int(), bookmarks.to_int(), views.to_int(), comments.to_int())
    

    Поскольку нам интересно проанализировать несколько статей, добавим возможность указания ссылки через командную строку. Также будем формировать имя файла лога по ID статьи:

    link = sys.argv[1]  # "https://habr.com/ru/post/000001/"
    link_path = urlparse.urlparse(link.strip("/")).path  # https://habr.com/ru/post/000001/ => /ru/post/000001
    link_parts = os.path.split(link_path)  # /ru/post/000001=> ('/ru/post', '000001')
    log_file = "habr_log%s.txt" % link_parts[1]
    

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

    delay_s = 5*60
    
    while True:
        # Get data
        data_str = get_as_str(link)
        data_csv = extract_as_csv(data_str)
        print data_csv
    
        # Append data to log
        with open(log_file, "a") as logfile:
            logfile.write(data_csv + '\n')
    
        sleep(delay_s)
    

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

    Далее, желательно сохранить данные, хотя бы за несколько дней. Т.к. держать компьютер включенным несколько дней неохота, берем Raspberry Pi — его мощности для такой задачи без проблем хватит, да и в отличие от ПК, Raspberry Pi не шумит и почти не потребляет электроэнергии. Заходим по SSH и запускаем наш скрипт:

    nohup python habr_parse.py https://habr.com/ru/post/0000001/  &
    

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

    Как бонус, можно запустить в фоне http-сервер, введя команду „nuhup python -m SimpleHTTPServer 8000 &“. Это позволит смотреть результаты прямо в браузере в любой момент, открывая ссылку вида http://192.168.1.101:8000 (адрес разумеется может быть другим).

    Теперь можно оставить Raspberry Pi включенным, и вернуться к проекту через несколько дней.

    Анализ данных


    Если все было сделано правильно, то на выходе должен быть примерно такой лог:

    2019-02-12T22:26:28.000,votes:12,bookmarks:0,views:448,comments:1;
    2019-02-12T22:31:29.000,votes:12,bookmarks:0,views:467,comments:1;
    2019-02-12T22:36:30.000,votes:14,bookmarks:1,views:482,comments:1;
    2019-02-12T22:41:30.000,votes:14,bookmarks:2,views:497,comments:1;
    2019-02-12T22:46:31.000,votes:14,bookmarks:2,views:513,comments:1;
    2019-02-12T22:51:32.000,votes:14,bookmarks:2,views:527,comments:1;
    2019-02-12T22:56:32.000,votes:14,bookmarks:2,views:543,comments:1;
    2019-02-12T23:01:33.000,votes:14,bookmarks:2,views:557,comments:2;
    2019-02-12T23:06:34.000,votes:14,bookmarks:2,views:567,comments:3;
    2019-02-12T23:11:35.000,votes:13,bookmarks:2,views:590,comments:4;
    ...
    2019-02-13T02:47:03.000,votes:15,bookmarks:3,views:1100,comments:20;
    2019-02-13T02:52:04.000,votes:15,bookmarks:3,views:1200,comments:20;
    

    Посмотрим, как его можно обработать. Для начала, загрузим csv в pandas dataframe:

    import pandas as pd
    import numpy as np
    import datetime
    
    log_path = "habr_data.txt"
    df = pd.read_csv(log_path, header=None, names=['datetime', 'votes', 'bookmarks', 'views', 'comments'])
    

    Добавим функции для конвертации и усреднения, и извлечем необходимые данные:

    def to_float(s):
        # "bookmarks:22" => 22.0
        num = ''.join(i for i in s if i.isdigit())
        return float(num)
    
    def running_mean(l, N=2):
        sum = 0
        result = len(l)*[0]
        for i in range(0, N):
            sum = sum + l[i]
            result[i] = sum / (i + 1)
        for i in range(N, len(l)):
            sum = sum - l[i - N] + l[i]
            result[i] = sum / N
        return result
    
    log_path = "habr_data.txt"
    df = pd.read_csv(log_path, header=None, names=['datetime', 'votes', 'bookmarks', 'views', 'comments'])
    print df.head()
    
    dates = pd.to_datetime(df['datetime'], format='%Y-%m-%dT%H:%M:%S.%f')
    dates += datetime.timedelta(hours=3)
    
    views = df["views"].map(to_float, na_action=None)
    views_avg = running_mean(views.values.tolist())
    votes = df["votes"].map(to_float, na_action=None)
    bookmarks = df["bookmarks"].map(to_float, na_action=None)
    comments = df["comments"].map(to_float, na_action=None)
    viewspervotes = views/votes
    viewsperbookmarks = views/bookmarks
    

    Усреднение нужно потому, что число просмотров на сайте выводится с шагом 100, что приводит к „рваному“ графику. В принципе это не обязательно, но с усреднением смотрится лучше. В коде также прибавляется московский часовой пояс (время на Raspberry Pi оказалось в GMT).

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

    import matplotlib.pyplot as plt
    
    # Draw
    fig, ax = plt.subplots()
    # plt.plot(dates, votes, 'ro', markersize=1, label='Votes')
    # plt.plot(dates, bookmarks, 'go', markersize=1, label='Bookmarks')
    # plt.plot(dates, comments, 'go', markersize=1, label='Comments')
    ax.plot(dates, views_avg, 'bo', markersize=1, label='Views')
    # plt.plot(dates, views_g, 'bo', markersize=1, label='Views')
    # plt.plot(dates, viewspervotes, 'ro', markersize=1, label='Views/Votes')
    # plt.plot(dates, viewsperbookmarks, 'go', markersize=1, label='Views/Bookmarks')
    ax.xaxis.set_major_formatter(mdates.DateFormatter("%d-%d-%Y"))
    ax.xaxis.set_major_locator(mdates.DayLocator())
    ax.xaxis.set_minor_locator(mdates.HourLocator(interval=1))
    fig.autofmt_xdate()
    plt.legend(loc='best')
    plt.xticks(rotation=45, ha="right")
    plt.tight_layout()
    plt.show()
    

    Результаты


    В начале каждого графика есть пустое место, которое объясняется просто — когда скрипт запускался, статьи уже были опубликованы, так что данные собирались не с нуля. „Нулевая“ точка была добавлена вручную из описания времени публикации статьи.

    Все выложенные графики сгенерированы matplotlib и вышеприведенным кодом.

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

    «Горячая» статья


    Это статья на какую-то популярную и актуальную тему, с названием вроде „Как МТС списывает деньги“ или „Роскомнадзор заблокировал porngit hub“.

    Такие статьи имеют большое число просмотров и комментариев, но „ажиотаж“ длится максимум несколько дней. Также видна небольшая разница в росте числа просмотров в дневное и ночное время (но не столь значительная как ожидалось — видимо, Хабр читают практически со всех часовых поясов).





    Число „лайков“ растет довольно значительно, число закладок при этом растет заметно медленнее. Это логично, т.к. статья может кому-то нравиться, но специфика текста такова, что для добавления в закладки она просто не нужна.



    Cоотношение просмотров и лайков сохраняется примерно одинаковым и составляет грубо, 400:1:



    «Техническая» статья


    Это более узкоспециализированная статья, типа „Настройка скриптов для Node JS“. Такая статья разумеется, набирает в разы меньше просмотров чем „горячая“, число комментариев также заметно меньше (в данном случае их оказалось всего 4).





    А вот следующий момент интереснее: число „лайков“ для таких статей растет заметно медленнее, чем число „закладок“. Тут все наоборот по сравнению с предыдущим вариантом — многие находят статью полезной, чтобы сохранить на будущее, но при этом читатель вовсе не обязательно нажмет „лайк“.



    Кстати, на этот момент хотелось бы обратить внимание администраторов сайта — при расчете рейтингов статей стоит засчитывать „закладки“ параллельно с „лайками“ (например объединяя множества по „ИЛИ“). В противном случае, это приводит к перекосу в рейтинге, когда заведомо хорошая статья имеет много добавлений в закладки (т.е. читателям она определенно понравилась), но эти люди забыли или поленились нажать „лайк“.

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



    «Подозрительная» статья (но это не точно)


    Для следующей рассмотренной статьи число „лайков“ выросло на треть за 5-минутный интервал (сразу на 10 при общих набранных 30 за все несколько дней).



    Можно было бы заподозрить накрутку, но „теория массового обслуживания“ в принципе допускает такие всплески. А может автор просто разослал ссылку всем своим 10 знакомым, что разумеется, не запрещено правилами.

    Выводы


    Главный вывод — все есть тлен и майя. Даже самый популярный материал, набирающий тысячи просмотров, уйдет „в прошлое“ всего за 3-4 дня. Такова увы, специфика современного интернета, и наверно, всей современной медийной индустрии в целом. И уверен, показанные цифры специфичны не только для Хабра, но и для любого аналогичного интернет-ресурса.

    В остальном, данный анализ имеет скорее „пятничный“ характер, и разумеется, не претендует на серьезное исследование. Также надеюсь кто-то нашел что-либо новое в использовании Pandas и Matplotlib.

    Спасибо за внимание.
    Поделиться публикацией

    Комментарии 30

      +5
      Интересное исследование! Но, мне кажется, вам надо было сделать еще кое-что, а именно:

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

      Было бы интересно соотнести позицию на главной, нахождение в блоках с приростом просмотров.

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

      Что касается соотношения лайков к просмотрам, думаю, тут еще может играть роль, какие пользователи её читают — зарегистрированные с способностью ставить лайки или больше незарегистрированные.
        +2
        Хорошая идея, можно было добавлять в лог факт наличия статьи в боковом или нижнем блоке, и посмотреть как это влияет на просмотры.

        Впрочем, каких-то необъяснимых всплесков на графиках и так не видно (хотя конечно _все_ статьи я не анализировал, меня бы забанили за DDOS-атаку если бы я выкачивал всё каждые 5 минут;).
          +1
          То есть число просмотров на мой взгляд, сильно зависит от видимости статьи (вероятности пользователя наткнуться на неё). Если вы, условно говоря, опубликовали статью, и через 5 минут люди опубликовали еще 20 статей, то ваша статья провалится вниз и никто ее не прочтет.
          Да. И более того, если опубликована «Техническая» статья, а близко по времени «Горячая» статья, то последняя может увести потенциальных читателей — зависит от размера и количиства комментов.
          +1
          Еще интересные вопросы:

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

          Даже самый популярный материал, набирающий тысячи просмотров, уйдет „в прошлое“ всего за 3-4 дня.
          Для «Горячих» статей это ожидаемо — сенсационность и актуальность могут быстро упасть. А для «Технических» статей это выглядит более странно. ИМХО можно объяснить тем, что большинству материал не сильно актуален, а читают и берут в закладки «на всякий случай — м.б. потом будет нужен».

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

          Интересно, как влияют споры и холивары в обсуждении на показатели статьи? М.б. один случай, когда комментаторы выскажутся по 1 разу (кто похвалит, кто обругает), и другой случай, когда несколько человек спорят на много экранов.
            +4
            Какой-то процент переходов из поисковых систем остается, и для старых статей, так что число просмотров слегка будет расти. Но это доли процентов имхо.

            Холивары на показатели имхо почти не влияют — голосуют-то только один раз. Но тут есть другой момент, 'горячая' статья с 100к просмотров наберет гораздо больший рейтинг чем узкоспециализированная с 5к просмотров, даже если ее качество (views/likes) ниже, чисто за счет большого числа прочитавших. Что поделать, жизнь неидеальна. Ну и Хабр все же развлекательный ресурс а не научный журнал.
              0
              Cпасибо за статью, хороший пример результата работы неспокойной головы и правильных рук.)
              Ну и Хабр все же развлекательный ресурс а не научный журнал.

              Я бы сказал, развлекательный + научный = образовательный.
              ошибка в тексте?
              Cоотношение просмотров и лайков… 400:1

              можно видеть, что оно заметно выше чем в первом варианте и составляет грубо, 150:1

              150:1 > 400:1?
                +1
                Спасибо.

                В тексте нет ошибки, под 150:1 и 400:1 я имел в виду 1 лайк на 150 просмотров, что больше чем 1 лайк на 400 просмотров. Может не очень удачно сформулировал, но идея надеюсь, ясна.
              +1
              Отвечу вам скорее как бывший автор статей, ну и как наблюдатель, конечно.

              Если статья получит продолжение в другой статье, то сильно ли это повлият на показатели?
              Прирост есть, но незначительный — того же порядка, как были бы переходы на внешний сайт. Но для определённых «холиварных» материалов может достигать и 2-3 тыс. (Например, в прошлом году была серия про «необразованную молодёжь», там были большие приросты при отсылках. Та же история с любыми «сериальными» статьями). При отсылке к обычной технической статье прирост в рамках нескольких сотен просмотров, а то и меньше.
              Интересно, как влияют споры и холивары в обсуждении на показатели статьи?
              Количество комментариев — по наблюдению — чаще всего влияет, т.к.: а) все идут посмотреть, да что же там так комментируют; б) справедливо и обратное — если статья интересная или спорная, её часто просматривают, значит, может быть много комментариев. И, как правило, если комментов нет, то и просмотров довольно мало, думаю, если «снять» данные, корреляция будет высокая.
                +1
                У меня мои статьи просто пассивно получают около 400-500 просмотров ежедневно.
                  0
                  На некоторых форумах запрещена «археология», на Хабре такого запрета нет.
                  Археология немножко ограничена. Пользователи в статусе Read&Comment не могут комментировать публикации старше 30 дней. Как велика доля таких пользователей не могу сказать.
                  +3
                  А вот следующий момент интереснее: число „лайков“ для таких статей растет заметно медленнее, чем число „закладок“. Тут все наоборот по сравнению с предыдущим вариантом — многие находят статью полезной, чтобы сохранить на будущее, но при этом читатель вовсе не обязательно нажмет „лайк“.

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

                    0
                    Да, тоже вариант, спасибо. Но мне все же кажется, что мотивация тут первична — читатель в принципе, не обязан нажимать «лайк», а вот если статья понравилась и хочется ее сохранить, то в закладки себе человек ее скорее всего, положит.
                    0
                    Закладку можно и не на хабре сделать, а в браузере. Лайк или дизлайк большинство не могут поставить.
                    Я свои статьи публиковал в пятницу вечером, когда вероятность набрать просмотры минимальна, однако, так имелось время ответить на комменты в выходные. В понедельник в общем списке статью уже было сложно найти. Но, одна статья написанная более года назад стабильно держит около 200 просмотров каждый день. То есть, стабильно в течение года. При этом, в первые дни после публикации она набрала не так уж и много просмотров.
                      0

                      Мне кажется стоило считать не рейтинг, а общий рейтинг — общее количество плюсов и минусов. Ведь тут может быть не "забыли поставить оценку", а "ставили противоположные оценки".
                      Ну и почему рассмотрено только три статьи, почему так мало?

                        0
                        Рейтинг я и считал как сумму оценок — лайков и дизлайков.
                        Хотя сейчас посмотрел HTML-исходник еще раз — в нем хранится и количество «плюсов», и количество «минусов» отдельно в параметрах uarr и darr:
                        <span class="voting_wjt__counter voting_wjt__counter_positive  js-score" title="Общий рейтинг 46: uarr;31 и darr;15">+16</span>

                        Так что даже интереснее было бы вывести отдельно двумя графиками (для популярных статей было бы интересно сравнить рост лайков и дизлайков), но уже поздно переделывать :)
                        0
                        Для начала определимся с метриками — что мы хотим узнать. Тут все просто, у каждой статьи есть 4 основных параметра, отображаемых на странице — это количество просмотров, лайков, закладок и комментариев. Их мы и будем анализировать.


                        Тут есть тонкости
                        1. «лайки» могут ставить далеко не все.
                        2. Комментируют тоже далеко не все. Даже если человеку есть что сказать — комментирование (и ответы) занимает время, которое лишним не бывает. Тем более, что жизнь у комментария в подавляющем большинстве случая еще короче, чем у статьи.
                        3. «Закладки» я обычно сохраняю в OneNote — так намного удобнее (рубрики, поиск). Думаю, что не я один (и не обязательно в OneNote)
                        Кроме того, с учетом того факта, что статья может внезапно исчезнуть — иногда сохраняю и полный текст статьи.

                        Так что для начала надо бы провести опрос — кто, где и как часто.

                          0
                          Да, разумеется.

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

                          Насчет комментариев, я обычно не комментирую если вижу что их >50 — высока вероятность, что такую массу уже никто не будет заморачиваться читать.
                          +2
                          Могу я предложить визуализировать через seaborn? Тут круто бы смотрелся timeseries plot. Сразу видно распределение плотности вероятности. seaborn.pydata.org/examples/errorband_lineplots.html
                          image
                            0
                            Спасибо за идею, попробую.
                              0
                              Подобное можно сделать без seaborn: обычной линией и полупрозрачными fill_between с нужными квантилями, например
                                0
                                А этот seaborn кстати бесплатный? Я давно хотел его попробовать, но до реального использования так и не дошло, matplotlib в целом устраивает (единственный минус, на больших наборах точек он все же тормозит).
                                  0
                                  Вроде бесплатный. А на какого размера наборах matplotlib подтормаживает. У меня временнЫе данные с минутным разрешением длиной в неделю и рядов в 40 шириной довольно быстро рисует contourf. Вот когда большого размера изображения рисовать приходится, тогда задумывается
                                    0
                                    Я пробовал выводить несколько тыс точек с лидара, было что-то типа 1fps, или даже меньше, не помню уже.
                                  0

                                  Pandas хорошо интегрирован с seaborn. Реально удобнее и уже готовыми методами.

                                +1
                                Надо на хабре ввести сортировку по likes/views и bookmarks/views за определенный период времени для поиска качественных статей.
                                  0
                                  Если под running_mean имелось ввиду скользящее среднее, то у Pandas есть метод rolling специально для этого. Он и со временем работать умеет.
                                    0
                                    Спасибо, попробую. Я пробовал какой-то метод из numpy, но он меняет размер массива и matplotlib уже не рисует когда 2 массива разной длины, влом было разбираться, взял другую реализацию.
                                      0
                                      Для data wrangling pandas вообще очень хорош: и resample (up- и down-), и rolling mean, и статистика. Кстати, если я правильно понимаю, то весь datetime есть в pd.datetime, ну или многое во всяком случае
                                    +1
                                    Мне когда-то понравилось как обёртки в pandas преображают оси, на которых отложено время, как здесь
                                    image
                                    Но работать с графиками из самого pandas не всегда удобно, поэтому в несколько строк кода можно сделать подобное и в matplotlib:
                                    import matplotlib.dates as dates
                                    import matplotlib.pyplot as plt
                                    
                                    axis.set_minor_locator(dates.DayLocator())
                                    axis.set_minor_formatter(dates.DateFormatter('%d'))
                                    axis.set_major_locator(dates.MonthLocator())
                                    axis.set_major_formatter(dates.DateFormatter('%b\n%Y'))
                                    

                                    axis — нужная ось
                                      0
                                      А как распарсить html если страница через js дорисовывается?

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                      Самое читаемое