Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

Hej Habr.

В föregående del Habrs trafik analyserades enligt huvudparametrarna - antalet artiklar, deras visningar och betyg. Frågan om sajtsektionernas popularitet förblev dock outredd. Det blev intressant att titta närmare på detta och hitta de populäraste och mest impopulära naven. Slutligen ska jag titta på geektimes-effekten mer i detalj, och avslutar med ett nytt urval av de bästa artiklarna baserat på nya rankningar.

Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

För den som är intresserad av vad som hänt är fortsättningen under klippet.

Låt mig återigen påminna er om att statistiken och betygen inte är officiella, jag har ingen insiderinformation. Det är inte heller garanterat att jag inte gjort fel någonstans eller missat något. Men ändå tycker jag att det blev intressant. Vi börjar med koden först, de som inte är intresserade av detta kan hoppa över de första avsnitten.

Datainsamling

I den första versionen av parsern togs endast hänsyn till antalet visningar, kommentarer och artikelbetyg. Detta är redan bra, men det tillåter dig inte att göra mer komplexa frågor. Det är dags att analysera de tematiska delarna av webbplatsen; detta gör att du kan göra ganska intressant forskning, till exempel se hur populariteten för avsnittet "C++" har förändrats under flera år.

Artikeltolken har förbättrats, nu returnerar den de nav som artikeln tillhör, liksom författarens smeknamn och hans betyg (mycket intressanta saker kan göras här också, men det kommer senare). Datan sparas i en csv-fil som ser ut ungefär så här:

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

Vi kommer att få en lista över de viktigaste tematiska nav på webbplatsen.

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)

Find_between-funktionen och Str-klassen väljer en sträng mellan två taggar, jag använde dem tidigare. Tematiska nav är markerade med ett "*" så att de enkelt kan markeras, och du kan även avkommentera motsvarande rader för att få avsnitt av andra kategorier.

Utdata från get_hubs-funktionen är en ganska imponerande lista, som vi sparar som en ordbok. Jag presenterar specifikt listan i sin helhet så att du kan uppskatta dess volym.

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

För jämförelse, sektionerna för geektimes ser mer blygsamma ut:

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

De återstående naven bevarades på samma sätt. Nu är det enkelt att skriva en funktion som returnerar resultatet oavsett om artikeln tillhör geektimes eller en profilnav.

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

Liknande funktioner gjordes för andra sektioner (”utveckling”, ”administration” etc.).

Bearbetning

Det är dags att börja analysera. Vi laddar datauppsättningen och bearbetar navdata.

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)

Nu kan vi gruppera data efter dag och visa antalet publikationer för olika nav.

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

Vi visar antalet publicerade artiklar med Matplotlib:

Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

Jag delade upp artiklarna "geektimes" och "geektimes only" i diagrammet, eftersom En artikel kan tillhöra båda sektionerna samtidigt (till exempel "DIY" + "microcontrollers" + "C++"). Jag använde beteckningen "profil" för att lyfta fram profilartiklar på sajten, även om den engelska termen profile för detta kanske inte är helt korrekt.

I förra delen frågade vi om "geektimes-effekten" förknippad med förändringen av betalningsreglerna för artiklar för geektimes som börjar i sommar. Låt oss visa geektimes-artiklarna separat:

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

Resultatet är intressant. Det ungefärliga förhållandet mellan visningar av geektimes-artiklar och totalen är någonstans runt 1:5. Men medan det totala antalet visningar fluktuerade märkbart, höll sig tittandet av "underhållningsartiklar" på ungefär samma nivå.

Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

Du kan också märka att det totala antalet visningar av artiklar i avsnittet "geektimes" fortfarande minskade efter att reglerna ändrats, men "by eye", med högst 5% av de totala värdena.

Det är intressant att titta på det genomsnittliga antalet visningar per artikel:

Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

För "underhållnings"-artiklar är det cirka 40 % över genomsnittet. Detta är förmodligen inte förvånande. Misslyckandet i början av april är oklart för mig, kanske är det vad som hände, eller så är det något slags analysfel, eller kanske en av geektime-författarna gick på semester ;).

Grafen visar förresten ytterligare två märkbara toppar i antalet artikelvisningar - nyårs- och majhelgerna.

Hubs

Låt oss gå vidare till den utlovade analysen av nav. Låt oss lista de 20 bästa hubbar efter antal visningar:

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

Resultat:

Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

Överraskande nog var det mest populära navet när det gäller åsikter "Informationssäkerhet"; de fem bästa ledarna inkluderade också "Programmering" och "Populärvetenskap".

Antitop upptar Gtk och Cocoa.

Habrastatistics: utforska de mest och minst besökta delarna av webbplatsen

Jag ska berätta en hemlighet, toppnaven kan också ses här, även om antalet visningar inte visas där.

Betyg

Och slutligen det utlovade betyget. Med hjälp av navanalysdata kan vi visa de mest populära artiklarna för de mest populära hubbarna för i år 2019.

Informationssäkerhet

Programmering

Populär vetenskap

karriär

Lagstiftning inom IT

webbutveckling

GTK

Och slutligen, så att ingen blir förolämpad, kommer jag att ge betyget för det minst besökta navet "gtk". Inom ett år publicerades den en Artikeln, som också "automatiskt" upptar den första raden i betyget.

Slutsats

Det blir ingen slutsats. Trevlig läsning alla.

Källa: will.com

Lägg en kommentar