Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

Čau Habr.

В předchozí díl Habrova návštěvnost byla analyzována podle hlavních parametrů – počtu článků, jejich zhlédnutí a hodnocení. Otázka oblíbenosti sekcí webu však zůstala neprozkoumaná. Začalo být zajímavé se na to podívat podrobněji a najít nejoblíbenější a neoblíbenější rozbočovače. Nakonec se na efekt geektimes podívám podrobněji a zakončím to novým výběrem nejlepších článků na základě nových žebříčků.

Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

Pro ty, které zajímá, co se stalo, je pokračování pod střihem.

Ještě jednou připomínám, že statistiky a hodnocení nejsou oficiální, nemám žádné zasvěcené informace. Také není zaručeno, že jsem někde neudělal chybu nebo něco nepřehlédl. Ale i tak si myslím, že to dopadlo zajímavě. Nejprve začneme kódem, koho to nezajímá, může první sekce přeskočit.

Sběr dat

V první verzi parseru byl brán v úvahu pouze počet zhlédnutí, komentáře a hodnocení článku. To je již dobré, ale neumožňuje provádět složitější dotazy. Je čas analyzovat tematické sekce webu; to vám umožní udělat docela zajímavý výzkum, například zjistit, jak se během několika let změnila popularita sekce „C++“.

Vylepšen byl analyzátor článků, nyní vrací huby, ke kterým článek patří, a také přezdívku autora a jeho hodnocení (zde se dá dělat spousta zajímavých věcí, ale to až později). Data jsou uložena v souboru csv, který vypadá 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
...

Obdržíme seznam hlavních tematických center webu.

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)

Funkce find_between a třída Str vybírají řetězec mezi dvěma značkami, použil jsem je dříve. Tematická centra jsou označena "*", takže je lze snadno zvýraznit, a můžete také odkomentovat odpovídající řádky, abyste získali sekce jiných kategorií.

Výstupem funkce get_hubs je poměrně působivý seznam, který si uložíme jako slovník. Seznam konkrétně uvádím celý, abyste mohli odhadnout 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'}

Pro srovnání, sekce geektimes vypadají skromněji:

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

Zbývající náboje byly zachovány stejným způsobem. Nyní je snadné napsat funkci, která vrátí výsledek, ať už článek patří do geektimes nebo 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

Obdobné funkce byly vytvořeny pro další sekce („vývoj“, „správa“ atd.).

Zpracování

Je čas začít analyzovat. Načteme datovou sadu a zpracujeme data 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)

Nyní můžeme data seskupit podle dne a zobrazit počet publikací pro různá centra.

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ánků pomocí Matplotlib:

Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

Články „geektimes“ a „geektimes only“ jsem v grafu rozdělil, protože Článek může patřit do obou sekcí současně (například „DIY“ + „microcontrollers“ + „C++“). Pro zvýraznění profilových článků na webu jsem použil označení „profil“, i když možná anglický výraz profile pro to není úplně správný.

V minulém díle jsme se ptali na „geektimes efekt“ spojený se změnou pravidel plateb za články za geektimes od letošního léta. Zobrazme články geektimes samostatně:

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ýsledek je zajímavý. Přibližný poměr zhlédnutí článků geektimes k celkovému je někde kolem 1:5. Zatímco ale celkový počet zhlédnutí znatelně kolísal, sledovanost „zábavných“ článků zůstala přibližně na stejné úrovni.

Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

Můžete si také všimnout, že celkový počet zobrazení článků v sekci „geektimes“ po změně pravidel stále klesl, ale „na oko“, maximálně o 5 % z celkových hodnot.

Je zajímavé podívat se na průměrný počet zobrazení na článek:

Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

U „zábavných“ článků je to asi 40 % nad průměrem. To asi není překvapivé. Neúspěch na začátku dubna mi není jasný, možná se to stalo, nebo je to nějaká chyba parsování, nebo možná jeden z autorů geektimes odjel na dovolenou ;).

Mimochodem, v grafu jsou patrné ještě dva znatelné vrcholy v počtu zobrazení článků - novoroční a květnové svátky.

Náboje

Přejděme ke slíbenému rozboru hubů. Uveďme seznam 20 nejlepších center podle 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ýsledek:

Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

Překvapivě nejoblíbenějším centrem z hlediska názorů byla „Bezpečnost informací“; mezi 5 nejlepších lídrů patřily také „Programování“ a „Populární věda“.

Antitop zabírá Gtk a Cocoa.

Habrastatistics: zkoumání nejvíce a nejméně navštěvovaných částí webu

Prozradím vám tajemství, jsou vidět i horní náboje zde, ačkoli počet zobrazení zde není uveden.

Hodnocení

A nakonec slíbené hodnocení. Pomocí dat analýzy hub můžeme zobrazit nejoblíbenější články pro nejoblíbenější huby pro tento rok 2019.

Informační bezpečnost

Programování

Populární věda

kariéra

Legislativa v IT

Vývoj webu

GTK

A nakonec, aby se nikdo neurazil, udělím hodnocení nejméně navštěvovaného hubu „gtk“. Za rok to vyšlo одна Článek, který také „automaticky“ zaujímá první řádek hodnocení.

Závěr

Žádný závěr nebude. Příjemné čtení všem.

Zdroj: www.habr.com

Přidat komentář