Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

Čau, Habr.

Š’ iepriekŔējā daļa Habra trafika tika analizēta pēc galvenajiem parametriem - rakstu skaita, to skatÄ«jumiem un reitingiem. Tomēr jautājums par vietnes sadaļu popularitāti palika neizskatÄ«ts. Kļuva interesanti to aplÅ«kot sÄ«kāk un atrast populārākos un nepopulārākos centrus. Visbeidzot, es sÄ«kāk aplÅ«koÅ”u geektimes efektu, beidzot ar jaunu labāko rakstu izlasi, pamatojoties uz jauniem reitingiem.

Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

Tiem, kam ir interese par notikuŔo, turpinājums ir zem griezuma.

Atgādinu vēlreiz, ka statistika un reitingi nav oficiāla, man nav nekādas iekŔējās informācijas. Nav arÄ« garantēts, ka kaut kur neesmu kļūdÄ«jies vai kaut ko palaidis garām. Bet tomēr, manuprāt, tas izrādÄ«jās interesanti. Vispirms sāksim ar kodu; tie, kurus tas neinteresē, var izlaist pirmās sadaļas.

Datu vākŔana

Pirmajā parsētāja versijā tika ņemts vērā tikai skatÄ«jumu skaits, komentāri un rakstu vērtējumi. Tas jau ir labi, bet neļauj veikt sarežģītākus vaicājumus. Ir pienācis laiks analizēt vietnes tematiskās sadaļas; tas ļaus jums veikt diezgan interesantus pētÄ«jumus, piemēram, redzēt, kā vairāku gadu laikā ir mainÄ«jusies sadaļas ā€œC++ā€ popularitāte.

Rakstu parsētājs ir uzlabots, tagad tas atgriež centrmezglus, kuriem raksts pieder, kā arÄ« autora segvārdu un viņa vērtējumu (Å”eit var izdarÄ«t arÄ« daudz interesantu lietu, bet tas bÅ«s vēlāk). Dati tiek saglabāti csv failā, kas izskatās apmēram Ŕādi:

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

Mēs saņemsim vietnes galveno tematisko centru sarakstu.

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 un klase Str izvēlas virkni starp diviem tagiem, es tos izmantoju pirms tam. Tematiskie centri ir atzÄ«mēti ar "*", lai tos varētu viegli izcelt, kā arÄ« varat atsaukt atbilstoŔās rindiņas, lai iegÅ«tu citu kategoriju sadaļas.

Funkcijas get_hubs izvade ir diezgan iespaidÄ«gs saraksts, kuru saglabājam kā vārdnÄ«cu. Es Ä«paÅ”i piedāvāju sarakstu pilnÄ«bā, lai jÅ«s varētu novērtēt tā apjomu.

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

Salīdzinājumam, geektimes sadaļas izskatās pieticīgāk:

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

AtlikuŔie mezgli tika saglabāti tādā paŔā veidā. Tagad ir viegli uzrakstīt funkciju, kas atgriež rezultātu neatkarīgi no tā, vai raksts pieder geektimes vai profila centram.

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

LÄ«dzÄ«gas funkcijas tika veiktas arÄ« citām sadaļām (ā€œizstrādeā€, ā€œadministrÄ“Å”anaā€ utt.).

Pārstrādes

Ir pienācis laiks sākt analīzi. Mēs ielādējam datu kopu un apstrādājam centrmezgla datus.

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)

Tagad mēs varam grupēt datus pa dienām un parādīt publikāciju skaitu dažādiem centriem.

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

Mēs parādām publicēto rakstu skaitu, izmantojot Matplotlib:

Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

Es diagrammā sadalÄ«ju rakstus ā€œgeektimesā€ un ā€œgeektimes onlyā€, jo Raksts var piederēt abām sadaļām vienlaikus (piemēram, ā€œDIYā€ + ā€œmikrokontrolleriā€ + ā€œC++ā€). Es izmantoju apzÄ«mējumu ā€œprofilsā€, lai izceltu vietnes profila rakstus, lai gan, iespējams, angļu valodas termins profile nav pilnÄ«gi pareizs.

IepriekŔējā daļā jautājām par ā€œgeektimes efektuā€, kas saistÄ«ts ar izmaiņām geektimes rakstu apmaksas noteikumos, sākot no Ŕīs vasaras. ParādÄ«sim geektimes rakstus atseviŔķi:

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

Rezultāts ir interesants. Aptuvenā geektimes rakstu skatÄ«jumu attiecÄ«ba pret kopējo skaitu ir kaut kur ap 1:5. Bet, lai gan kopējais skatÄ«jumu skaits manāmi svārstÄ«jās, ā€œizklaidesā€ rakstu skatÄ«jums palika aptuveni tajā paŔā lÄ«menÄ«.

Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

Tāpat var pamanÄ«t, ka kopējais rakstu skatÄ«jumu skaits sadaļā ā€œgeektimesā€ pēc noteikumu maiņas joprojām kritās, bet ā€œar aciā€, ne vairāk kā par 5% no kopējām vērtÄ«bām.

Interesanti apskatīt vidējo skatījumu skaitu uz vienu rakstu:

Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

ā€œIzklaidesā€ rakstiem tas ir par aptuveni 40% virs vidējā lÄ«meņa. Tas droÅ”i vien nav pārsteidzoÅ”i. Neveiksme aprīļa sākumā man ir neskaidra, varbÅ«t tā ir noticis, vai tā ir kāda veida parsÄ“Å”anas kļūda, vai varbÅ«t kāds no geektimes autoriem devies atvaļinājumā ;).

Starp citu, grafikā redzamas vēl divas manāmas rakstu skatījumu skaita virsotnes - Jaunais gads un maija brīvdienas.

Centrmezgli

Pāriesim pie solītās centrmezglu analīzes. Uzskaitīsim 20 populārākos centrus pēc skatījumu skaita:

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

Rezultāts:

Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

PārsteidzoÅ”i, ka populārākais centrs skatÄ«jumu ziņā bija ā€œInformācijas droŔībaā€, top 5 lÄ«deru vidÅ« bija arÄ« ā€œProgrammÄ“Å”anaā€ un ā€œPopulārā zinātneā€.

Antitop aizņem Gtk un Cocoa.

Habrastatistika: vietnes visvairāk un vismazāk apmeklēto sadaļu izpēte

AtklāŔu noslēpumu, top rumbas arÄ« var redzēt Å”eit, lai gan skatÄ«jumu skaits tur nav parādÄ«ts.

Novērtējums

Un visbeidzot solÄ«tais reitings. Izmantojot centrmezglu analÄ«zes datus, mēs varam parādÄ«t populārākos rakstus par populārākajiem centrmezgliem Å”ajā 2019. gadā.

Informācijas droŔība

ProgrammēŔana

Populārā zinātne

karjera

Tiesību akti IT jomā

web izstrāde

GTK

Un visbeidzot, lai neviens neapvainotos, es pieŔķirÅ”u vismazāk apmeklētā centra ā€œgtkā€ vērtējumu. Gada laikā tas tika publicēts viens Raksts, kas arÄ« ā€œautomātiskiā€ ieņem reitinga pirmo rindu.

Secinājums

Secinājuma nebūs. Priecīgu lasīŔanu visiem.

Avots: www.habr.com

Pievieno komentāru