Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

Sveiki, Habr.

В ankstesnė dalis Habro srautas buvo analizuojamas pagal pagrindinius parametrus – straipsnių skaičių, jų peržiūras ir įvertinimus. Tačiau svetainės skyrių populiarumo klausimas liko nenagrinėtas. Pasidarė įdomu pažvelgti į tai išsamiau ir rasti populiariausius ir nepopuliariausius centrus. Galiausiai pažvelgsiu į geektimes efektą išsamiau ir baigsiu nauju geriausių straipsnių atranka, pagrįsta naujais reitingais.

Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

Tiems, kurie domisi tuo, kas nutiko, tęsinys yra po ranka.

Dar kartą priminsiu, kad statistika ir reitingai nėra oficialūs, neturiu jokios viešai neatskleistos informacijos. Taip pat negarantuojama, kad kur nors nesuklydau ar ko nors nepraleidau. Bet vis tiek manau, kad pasirodė įdomu. Pirmiausia pradėsime nuo kodo, tie, kuriems tai neįdomu, gali praleisti pirmąsias dalis.

Duomenų rinkimas

Pirmojoje analizatoriaus versijoje buvo atsižvelgta tik į peržiūrų, komentarų ir straipsnių įvertinimų skaičių. Tai jau gerai, bet neleidžia pateikti sudėtingesnių užklausų. Atėjo laikas išanalizuoti temines svetainės skiltis, tai leis atlikti gana įdomius tyrimus, pavyzdžiui, pamatyti, kaip per kelerius metus pasikeitė „C++“ skilties populiarumas.

Straipsnių analizatorius buvo patobulintas, dabar jis grąžina centrus, kuriems priklauso straipsnis, taip pat autoriaus slapyvardį ir jo įvertinimą (čia taip pat galima padaryti daug įdomių dalykų, bet tai bus vėliau). Duomenys išsaugomi csv faile, kuris atrodo maždaug taip:

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

Gausime pagrindinių svetainės teminių centrų sąrašą.

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 ir Str klasė pasirenka eilutę tarp dviejų žymų, aš jas naudojau anksčiau. Teminiai centrai pažymėti „*“, kad juos būtų galima lengvai paryškinti, taip pat galite panaikinti atitinkamas eilutes, kad gautumėte kitų kategorijų skyrius.

Funkcijos get_hubs išvestis yra gana įspūdingas sąrašas, kurį išsaugome kaip žodyną. Konkrečiai pateikiu visą sąrašą, kad galėtumėte įvertinti jo apimtį.

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

Palyginimui, geektimes skyriai atrodo kukliau:

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

Likę stebulės buvo išsaugotos taip pat. Dabar lengva parašyti funkciją, kuri grąžina rezultatą, nesvarbu, ar straipsnis priklauso geektimes, ar profilio centrui.

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

Panašios funkcijos buvo padarytos ir kitiems skyriams („plėtra“, „administravimas“ ir kt.).

apdorojimas

Pats laikas pradėti analizuoti. Įkeliame duomenų rinkinį ir apdorojame koncentratoriaus duomenis.

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)

Dabar galime sugrupuoti duomenis pagal dieną ir rodyti įvairių centrų publikacijų skaičių.

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

Rodome paskelbtų straipsnių skaičių naudojant Matplotlib:

Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

Diagramoje suskirstiau straipsnius „geektimes“ ir „geektimes only“, nes Straipsnis gali priklausyti abiem skyriams vienu metu (pvz., „pasidaryk pats“ + „mikrovaldikliai“ + „C++“). Naudojau pavadinimą „profilis“, kad paryškinčiau profilio straipsnius svetainėje, nors galbūt angliškas terminas profile nėra visiškai teisingas.

Ankstesnėje dalyje teiravomės apie „geektime“ efektą, susijusį su straipsnių apmokėjimo taisyklių pakeitimu nuo šios vasaros. Atskirai parodykime „geektimes“ straipsnius:

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

Rezultatas įdomus. Apytikslis „geektimes“ straipsnių peržiūrų ir bendro skaičiaus santykis yra maždaug 1:5. Tačiau nors bendras peržiūrų skaičius pastebimai svyravo, „pramoginių“ straipsnių peržiūra išliko maždaug tokia pati.

Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

Taip pat galite pastebėti, kad bendras straipsnių peržiūrų skaičius skiltyje „geektimes“ vis tiek sumažėjo pakeitus taisykles, bet „iš akies“, ne daugiau kaip 5% visų reikšmių.

Įdomu pažvelgti į vidutinį vieno straipsnio peržiūrų skaičių:

Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

„Pramoginių“ straipsnių atveju jis yra maždaug 40% didesnis nei vidutinis. Tai tikriausiai nestebina. Balandžio pradžios gedimas man neaiškus, gal taip ir atsitiko, o gal kažkokia analizavimo klaida, o gal vienas iš geektimes autorių išėjo atostogų ;).

Beje, grafike matyti dar du pastebimi straipsnių peržiūrų pikai – Naujieji metai ir gegužės šventės.

Stebulės

Pereikime prie žadėtos koncentratorių analizės. Išvardinkime 20 populiariausių centrų pagal peržiūrų skaičių:

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

Rezultatas:

Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

Stebėtina, kad populiariausias centras pagal nuomones buvo „Informacijos saugumas“;

Antitop užima Gtk ir kakavą.

Habrastatistika: daugiausiai ir mažiausiai lankomų svetainės dalių tyrinėjimas

Išduosiu paslaptį, matosi ir viršutiniai stebulės čia, nors peržiūrų skaičius ten nerodomas.

Įvertinimas

Ir pabaigai – žadėtas įvertinimas. Naudodami centrų analizės duomenis galime rodyti populiariausius šių 2019 m. centrų straipsnius.

Informacijos saugumas

Programavimas

Populiarusis mokslas

karjera

Teisės aktai IT srityje

Interneto kūrimas

GTK

Ir galiausiai, kad niekas neįsižeistų, pateiksiu mažiausiai lankomo mazgo „gtk“ įvertinimą. Per metus jis buvo paskelbtas vienas straipsnis, kuris taip pat „automatiškai“ užima pirmąją reitingo eilutę.

išvada

Išvados nebus. Malonaus skaitymo visiems.

Šaltinis: www.habr.com

Добавить комментарий