Habrastatistics: explora as seccións máis e menos visitadas do sitio

Ola Habr.

В parte anterior O tráfico de Habr analizouse segundo os principais parámetros: o número de artigos, as súas opinións e valoracións. Non obstante, a cuestión da popularidade das seccións do sitio permaneceu sen examinar. Fíxose interesante ver isto con máis detalle e atopar os centros máis populares e impopulares. Por último, verei o efecto geektimes con máis detalle, rematando cunha nova selección dos mellores artigos baseada en novas clasificacións.

Habrastatistics: explora as seccións máis e menos visitadas do sitio

Para os que estean interesados ​​no que pasou, a continuación está baixo o corte.

Permítanme recordarlle unha vez máis que as estatísticas e as valoracións non son oficiais, non teño información privilegiada. Tampouco está garantido que non cometín un erro nalgún lugar ou que me perdín algo. Pero aínda así, creo que resultou interesante. Comezaremos primeiro co código os que non estean interesados ​​poden saltar as primeiras seccións.

Recollida de datos

Na primeira versión do analizador, só se tivo en conta o número de visualizacións, comentarios e valoracións dos artigos. Isto xa é bo, pero non permite facer consultas máis complexas. É hora de analizar as seccións temáticas do sitio, isto permitirache facer unha investigación bastante interesante, por exemplo, ver como cambiou a popularidade da sección "C++" ao longo de varios anos.

Mellorouse o analizador de artigos, agora devolve os centros aos que pertence o artigo, así como o alcume do autor e a súa valoración (aquí tamén se poden facer moitas cousas interesantes, pero iso virá máis tarde). Os datos gárdanse nun ficheiro csv que se parece a isto:

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

Recibiremos unha lista dos principais centros temáticos do sitio.

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 función find_between e a clase Str seleccionan unha cadea entre dúas etiquetas, useinas antes. Os centros temáticos están marcados cun "*" para que se poidan resaltar facilmente, e tamén podes descomentar as liñas correspondentes para obter seccións doutras categorías.

A saída da función get_hubs é unha lista bastante impresionante, que gardamos como dicionario. En concreto presento a lista na súa totalidade para que poidades estimar o 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'}

A modo de comparación, as seccións de geektimes parecen máis 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'}

Do mesmo xeito conserváronse os núcleos restantes. Agora é fácil escribir unha función que devolva o resultado se o artigo pertence a geektimes ou a un centro 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

Fixéronse funcións similares para outras seccións (“desenvolvemento”, “administración”, etc.).

Procesamento

É hora de comezar a analizar. Cargamos o conxunto de datos e procesamos os datos 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 datos por día e mostrar o número de publicacións para diferentes centros.

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

Mostramos o número de artigos publicados usando Matplotlib:

Habrastatistics: explora as seccións máis e menos visitadas do sitio

Dividín os artigos "geektimes" e "geektimes only" no gráfico, porque Un artigo pode pertencer ás dúas seccións ao mesmo tempo (por exemplo, "DIY" + "microcontroladores" + "C++"). Usei a designación "perfil" para resaltar artigos de perfil no sitio, aínda que quizais o termo en inglés perfil para isto non sexa totalmente correcto.

Na parte anterior preguntamos polo "efecto geektimes" asociado ao cambio nas regras de pago dos artigos para os geektimes a partir deste verán. Imos mostrar os artigos de geektimes por separado:

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 é interesante. A proporción aproximada de visualizacións dos artigos de Geektimes co total é de aproximadamente 1:5. Pero aínda que o número total de visualizacións variou notablemente, a visualización de artigos de "entretemento" mantívose aproximadamente ao mesmo nivel.

Habrastatistics: explora as seccións máis e menos visitadas do sitio

Tamén podes notar que o número total de visualizacións de artigos na sección "geektimes" aínda diminuíu despois de cambiar as regras, pero "a ollos", non máis do 5% dos valores totais.

É interesante ver o número medio de visitas por artigo:

Habrastatistics: explora as seccións máis e menos visitadas do sitio

Para artigos de "entretemento" é un 40% por riba da media. Isto probablemente non sexa sorprendente. O fallo de principios de abril non me queda claro, quizais iso foi o que pasou, ou sexa algún tipo de erro de análise, ou quizais un dos autores de geektimes marchou de vacacións ;).

Por certo, o gráfico mostra dous picos máis notables no número de visualizacións de artigos: as vacacións de ano e maio.

Hubs

Pasemos á análise prometida dos hubs. Enumeremos os 20 principais centros segundo o número de visualizacións:

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: explora as seccións máis e menos visitadas do sitio

Sorprendentemente, o centro máis popular en termos de vistas foi "Seguridade da información" os 5 principais líderes tamén incluíron "Programación" e "Ciencia popular".

Antitop ocupa Gtk e Cocoa.

Habrastatistics: explora as seccións máis e menos visitadas do sitio

Vouche contar un segredo, tamén se poden ver os principais hubs aquí, aínda que alí non se mostra o número de visualizacións.

Clasificación

E por último, a valoración prometida. Usando os datos de análise do centro, podemos mostrar os artigos máis populares para os centros máis populares para este ano 2019.

Seguridade da información

Programación

Ciencia Popular

carreira

Lexislación en TI

Desenvolvemento web

GTK

E por último, para que ninguén se ofenda, darei a clasificación do hub menos visitado "gtk". Nun ano publicouse одна O artigo, que tamén ocupa "automáticamente" a primeira liña da clasificación.

Conclusión

Non haberá conclusión. Feliz lectura a todos.

Fonte: www.habr.com

Engadir un comentario