Хабрастатистика: исследуем наиболее и наименее посещаемые разделы сайта

    Привет, Хабр.

    В предыдущей части была проанализирована посещаемость Хабра по основным параметрам — количеству статей, их просмотрам и рейтингам. Однако вопрос популярности разделов сайта остался не рассмотренным. Стало интересно рассмотреть это более подробно, и найти самые популярные и самые непопулярные хабы. Наконец, я рассмотрю «geektimes-эффект» более подробно, и в завершении читатели получат новую подборку лучших статей по новым рейтингам.



    Кому интересно что получилось, продолжение под катом.

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

    Сбор данных


    В первой версии парсера учитывались лишь число просмотров, комментариев и рейтинг статей. Это уже неплохо, но не позволяет делать более сложные запросы. Пора проанализировать тематические разделы сайта, это позволит делать достаточно интересные исследования, например, посмотреть как менялась популярность раздела «С++» за несколько лет.

    Парсер статей был улучшен, теперь он возвращает хабы, к которым относится статья, а также ник автора и его рейтинг (тут тоже можно сделать много интересного, но это потом). Данные сохранены в csv-файле примерно такого вида:

    2018-12-18T12:43Z,https://habr.com/ru/post/433550/,"Мессенджер Slack — причины выбора, косяки при внедрении и особенности сервиса, облегчающие жизнь",votes:7,votesplus:8,votesmin:1,bookmarks:32,
    views:8300,comments:10,user:ReDisque,karma:5,subscribers:2,hubs:productpm+soft
    ...
    

    Получим список основных тематических хабов сайта.

    def get_as_str(link: str) -> Str:
        try:
            r = requests.get(link)
            return Str(r.text)
        except Exception as e:
            return Str("")
    
    def get_hubs():
        hubs = []
        for p in range(1, 12):
            page_html = get_as_str("https://habr.com/ru/hubs/page%d/" % p)
            # page_html = get_as_str("https://habr.com/ru/hubs/geektimes/page%d/" % p)  # Geektimes
            # page_html = get_as_str("https://habr.com/ru/hubs/develop/page%d/" % p)  # Develop
            # page_html = get_as_str("https://habr.com/ru/hubs/admin/page%d" % p)  # Admin
            for hub in page_html.split("media-obj media-obj_hub"):
                info = Str(hub).find_between('"https://habr.com/ru/hub', 'list-snippet__tags') 
                if "*</span>" in info:
                    hub_name = info.find_between('/', '/"')
                    if len(hub_name) > 0 and len(hub_name) < 32:
                        hubs.append(hub_name)
        print(hubs)

    Функция find_between и класс Str выделяют строку между двух тегов, я использовал их ранее. Тематические хабы отмечены "*", так что их легко выделить, можно также раскомментировать соответствующие строки, чтобы получить разделы других категорий.

    На выходе функции get_hubs получаем достаточно внушительный список, который сохраняем как dictionary. Специально привожу список целиком, чтобы можно было оценить его объем.

    hubs_profile = {'infosecurity', 'programming', 'webdev', 'python', 'sys_admin', 'it-infrastructure', 'devops', 'javascript', 'open_source', 'network_technologies', 'gamedev', 'cpp', 'machine_learning', 'pm', 'hr_management', 'linux', 'analysis_design', 'ui', 'net', 'hi', 'maths', 'mobile_dev', 'productpm', 'win_dev', 'it_testing', 'dev_management', 'algorithms', 'go', 'php', 'csharp', 'nix', 'data_visualization', 'web_testing', 's_admin', 'crazydev', 'data_mining', 'bigdata', 'c', 'java', 'usability', 'instant_messaging', 'gtd', 'system_programming', 'ios_dev', 'oop', 'nginx', 'kubernetes', 'sql', '3d_graphics', 'css', 'geo', 'image_processing', 'controllers', 'game_design', 'html5', 'community_management', 'electronics', 'android_dev', 'crypto', 'netdev', 'cisconetworks', 'db_admins', 'funcprog', 'wireless', 'dwh', 'linux_dev', 'assembler', 'reactjs', 'sales', 'microservices', 'search_technologies', 'compilers', 'virtualization', 'client_side_optimization', 'distributed_systems', 'api', 'media_management', 'complete_code', 'typescript', 'postgresql', 'rust', 'agile', 'refactoring', 'parallel_programming', 'mssql', 'game_promotion', 'robo_dev', 'reverse-engineering', 'web_analytics', 'unity', 'symfony', 'build_automation', 'swift', 'raspberrypi', 'web_design', 'kotlin', 'debug', 'pay_system', 'apps_design', 'git', 'shells', 'laravel', 'mobile_testing', 'openstreetmap', 'lua', 'vs', 'yii', 'sport_programming', 'service_desk', 'itstandarts', 'nodejs', 'data_warehouse', 'ctf', 'erp', 'video', 'mobileanalytics', 'ipv6', 'virus', 'crm', 'backup', 'mesh_networking', 'cad_cam', 'patents', 'cloud_computing', 'growthhacking', 'iot_dev', 'server_side_optimization', 'latex', 'natural_language_processing', 'scala', 'unreal_engine', 'mongodb', 'delphi',  'industrial_control_system', 'r', 'fpga', 'oracle', 'arduino', 'magento', 'ruby', 'nosql', 'flutter', 'xml', 'apache', 'sveltejs', 'devmail', 'ecommerce_development', 'opendata', 'Hadoop', 'yandex_api', 'game_monetization', 'ror', 'graph_design', 'scada', 'mobile_monetization', 'sqlite', 'accessibility', 'saas', 'helpdesk', 'matlab', 'julia', 'aws', 'data_recovery', 'erlang', 'angular', 'osx_dev', 'dns', 'dart', 'vector_graphics', 'asp', 'domains', 'cvs', 'asterisk', 'iis', 'it_monetization', 'localization', 'objectivec', 'IPFS', 'jquery', 'lisp', 'arvrdev', 'powershell', 'd', 'conversion', 'animation', 'webgl', 'wordpress', 'elm', 'qt_software', 'google_api', 'groovy_grails', 'Sailfish_dev', 'Atlassian', 'desktop_environment', 'game_testing', 'mysql', 'ecm', 'cms', 'Xamarin', 'haskell', 'prototyping', 'sw', 'django', 'gradle', 'billing', 'tdd', 'openshift', 'canvas', 'map_api', 'vuejs', 'data_compression', 'tizen_dev', 'iptv', 'mono', 'labview', 'perl', 'AJAX', 'ms_access', 'gpgpu', 'infolust', 'microformats', 'facebook_api', 'vba', 'twitter_api', 'twisted', 'phalcon', 'joomla', 'action_script', 'flex', 'gtk', 'meteorjs', 'iconoskaz', 'cobol', 'cocoa', 'fortran', 'uml', 'codeigniter', 'prolog', 'mercurial', 'drupal', 'wp_dev', 'smallbasic', 'webassembly', 'cubrid', 'fido', 'bada_dev', 'cgi', 'extjs', 'zend_framework', 'typography', 'UEFI', 'geo_systems', 'vim', 'creative_commons', 'modx', 'derbyjs', 'xcode', 'greasemonkey', 'i2p', 'flash_platform', 'coffeescript', 'fsharp', 'clojure', 'puppet', 'forth', 'processing_lang', 'firebird', 'javame_dev', 'cakephp', 'google_cloud_vision_api', 'kohanaphp', 'elixirphoenix', 'eclipse', 'xslt', 'smalltalk', 'googlecloud', 'gae', 'mootools', 'emacs', 'flask', 'gwt', 'web_monetization', 'circuit-design', 'office365dev', 'haxe', 'doctrine', 'typo3', 'regex', 'solidity', 'brainfuck', 'sphinx', 'san', 'vk_api', 'ecommerce'}
    

    Для сравнения, разделы geektimes выглядят скромнее:

    hubs_gt = {'popular_science', 'history', 'soft', 'lifehacks', 'health', 'finance', 'artificial_intelligence', 'itcompanies', 'DIY', 'energy', 'transport', 'gadgets', 'social_networks', 'space', 'futurenow', 'it_bigraphy', 'antikvariat', 'games', 'hardware', 'learning_languages', 'urban', 'brain', 'internet_of_things', 'easyelectronics', 'cellular', 'physics', 'cryptocurrency', 'interviews', 'biotech', 'network_hardware', 'autogadgets', 'lasers', 'sound', 'home_automation', 'smartphones', 'statistics', 'robot', 'cpu', 'video_tech', 'Ecology', 'presentation', 'desktops', 'wearable_electronics', 'quantum', 'notebooks', 'cyberpunk', 'Peripheral', 'demoscene', 'copyright', 'astronomy', 'arvr', 'medgadgets', '3d-printers', 'Chemistry', 'storages', 'sci-fi', 'logic_games', 'office', 'tablets', 'displays', 'video_conferencing', 'videocards', 'photo', 'multicopters', 'supercomputers', 'telemedicine', 'cybersport', 'nano', 'crowdsourcing', 'infographics'}
    

    Аналогично были сохранены остальные хабы. Теперь несложно написать функцию, которая возвращает результат, относится статья к geektimes или к профильному хабу.

    def is_geektimes(hubs: List) -> bool:
        return len(set(hubs) & hubs_gt) > 0
    
    def is_geektimes_only(hubs: List) -> bool:
        return is_geektimes(hubs) is True and is_profile(hubs) is False
    
    def is_profile(hubs: List) -> bool:
        return len(set(hubs) & hubs_profile) > 0
    

    Аналогичные функции были сделаны для других разделов («разработка», «администрирование» и пр).

    Обработка


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

    def to_list(s: str) -> List[str]:
        # "user:popular_science+astronomy" => [popular_science, astronomy]
        return s.split(':')[1].split('+')
    
    def to_date(dt: datetime) -> datetime.date:
        return dt.date()
    
    df = pd.read_csv("habr_2019.csv", sep=',', encoding='utf-8', error_bad_lines=True, quotechar='"', comment='#')
    dates = pd.to_datetime(df['datetime'], format='%Y-%m-%dT%H:%MZ')
    dates += datetime.timedelta(hours=3)
    df['date'] = dates.map(to_date, na_action=None)
    hubs = df["hubs"].map(to_list, na_action=None)
    df['hubs'] = hubs
    df['is_profile'] = hubs.map(is_profile, na_action=None)
    df['is_geektimes'] = hubs.map(is_geektimes, na_action=None)
    df['is_geektimes_only'] = hubs.map(is_geektimes_only, na_action=None)
    df['is_admin'] = hubs.map(is_admin, na_action=None)
    df['is_develop'] = hubs.map(is_develop, na_action=None)
    

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

    g = df.groupby(['date'])
    days_count = g.size().reset_index(name='counts')
    year_days = days_count['date'].values
    grouped = g.sum().reset_index()
    profile_per_day_avg = grouped['is_profile'].rolling(window=20, min_periods=1).mean()
    geektimes_per_day_avg = grouped['is_geektimes'].rolling(window=20, min_periods=1).mean()
    geektimesonly_per_day_avg = grouped['is_geektimes_only'].rolling(window=20, min_periods=1).mean()
    admin_per_day_avg = grouped['is_admin'].rolling(window=20, min_periods=1).mean()
    develop_per_day_avg = grouped['is_develop'].rolling(window=20, min_periods=1).mean()
    

    Выводим количество опубликованных статей с помощью Matplotlib:



    Я разделил в графике статьи «geektimes» и «geektimes only», т.к. статья может принадлежать к обеим разделам одновременно (например «DIY» + «микроконтроллеры» + «С++»). Обозначением «profile» я выделил профильные статьи сайта, хотя возможно, английский термин profile для этого не совсем верный.

    В предыдущей части спрашивали про «geektimes-эффект», связанный с изменением правил оплаты статей для geektimes с этого лета. Выведем отдельно статьи geektimes:

    df_gt = df[(df['is_geektimes_only'] == True)]
    group_gt = df_gt.groupby(['date'])
    days_count_gt = group_gt.size().reset_index(name='counts')
    grouped = group_gt.sum().reset_index()
    year_days_gt = days_count_gt['date'].values
    view_gt_per_day_avg = grouped['views'].rolling(window=20, min_periods=1).mean()
    

    Результат интересный. Примерное соотношение просмотров статей geektimes к общему где-то 1:5. Но если общее число просмотров заметно колебалось, то просмотр «развлекательных» статей держался примерно на одном уровне.



    Также можно заметить, что общее число просмотров статей раздела «geektimes» после изменения правил все же упало, но «на глаз», не больше чем на 5% от общих значений.

    Интересно посмотреть среднее число просмотров на статью:



    Для «развлекательных» статей оно примерно на 40% выше среднего. Наверно это неудивительно. Провал в начале апреля мне непонятен, может так и было, или это какая-то ошибка парсинга, а может кто-то из авторов geektimes ушел в отпуск ;).

    Кстати, на графике видны еще два заметных пика числа просмотров статей — новогодние и майские праздники.

    Хабы


    Перейдем к обещанному анализу хабов. Выведем топ 20 хабов по числу просмотров:

    hubs_info = []
    for hub_name in hubs_all:
        mask = df['hubs'].apply(lambda x: hub_name in x)
        df_hub = df[mask]
    
        count, views = df_hub.shape[0], df_hub['views'].sum()
        hubs_info.append((hub_name, count, views))
    
    # Draw hubs
    hubs_top = sorted(hubs_info, key=lambda v: v[2], reverse=True)[:20]
    top_views = list(map(lambda x: x[2], hubs_top))
    top_names = list(map(lambda x: x[0], hubs_top))
    
    plt.rcParams["figure.figsize"] = (8, 6)
    plt.bar(range(0, len(top_views)), top_views)
    plt.xticks(range(0, len(top_names)), top_names, rotation=90)
    plt.ticklabel_format(style='plain', axis='y')
    plt.tight_layout()
    plt.show()
    

    Результат:



    На удивление, самым популярным по просмотрам оказался хаб «Информационная безопасность», также в топ-5 лидеров входят «Программирование» и «Popular science».

    Антитоп занимает Gtk и Cocoa.



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

    Рейтинг


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

    Информационная безопасность


    Программирование


    Научно-популярное


    Карьера


    Законодательство в IT


    Веб-девелопмент


    GTK

    И наконец, чтобы никому не было обидно, приведу рейтинг самого малопосещаемого хаба «gtk». В нем за год была опубликована одна статья, она же «автоматом» занимает первую строчку рейтинга.


    Заключение


    Заключения не будет. Всем приятного чтения.

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 5 065 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      –9
      Хабр давно уже дохлый сайт.
        +1

        Вполне себе бодрый. Куча читателей и статей. ИМХО, проблема не в уменьшении активности на сайте, а в смещении тематики сайта. Раньше хабр был интересен только программистам и гикам. Теперь значительно большему кругу лиц. Контент неизбежно становился более попсовым. Это приводит к вымыванию профильной аудитории, что делает контент еще попсовей. Система с положительной обратной связью.

          0
          Раньше хабр был интересен только программистам и гикам. Теперь значительно большему кругу лиц.

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

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

          А так да, нельзя объять необъятное. Уже сейчас 50 статей на сайте в день это больше, чем я как читатель могу прочитать, 80% информации просто пропускается.
            0

            "зато есть надежда что будет больше информации именно IT-содержания".
            Лично у меня, нет такой надежды. Сравните активность в профильных хабах, типа C++, C#, JS в году так 2013 и сейчас. Если есть желание и время, сравните по данным хабам число статей, их рейтинг, количество и средний рейтинг комментариев к статьям, число добавления в закладки. Я практически уверен, что станет грустно. Я хорошо помню какая в этих хабах была движуха в 2013. Сейчас такого нет даже отдаленно.

              0
              Я сначала относился негативно к смене правил оплаты, а сейчас посмотрев статистику, думаю, что здравый смысл в этом есть

              Смена правил оплаты действительно была связана только с желанием сделать больший акцент на технических статьях, как это изначально было на Хабре, до введения ППА (что не исключает того, что в «гиктаймс-хабах» может оказаться что-то техническое-претехническое).

              Уже сейчас 50 статей на сайте в день это больше, чем я как читатель могу прочитать

              Постараемся скоро немного «почистить» ленту (без ущерба для авторов статей и читателей).
                0
                С лентой все сложнее имхо. Если просто сократить ленту, то статей в первую страницу будет попадать еще меньше, и средний рейтинг еще больше упадет. В идеале нужна какая-то «интеллектуальная» система, формирующая список статей для каждого читателя по его истории просмотров, примерно как в youtube делается. Тогда такую ленту будет более интересно читать.
                  +1
                  Не, речь не про сокращение ленты — подумываем над тем, чтобы вынести все мероприятия в отдельный раздел (как это было с новостям). Впрочем, так давно было на Хабре :) Пример с новостями показывает, что это вполне хороший вариант — сейчас новости смотрят и обсуждают, но при этом они не вытесняют лонгриды в ленте.

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

                  Похоже на win-win? :)

                  А насчёт «интеллектуальности» — это давно напрашивается, но мы проводили опрос и пользователи проголосовали за «хронологический порядок», который, по всей видимости, им проще контролировать. Ведь если зайти в тот же вконтакт/фейсбук/ютуб, сразу непонятно, какой пласт контента ты упустил с момента последнего входа )
                    0
                    Хронологический порядок это нормально, я другое имел в виду.

                    У меня по умолчанию включен режим «Лучшие статьи», т.е. я и так вижу примерно 20% всех публикаций, так что 80% я и так упускаю по-любому (режим «все подряд» я не использую, т.к. там слишком много уже).

                    Так вот, хотелось бы чтобы режим «лучшие» показывал результаты с учетом моей истории просмотра. Если например, мне неинтересна Java, то и статьи по ней я читать не буду даже если в ленте они есть, а статью про Python я с большей вероятностью открою.

                    Не знаю, понятно ли объяснил. Понятно что можно вручную настроить подписки на хабы, но лень заморачиваться, лучше чтобы оно все «само» формировалось :)
                      +1
                      Да, смысл понятен, интересно — передам коллегам для обсуждения.
                      cc karaboz
                        0
                        Я уже как-то писал, повторюсь.
                        Если например, мне неинтересна Java, то и статьи по ней я читать не буду даже если в ленте они есть, а статью про Python я с большей вероятностью открою.

                        Более простым, быстрым решением «здесь и сейчас» может стать добавление т.н. «черного списка» в который можно будет закинуть определенные хабы.
                        Исходя из личного опыта просмотра ленты, даже если статья интересная сама по себе (например про алгоритмы), но в качестве примера используется java, то скорей всего я эту статью читать не буду (1-2 из 10 в лучшем случае и то не факт). Но ради этих же 1-2 статей которые я возможно бы прочитал добавлять в ленту 10 статей — как по мне выглядит избыточно и больше похоже на инфомусор(если можно так сказать, не в обижу джавистам) в ленте лично для меня.
                        0

                        Есть подписки на хабы и авторов. Лично меня вполне устраивает. Все, кроме качества контента в этих подписках

                          +1
                          Это же саморегулирующаяся система с обратной связью. Хабр имеет очевидную «науч.поп» направленность, какие-то серьезные узкоспециализированные статьи со сложным кодом здесь имхо просто «не идут», не набирают ни рейтинга, ни просмотров. Я сам после анализа последних рейтингов зарекся выкладывать сюда что-то сложное, смысла нет — работа впустую.
                            +1
                            какие-то серьезные узкоспециализированные статьи со сложным кодом здесь имхо просто «не идут», не набирают ни рейтинга, ни просмотров
                            До первого разделения это было не так. ИМХО, оно убило тот хабр, который стимулировал меня на статью. «Гики» заходили сюда почитать научпоп и пописать технохардкор. Всем пользователям это было выгодно в плане разнообразия и качества контента. После разделения научпоп переехал. Гикам стало не интересно на хабре, т.к. они туда ходили за научпопом, а авторам качественного научпопа на гиктаймсе, из-за изменений в ППА и увеличения средней попсовости ресурса (трудозатраты на качественный научпоп перестали окупаться аудиторией). Дальнейшее объединение уже не могло вернуть ресурс на прежнюю точку локального равновесия.
                            выкладывать сюда что-то сложное, смысла нет — работа впустую
                            Соглашусь, это подтверждает мою гипотезу выше. Поэтому самое интересное, что встречается здесь последнее время, это хорошие технические переводы, а качественного уникального контента все меньше.
                              +1
                              Есть еще такой момент, что все новые и полезные статьи по программированию в основном на английском, вся аудитория соответственно, там же. Русскоязычные статьи по программированию актуальны больше для джуниоров имхо.
                                0

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

                                  0
                                  Ну, хабр таки не благотворительная организация, а вполне себе коммерческий сайт, живущий за счет рекламы и корпоративных блогов. Мало просмотров, нет поощрений, нет статей, еще раз повторю мысль про обратную связь :) Чтобы к примеру, увеличилось число статей про Python или С++, нужно как-то изменять рейтинги по ним, правила попадания в топ и пр. Надо ли это кому-то, я не знаю.
                              0
                              серьезные узкоспециализированные статьи со сложным кодом здесь имхо просто «не идут»

                              Если бы их не минусовали (про причины не будем рассуждать), то статей, мне кажется, было бы больше… Ведь ЛЮБОЙ труд нужно поощрять, ну хотя бы не мешать.

                                0
                                А если там откровенная ересь, тоже не минусовать? Тогда чем это поможет хабру и его читателям?
              0

              А еще в одной из пятничных прямых линий с ТМ (когда-то за последние полгода) даже приводился официальный график и явно говорилось, что посещение в летние месяцы (по вполне очевидным причинам :) в среднем несколько проседает. :)

                0
                Да, интересно что в новогодние праздники строго наоборот — среднее число просмотров на статью растет. Хотя и статей в праздники публикуется меньше.

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

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