Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Hei Habr.

В edellinen osa Habrin liikennettä analysoitiin pääparametrien - artikkelien lukumäärän, niiden näkemysten ja arvioiden - mukaan. Kysymys sivustoosioiden suosiosta jäi kuitenkin tutkimatta. Oli mielenkiintoista tarkastella tätä tarkemmin ja löytää suosituimmat ja epäsuosituimmat keskukset. Lopuksi tarkastelen geektimes-vaikutusta tarkemmin, päätän uuteen parhaiden artikkelien valikoimaan uusien sijoituksien perusteella.

Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Tapahtuneesta kiinnostuneille jatkoa on luvassa.

Muistutan vielä kerran, että tilastot ja luokitukset eivät ole virallisia, minulla ei ole sisäpiiritietoa. Ei myöskään voida taata, ettenkö tehnyt virhettä jossain tai missannut jotain. Mutta silti mielestäni se osoittautui mielenkiintoiseksi. Aloitamme ensin koodilla; ne, jotka eivät ole kiinnostuneita tästä, voivat ohittaa ensimmäiset osiot.

Tiedonkeruu

Jäsentimen ensimmäisessä versiossa huomioitiin vain näyttökertojen, kommenttien ja artikkelien arvioiden määrä. Tämä on jo hyvä, mutta se ei salli monimutkaisempien kyselyiden tekemistä. On aika analysoida sivuston temaattisia osioita, jolloin voit tehdä varsin mielenkiintoista tutkimusta, esimerkiksi nähdä kuinka "C++" -osion suosio on muuttunut useiden vuosien aikana.

Artikkelin jäsennintä on parannettu, nyt se palauttaa keskittimet, joihin artikkeli kuuluu, sekä kirjoittajan lempinimen ja hänen arvosanansa (täälläkin voi tehdä paljon mielenkiintoista, mutta se tulee myöhemmin). Tiedot tallennetaan csv-tiedostoon, joka näyttää suunnilleen tältä:

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

Saamme luettelon sivuston tärkeimmistä teemakeskuksista.

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-funktio ja Str-luokka valitsevat merkkijonon kahden tagin väliltä, ​​käytin niitä aikaisemmin. Teemakeskukset on merkitty *-merkillä, jotta ne voidaan helposti korostaa, ja voit myös poistaa vastaavien rivien kommentit saadaksesi osia muista luokista.

Get_hubs-funktion tulos on melko vaikuttava luettelo, jonka tallennamme sanakirjaksi. Esitän luettelon nimenomaan kokonaisuudessaan, jotta voit arvioida sen määrää.

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

Vertailun vuoksi geektimes-osiot näyttävät vaatimattomammilta:

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

Loput keskukset säilytettiin samalla tavalla. Nyt on helppo kirjoittaa funktio, joka palauttaa tuloksen riippumatta siitä, kuuluuko artikkeli geektimesiin tai profiilikeskukseen.

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

Samanlaisia ​​toimintoja tehtiin myös muille osiolle ("kehitys", "hallinto" jne.).

Käsittely

On aika aloittaa analysointi. Lataamme tietojoukon ja käsittelemme keskitintiedot.

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)

Nyt voimme ryhmitellä tiedot päiväkohtaisesti ja näyttää julkaisujen lukumäärän eri keskuksille.

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

Näytämme julkaistujen artikkelien määrän Matplotlibillä:

Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Jaoin artikkelit "geektimes" ja "geektimes only" kaaviossa, koska Artikkeli voi kuulua molempiin osioon samanaikaisesti (esimerkiksi "DIY" + "mikroohjaimet" + "C++"). Käytin nimitystä "profiili" korostaakseni profiiliartikkeleita sivustolla, vaikka ehkä englanninkielinen termi profile ei olekaan täysin oikea.

Edellisessä osassa kysyimme "nörttiaikojen vaikutuksesta", joka liittyy nörttiaikojen artikkelien maksusääntöjen muutokseen kesästä alkaen. Näytämme geektimes-artikkelit erikseen:

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

Tulos on mielenkiintoinen. Geektimes-artikkelien näyttökertojen likimääräinen suhde kokonaismäärään on jossain 1:5. Mutta vaikka katselukertojen kokonaismäärä vaihteli huomattavasti, "viihde"-artikkeleiden katselumäärä pysyi suunnilleen samalla tasolla.

Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Voit myös huomata, että "geektimes"-osion artikkelien katselukerrat laskivat edelleen sääntöjen muuttamisen jälkeen, mutta "silmäyksellä", enintään 5% kokonaisarvoista.

On mielenkiintoista tarkastella keskimääräistä näyttökertaa artikkelia kohti:

Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Viihdeartikkeleissa se on noin 40 % keskiarvon yläpuolella. Tämä ei varmaan ole yllättävää. Huhtikuun alun epäonnistuminen on minulle epäselvä, ehkä niin tapahtui, tai se on jonkinlainen jäsennysvirhe tai ehkä joku geektimesin kirjoittajista lähti lomalle ;).

Muuten, kaavio näyttää kaksi huomattavampaa huippua artikkelien katselukertojen määrässä - uudenvuoden ja toukokuun lomat.

Keskittimet

Jatketaan luvattuun keskittimien analyysiin. Listataan 20 parasta keskusasemaa katselukertojen mukaan:

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

Результат:

Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Yllättäen suosituin keskus näkemyksillä oli "Information Security", viiden parhaan johtajan joukkoon kuuluivat myös "Ohjelmointi" ja "Popular science".

Antitop miehittää Gtk:n ja kaakaon.

Habrastatistiikka: sivuston eniten ja vähiten vierailtujen osien tutkiminen

Kerron sinulle salaisuuden, myös huippunapit ovat nähtävissä täällä, vaikka näyttökertojen määrää ei näytetä siellä.

Luokitus

Ja lopuksi luvattu arvosana. Keskittimen analyysitietojen avulla voimme näyttää tämän vuoden 2019 suosituimpien keskusten suosituimmat artikkelit.

Tietoturva

Ohjelmointi

Populaari Tiede

ura

IT-lainsäädäntö

verkkokehitys

GTK

Ja lopuksi, jotta kukaan ei loukkaantuisi, annan arvosanan vähiten vieraillulle keskukselle "gtk". Vuoden sisällä se julkaistiin одна Artikkeli, joka myös "automaattisesti" on luokituksen ensimmäisellä rivillä.

Johtopäätös

Ei tule mitään johtopäätöstä. Hyvää lukemista kaikille.

Lähde: will.com

Lisää kommentti