Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

Ahoj Habr.

В predchádzajúca časť Habrova návštevnosť bola analyzovaná podľa hlavných parametrov – počtu článkov, ich videní a hodnotení. Otázka obľúbenosti sekcií stránok však zostala nepreskúmaná. Začalo byť zaujímavé pozrieť sa na to podrobnejšie a nájsť najobľúbenejšie a najnepopulárnejšie huby. Na záver sa na efekt geektimes pozriem podrobnejšie a skončím novým výberom najlepších článkov na základe nových rebríčkov.

Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

Pre tých, ktorých zaujíma, čo sa stalo, pokračovanie je pod strihom.

Ešte raz pripomínam, že štatistiky a hodnotenia nie sú oficiálne, nemám žiadne zasvätené informácie. Taktiež nie je zaručené, že som niekde neurobil chybu alebo niečo neprehliadol. Ale aj tak si myslím, že to dopadlo zaujímavo. Najprv začneme kódom, koho to nezaujíma, môže prvé časti preskočiť.

Zber dát

V prvej verzii analyzátora sa bral do úvahy iba počet zobrazení, komentárov a hodnotenia článkov. To je už dobré, ale neumožňuje vám to robiť zložitejšie otázky. Je čas analyzovať tematické sekcie stránky; to vám umožní urobiť celkom zaujímavý výskum, napríklad zistiť, ako sa zmenila popularita sekcie „C++“ v priebehu niekoľkých rokov.

Analyzátor článkov bol vylepšený, teraz vracia huby, do ktorých článok patrí, ako aj prezývku autora a jeho hodnotenie (aj tu sa dá urobiť veľa zaujímavého, ale to príde neskôr). Údaje sa uložia do súboru csv, ktorý vyzerá asi takto:

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

Dostaneme zoznam hlavných tematických centier stránky.

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)

Funkcia find_between a trieda Str vyberajú reťazec medzi dvoma značkami, použil som ich skôr. Tematické centrá sú označené „*“, takže sa dajú ľahko zvýrazniť a môžete tiež odkomentovať príslušné riadky, aby ste získali sekcie iných kategórií.

Výstupom funkcie get_hubs je pomerne pôsobivý zoznam, ktorý si uložíme ako slovník. Zoznam uvádzam konkrétne celý, aby ste vedeli odhadnúť jeho objem.

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

Pre porovnanie, sekcie geektimes vyzerajú skromnejšie:

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

Zvyšné náboje boli zachované rovnakým spôsobom. Teraz je ľahké napísať funkciu, ktorá vráti výsledok, či už článok patrí do geektimes alebo profilového centra.

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

Podobné funkcie boli vytvorené aj pre ďalšie sekcie („vývoj“, „správa“ atď.).

spracovanie

Je čas začať analyzovať. Načítame dataset a spracujeme údaje hubu.

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)

Teraz môžeme zoskupiť údaje podľa dňa a zobraziť počet publikácií pre rôzne centrá.

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

Zobrazujeme počet publikovaných článkov pomocou Matplotlib:

Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

Články „geektimes“ a „geektimes only“ som v grafe rozdelil, pretože Článok môže patriť do oboch sekcií súčasne (napríklad „urob si sám“ + „mikroovládače“ + „C++“). Označenie „profil“ som použil na zvýraznenie profilových článkov na stránke, aj keď možno anglický výraz profile pre to nie je úplne správny.

V predchádzajúcej časti sa pýtali na “geektimes effect” spojený so zmenou pravidiel platby za články pre geektimes toto leto. Ukážme články geektimes oddelene:

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

Výsledok je zaujímavý. Približný pomer zobrazení geektimes článkov k celkovému je niekde okolo 1:5. No kým celkový počet videní značne kolísal, sledovanosť „zábavných“ článkov zostala približne na rovnakej úrovni.

Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

Môžete si tiež všimnúť, že celkový počet zobrazení článkov v sekcii „geektimes“ po zmene pravidiel stále klesol, ale „od oka“, nie o viac ako 5 % z celkových hodnôt.

Je zaujímavé pozrieť sa na priemerný počet zobrazení na článok:

Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

V prípade „zábavných“ článkov je to asi 40 % nad priemerom. To asi nie je prekvapujúce. Neúspech zo začiatku apríla je pre mňa nepochopiteľný, možno sa tak stalo, alebo je to nejaká chyba pri parsovaní, alebo možno jeden z autorov geektimes odišiel na dovolenku ;).

Mimochodom, v grafe sú ešte dva badateľné vrcholy v počte zobrazení článkov – novoročné a májové sviatky.

Náboje

Prejdime k sľúbenému rozboru hubov. Uveďme zoznam 20 najlepších centier podľa počtu zobrazení:

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

Výsledok:

Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

Prekvapivo najpopulárnejším centrom z hľadiska názorov bola „Bezpečnosť informácií“; medzi 5 najlepších lídrov patrili aj „Programovanie“ a „Populárna veda“.

Antitop zaberá Gtk a Cocoa.

Habrastatistics: skúmanie najviac a najmenej navštevovaných častí stránky

Poviem ti tajomstvo, vidno aj vrchné náboje tu, aj keď počet zobrazení tam nie je uvedený.

Hodnotenie

A na záver sľúbené hodnotenie. Pomocou údajov analýzy rozbočovačov môžeme zobraziť najobľúbenejšie články pre najpopulárnejšie rozbočovače pre tento rok 2019.

Informačná bezpečnosť

programovanie

Populárna veda

kariéra

Legislatíva v IT

vývoj webových aplikácií

GTK

A nakoniec, aby sa nikto neurazil, dám hodnotenie najmenej navštevovaného hubu „gtk“. Do roka to vyšlo одна článok, ktorý tiež „automaticky“ zaberá prvý riadok hodnotenia.

Záver

Záver nebude. Príjemné čítanie všetkým.

Zdroj: hab.com

Pridať komentár