Habrastatistics: explorando as seções mais e menos visitadas do site

Olá, Habr.

В parte anterior O tráfego de Habr foi analisado de acordo com os principais parâmetros - número de artigos, suas visualizações e classificações. No entanto, a questão da popularidade das seções do site permaneceu sem ser examinada. Tornou-se interessante observar isso com mais detalhes e encontrar os hubs mais populares e mais impopulares. Por fim, examinarei o efeito geektimes com mais detalhes, terminando com uma nova seleção dos melhores artigos com base em novas classificações.

Habrastatistics: explorando as seções mais e menos visitadas do site

Para quem se interessa pelo ocorrido, a continuação fica em segundo plano.

Deixe-me lembrar mais uma vez que as estatísticas e classificações não são oficiais, não tenho nenhuma informação privilegiada. Também não é garantido que não cometi um erro em algum lugar ou perdi alguma coisa. Mas ainda assim, acho que ficou interessante. Começaremos primeiro com o código; quem não estiver interessado nisso pode pular as primeiras seções.

Coleta de dados

Na primeira versão do analisador foram considerados apenas o número de visualizações, comentários e avaliações dos artigos. Isso já é bom, mas não permite fazer consultas mais complexas. É hora de analisar as seções temáticas do site; isso permitirá que você faça pesquisas bastante interessantes, por exemplo, veja como a popularidade da seção “C++” mudou ao longo de vários anos.

O analisador de artigos foi melhorado, agora retorna os hubs aos quais o artigo pertence, bem como o apelido do autor e sua avaliação (muitas coisas interessantes podem ser feitas aqui também, mas isso virá mais tarde). Os dados são salvos em um arquivo csv parecido com este:

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
...

Receberemos uma lista dos principais pólos temáticos do site.

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)

A função find_between e a classe Str selecionam uma string entre duas tags, eu as usei mais cedo. Os hubs temáticos são marcados com um “*” para que possam ser facilmente destacados, e você também pode descomentar as linhas correspondentes para obter seções de outras categorias.

A saída da função get_hubs é uma lista bastante impressionante, que salvamos como um dicionário. Estou apresentando especificamente a lista na íntegra para que vocês possam estimar seu volume.

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'}

Para efeito de comparação, as seções geektimes parecem mais modestas:

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'}

Os demais hubs foram preservados da mesma forma. Agora é fácil escrever uma função que retorne o resultado quer o artigo pertença ao geektimes ou a um hub de perfil.

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

Funções semelhantes foram feitas para outras seções (“desenvolvimento”, “administração”, etc.).

Processamento

É hora de começar a analisar. Carregamos o conjunto de dados e processamos os dados do hub.

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)

Agora podemos agrupar os dados por dia e exibir o número de publicações para diferentes hubs.

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()

Exibimos o número de artigos publicados usando Matplotlib:

Habrastatistics: explorando as seções mais e menos visitadas do site

Dividi os artigos “geektimes” e “geektimes only” no gráfico, pois Um artigo pode pertencer a ambas as seções ao mesmo tempo (por exemplo, “DIY” + “microcontroladores” + “C++”). Usei a designação “perfil” para destacar artigos de perfil no site, embora talvez o termo inglês profile para isso não seja totalmente correto.

Na parte anterior questionámos sobre o “efeito geektimes” associado à alteração das regras de pagamento de artigos para geektimes a partir deste verão. Vamos exibir os artigos do geektimes separadamente:

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()

O resultado é interessante. A proporção aproximada de visualizações dos artigos do geektimes em relação ao total é algo em torno de 1:5. Mas embora o número total de visualizações tenha flutuado visivelmente, a visualização de artigos de “entretenimento” permaneceu aproximadamente no mesmo nível.

Habrastatistics: explorando as seções mais e menos visitadas do site

Você também pode notar que o número total de visualizações de artigos da seção “geektimes” ainda caiu após a mudança nas regras, mas “a olho”, em não mais que 5% dos valores totais.

É interessante observar a média de visualizações por artigo:

Habrastatistics: explorando as seções mais e menos visitadas do site

Para artigos de “entretenimento” está cerca de 40% acima da média. Isto provavelmente não é surpreendente. A falha no início de abril não está clara para mim, talvez tenha sido isso que aconteceu, ou seja algum tipo de erro de análise, ou talvez um dos autores do Geektimes tenha saído de férias;).

A propósito, o gráfico mostra mais dois picos perceptíveis no número de visualizações de artigos - os feriados de Ano Novo e de maio.

Hubs

Passemos à prometida análise dos hubs. Vamos listar os 20 principais hubs por número de visualizações:

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()

Resultado:

Habrastatistics: explorando as seções mais e menos visitadas do site

Surpreendentemente, o centro mais popular em termos de pontos de vista foi “Segurança da Informação”; os 5 principais líderes também incluíram “Programação” e “Ciência Popular”.

Antitop ocupa Gtk e Cocoa.

Habrastatistics: explorando as seções mais e menos visitadas do site

Vou te contar um segredo, os principais hubs também podem ser vistos aqui, embora o número de visualizações não seja mostrado lá.

Classificação

E, finalmente, a classificação prometida. Usando dados de análise de hub, podemos exibir os artigos mais populares dos hubs mais populares deste ano de 2019.

Segurança da informação

Programação

Ciência popular

carreira

Legislação em TI

desenvolvimento web

GTK

E por último, para que ninguém se ofenda, darei a classificação do hub menos visitado “gtk”. Em um ano foi publicado um O artigo, que também ocupa “automaticamente” a primeira linha da classificação.

Conclusão

Não haverá conclusão. Boa leitura a todos.

Fonte: habr.com

Adicionar um comentário