Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Hej Habr.

В prejšnji del Habrov promet je bil analiziran glede na glavne parametre - število člankov, njihov ogled in ocene. Vendar je vprašanje priljubljenosti razdelkov spletnega mesta ostalo nepreverjeno. Zanimivo je bilo pogledati to podrobneje in najti najbolj priljubljena in najbolj nepriljubljena vozlišča. Nazadnje si bom podrobneje ogledal učinek geektimes in končal z novim izborom najboljših člankov na podlagi novih lestvic.

Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Za tiste, ki jih zanima, kaj se je zgodilo, je nadaljevanje pod rezom.

Naj vas še enkrat spomnim, da statistika in ocene niso uradne, nimam notranjih informacij. Prav tako ni zagotovljeno, da se nisem kje zmotil ali kaj spregledal. A kljub temu se mi zdi zanimivo. Najprej bomo začeli s kodo, tiste, ki jih to ne zanima, lahko prve razdelke preskočijo.

Zbiranje podatkov

V prvi različici razčlenjevalnika je bilo upoštevano le število ogledov, komentarjev in ocen člankov. To je že dobro, vendar vam ne omogoča izdelave bolj zapletenih poizvedb. Čas je, da analiziramo tematske razdelke spletnega mesta; to vam bo omogočilo precej zanimivo raziskavo, na primer, da vidite, kako se je priljubljenost razdelka »C++« spreminjala v nekaj letih.

Razčlenjevalec člankov je izboljšan, zdaj vrne vozlišča, ki jim članek pripada, pa tudi vzdevek avtorja in njegovo oceno (tudi tukaj je mogoče početi marsikaj zanimivega, a to pride kasneje). Podatki so shranjeni v datoteki csv, ki je videti nekako takole:

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

Prejeli bomo seznam glavnih tematskih središč spletnega mesta.

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)

Funkcija find_between in razred Str izbereta niz med dvema oznakama, uporabil sem ju prej. Tematska vozlišča so označena z "*", tako da jih je mogoče preprosto označiti, prav tako pa lahko odkomentirate ustrezne vrstice, da dobite razdelke drugih kategorij.

Rezultat funkcije get_hubs je dokaj impresiven seznam, ki ga shranimo kot slovar. Seznam podajam posebej v celoti, da lahko ocenite obseg.

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

Za primerjavo, razdelki geektimes so videti bolj skromni:

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

Preostala pesta so bila ohranjena na enak način. Zdaj je preprosto napisati funkcijo, ki vrne rezultat, ne glede na to, ali članek pripada geektimesu ali središču profila.

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

Podobne funkcije so bile narejene za druge razdelke (»razvoj«, »administracija« itd.).

Predelava

Čas je, da začnemo analizirati. Naložimo nabor podatkov in obdelamo podatke vozlišča.

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)

Sedaj lahko združimo podatke po dnevih in prikažemo število objav za različna vozlišča.

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

Število objavljenih člankov prikažemo z Matplotlib:

Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Na grafikonu sem razdelil članka »geektimes« in »geektimes only«, ker Članek lahko sodi v obe rubriki hkrati (npr. “DIY” + “mikrokontrolerji” + “C++”). Oznako “profil” sem uporabil za poudarjanje profilnih člankov na strani, čeprav morda angleški izraz profile za to ni povsem pravilen.

V prejšnjem delu smo spraševali o "učinku geektimesa", povezanem s spremembo pravil plačila za članke za geektimes, ki se začnejo to poletje. Prikažimo članke geektimes ločeno:

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

Rezultat je zanimiv. Približno razmerje ogledov člankov geektimesa glede na skupno je nekje 1:5. A medtem ko je skupno število ogledov opazno nihalo, je gledanost »razvedrilnih« člankov ostala na približno enaki ravni.

Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Opazite lahko tudi, da je skupno število ogledov člankov v razdelku »geektimes« po spremembi pravil še vedno padlo, vendar »na oko«, za največ 5% skupnih vrednosti.

Zanimivo je pogledati povprečno število ogledov na članek:

Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Za “razvedrilne” članke je približno 40 % nad povprečjem. To verjetno ni presenetljivo. Neuspeh v začetku aprila mi ni jasen, morda se je to zgodilo, ali je kakšna napaka pri razčlenjevanju, ali pa je šel kdo od avtorjev geektimesa na dopust ;).

Mimogrede, graf prikazuje še dva opazna vrha v številu ogledov člankov - novo leto in majske praznike.

Pesta

Preidimo k obljubljeni analizi vozlišč. Naštejmo 20 najboljših središč po številu ogledov:

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

Rezultat:

Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Presenetljivo je bilo najbolj priljubljeno središče glede na oglede »Informacijska varnost«, med top 5 vodilnih sta bila tudi »Programiranje« in »Popularna znanost«.

Antitop zaseda Gtk in Cocoa.

Habrastatistika: raziskovanje najbolj in najmanj obiskanih delov spletnega mesta

Povedal vam bom skrivnost, da se vidijo tudi zgornja pesta tukaj, čeprav tam ni prikazano število ogledov.

Ocena

In za konec še obljubljena ocena. Z uporabo podatkov analize vozlišč lahko prikažemo najbolj priljubljene članke za najbolj priljubljena vozlišča za to leto 2019.

Informacijska varnost

Programiranje

Poljudno znanost

kariera

Zakonodaja v IT

spletni razvoj

GTK

In končno, da ne bo kdo užaljen, bom dal oceno najmanj obiskanega vozlišča "gtk". V enem letu je bil objavljen одна Članek, ki tudi »samodejno« zaseda prvo vrstico ocene.

Zaključek

Zaključka ne bo. Prijetno branje vsem.

Vir: www.habr.com

Dodaj komentar