Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

Tere Habr.

В eelmine osa Habri liiklust analüüsiti põhiparameetrite – artiklite arvu, nende vaatamiste ja hinnangute – järgi. Saidi sektsioonide populaarsuse küsimus jäi aga uurimata. Huvitav oli seda üksikasjalikumalt vaadata ja leida kõige populaarsemad ja ebapopulaarsemad keskused. Lõpetuseks vaatan lähemalt geektimesi efekti, lõpetades uue parimate artiklite valikuga uute edetabelite põhjal.

Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

Kellele juhtunu vastu huvi pakub, siis jätk on lahtri all.

Tuletan veel kord meelde, et statistika ja reitingud ei ole ametlikud, minul puudub siseinfo. Samuti pole garanteeritud, et ma kuskil ei eksinud või millestki mööda ei pannud. Aga siiski, ma arvan, et see osutus huvitavaks. Kõigepealt alustame koodiga, kes sellest huvitatud ei ole, võivad esimesed jaotised vahele jätta.

Andmete kogumine

Parseri esimeses versioonis võeti arvesse ainult vaatamiste, kommentaaride ja artiklite hinnangute arvu. See on juba hea, kuid see ei võimalda teil teha keerukamaid päringuid. On aeg analüüsida saidi temaatilisi jaotisi, see võimaldab teil teha üsna huvitavaid uuringuid, näiteks vaadata, kuidas jaotise "C++" populaarsus on mitme aasta jooksul muutunud.

Artikli parserit on täiustatud, nüüd tagastab see jaoturid, kuhu artikkel kuulub, samuti autori hüüdnime ja tema hinnangu (ka siin saab teha palju huvitavat, aga see tuleb hiljem). Andmed salvestatakse csv-faili, mis näeb välja umbes selline:

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

Saame saidi peamiste temaatiliste keskuste loendi.

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)

Funktsioon find_between ja klass Str valivad stringi kahe sildi vahel, mina kasutasin neid enne. Temaatilised jaoturid on tähistatud tähega "*", nii et neid saab hõlpsasti esile tõsta, ja saate ka vastavaid ridu kommenteerida, et saada teiste kategooriate jaotisi.

Funktsiooni get_hubs väljund on üsna muljetavaldav nimekiri, mille salvestame sõnaraamatuna. Esitan konkreetselt nimekirja tervikuna, et saaksite hinnata selle mahtu.

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

Võrdluseks, geektimesi jaotised näevad tagasihoidlikumad välja:

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

Ülejäänud sõlmpunktid säilitati samamoodi. Nüüd on lihtne kirjutada funktsiooni, mis tagastab tulemuse olenemata sellest, kas artikkel kuulub geektimesi või profiilijaoturisse.

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

Sarnased funktsioonid tehti ka teistele osadele (“arendus”, “haldus” jne).

Töötlemine

On aeg hakata analüüsima. Laadime andmestiku ja töötleme jaoturi andmeid.

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)

Nüüd saame andmeid rühmitada päevade kaupa ja kuvada erinevate jaoturite väljaannete arvu.

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

Kuvame Matplotlibi abil avaldatud artiklite arvu:

Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

Jagasin graafikus artiklid “geektimes” ja “ainult geektimes”, kuna Artikkel võib kuuluda korraga mõlemasse jaotisesse (näiteks "DIY" + "mikrokontrollerid" + "C++"). Kasutasin saidi profiiliartiklite esiletõstmiseks nimetust "profiil", ehkki võib-olla pole selle ingliskeelne termin profile täiesti õige.

Eelmises osas uurisime “geektime efekti” kohta, mis on seotud sellest suvest algava geektime artiklite maksereeglite muudatusega. Kuvame geektimesi artiklid eraldi:

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

Tulemus on huvitav. Geektimesi artiklite vaatamiste ligikaudne suhe koguarvusse on kuskil 1:5. Kuid kuigi vaatamiste koguarv kõikus märgatavalt, jäi “meelelahutuslike” artiklite vaatamine ligikaudu samale tasemele.

Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

Samuti võite märgata, et artiklite vaatamiste koguarv jaotises "geektimes" langes pärast reeglite muutmist siiski, kuid "silma järgi", mitte rohkem kui 5% koguväärtustest.

Huvitav on vaadata keskmist vaatamiste arvu artikli kohta:

Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

"Meelelahutuslike" artiklite puhul on see umbes 40% keskmisest kõrgem. See pole ilmselt üllatav. Aprilli alguse tõrge on minu jaoks ebaselge, võib-olla juhtus nii või on see mingi sõelumisviga või äkki läks keegi geektimesi autoritest puhkusele ;).

Muide, graafikul on näha veel kaks märgatavamat artiklite vaatamiste arvu tippu - uusaasta ja maipühad.

Jaoturid

Liigume edasi lubatud jaoturite analüüsi juurde. Loetleme 20 parimat jaoturit vaatamiste arvu järgi:

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

Tulemus:

Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

Üllataval kombel oli vaadetelt kõige populaarsem sõlmpunkt “Infoturve”, esiviisikusse mahtusid ka “Programmeerimine” ja “Populaarteadus”.

Antitop hõivab Gtk ja Cocoa.

Habrastatistika: saidi kõige ja kõige vähem külastatud osade uurimine

Ma ütlen teile saladuse, ülemised rummud on ka näha siin, kuigi vaatamiste arvu seal ei näidata.

Hinnang

Ja lõpuks lubatud reiting. Jaoturi analüüsi andmeid kasutades saame kuvada selle 2019. aasta populaarseimate jaoturite populaarseimad artiklid.

Infoturbe

Programmeerimine

Populaarne teadus

karjäär

IT-alane seadusandlus

Veebiarendus

GTK

Ja lõpuks, et keegi ei oleks solvunud, annan kõige vähem külastatud keskusele hinnangu “gtk”. Aastaga ilmus see üks Artikkel, mis asub ka "automaatselt" reitingu esimesel real.

Järeldus

Järeldust ei tule. Head lugemist kõigile.

Allikas: www.habr.com

Lisa kommentaar