Habrastatistica: esplorare le sezioni più e meno visitate del sito

Ciao Habr.

В sezione precedente Il traffico di Habr è stato analizzato in base ai parametri principali: numero di articoli, visualizzazioni e valutazioni. Tuttavia, la questione della popolarità delle sezioni del sito è rimasta inesplorata. È diventato interessante esaminarlo in modo più dettagliato e trovare gli hub più popolari e quelli più impopolari. Infine, esaminerò più in dettaglio l'effetto geektimes, concludendo con una nuova selezione dei migliori articoli basata su nuove classifiche.

Habrastatistica: esplorare le sezioni più e meno visitate del sito

Per coloro che sono interessati a quello che è successo, il seguito è tagliato.

Permettimi di ricordarti ancora una volta che le statistiche e le valutazioni non sono ufficiali, non ho informazioni privilegiate. Inoltre, non è garantito che io non abbia commesso un errore da qualche parte o che mi sia sfuggito qualcosa. Ma comunque penso che si sia rivelato interessante. Inizieremo prima con il codice; chi non è interessato può saltare le prime sezioni.

Raccolta dati

Nella prima versione del parser veniva preso in considerazione solo il numero di visualizzazioni, commenti e valutazioni degli articoli. Questo è già buono, ma non ti consente di effettuare query più complesse. È tempo di analizzare le sezioni tematiche del sito; questo ti permetterà di fare ricerche piuttosto interessanti, ad esempio vedere come è cambiata la popolarità della sezione “C++” nel corso degli anni.

Il parser degli articoli è stato migliorato, ora restituisce gli hub a cui appartiene l'articolo, così come il nickname dell'autore e la sua valutazione (anche qui si possono fare molte cose interessanti, ma questo verrà dopo). I dati vengono salvati in un file CSV simile a questo:

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

Riceveremo l'elenco dei principali snodi tematici del sito.

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)

La funzione find_between e la classe Str selezionano una stringa tra due tag, io le ho usate prima. Gli hub tematici sono contrassegnati con un "*" in modo che possano essere facilmente evidenziati e puoi anche rimuovere i commenti dalle righe corrispondenti per ottenere sezioni di altre categorie.

L'output della funzione get_hubs è un elenco abbastanza impressionante, che salviamo come dizionario. Presento specificamente l'elenco nella sua interezza in modo che tu possa stimarne il volume.

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

Per fare un confronto, le sezioni di geektimes sembrano più modeste:

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

I restanti hub sono stati conservati allo stesso modo. Ora è facile scrivere una funzione che restituisca il risultato se l'articolo appartiene a geektimes o a un hub del profilo.

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

Funzioni simili sono state realizzate per altre sezioni (“sviluppo”, “amministrazione”, ecc.).

lavorazione

È ora di iniziare ad analizzare. Carichiamo il set di dati ed elaboriamo i dati dell'hub.

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)

Ora possiamo raggruppare i dati per giorno e visualizzare il numero di pubblicazioni per diversi hub.

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

Visualizziamo il numero di articoli pubblicati utilizzando Matplotlib:

Habrastatistica: esplorare le sezioni più e meno visitate del sito

Ho diviso gli articoli “geektimes” e “geektimes only” nella tabella, perché Un articolo può appartenere a entrambe le sezioni contemporaneamente (ad esempio “DIY” + “microcontrollori” + “C++”). Ho usato la designazione “profilo” per evidenziare gli articoli del profilo sul sito, anche se forse il termine inglese profilo per questo non è del tutto corretto.

Nella parte precedente ci siamo interrogati sull’“effetto geektimes” legato al cambiamento delle regole di pagamento degli articoli per geektimes a partire da quest’estate. Mostriamo separatamente gli articoli di geektimes:

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

Il risultato è interessante. Il rapporto approssimativo tra le visualizzazioni degli articoli di geektimes e il totale è di circa 1:5. Ma mentre il numero totale di visualizzazioni ha oscillato notevolmente, la visualizzazione di articoli di “intrattenimento” è rimasta più o meno allo stesso livello.

Habrastatistica: esplorare le sezioni più e meno visitate del sito

Puoi anche notare che il numero totale di visualizzazioni degli articoli nella sezione “geektimes” è ancora diminuito dopo la modifica delle regole, ma “a occhio”, di non più del 5% dei valori totali.

È interessante osservare il numero medio di visualizzazioni per articolo:

Habrastatistica: esplorare le sezioni più e meno visitate del sito

Per gli articoli di “intrattenimento” è circa il 40% sopra la media. Questo probabilmente non è sorprendente. Il fallimento di inizio aprile non mi è chiaro, forse è successo proprio questo, oppure si è trattato di qualche errore di analisi, o forse uno degli autori di geektimes è andato in vacanza ;).

A proposito, il grafico mostra altri due picchi evidenti nel numero di visualizzazioni di articoli: il Capodanno e le vacanze di maggio.

Hub

Passiamo alla promessa analisi degli hub. Elenchiamo i primi 20 hub per numero di visualizzazioni:

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

Il risultato:

Habrastatistica: esplorare le sezioni più e meno visitate del sito

Sorprendentemente, l’hub più popolare in termini di opinioni è stato “Sicurezza dell’informazione”; tra i primi 5 leader figurano anche “Programmazione” e “Scienza popolare”.

Antitop occupa Gtk e Cocoa.

Habrastatistica: esplorare le sezioni più e meno visitate del sito

Ti svelo un segreto, si vedono anche gli hub superiori qui, sebbene il numero di visualizzazioni non sia mostrato lì.

Valutazione

E infine, la valutazione promessa. Utilizzando i dati di analisi degli hub, possiamo visualizzare gli articoli più popolari per gli hub più popolari per quest'anno 2019.

Sicurezza delle informazioni

Programmazione

Scienza popolare

carriera

Legislazione in ambito informatico

sviluppo web

GTK

E infine, affinché nessuno si offenda, darò la valutazione dell'hub meno visitato “gtk”. Nel giro di un anno fu pubblicato uno L'articolo, che occupa anche “automaticamente” la prima riga del rating.

conclusione

Non ci sarà alcuna conclusione. Buona lettura a tutti.

Fonte: habr.com

Aggiungi un commento