Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

Hei Habr.

В forrige del Habrs trafikk ble analysert i henhold til hovedparametrene - antall artikler, deres visninger og rangeringer. Spørsmålet om populariteten til nettstedsdelene forble imidlertid uundersøkt. Det ble interessant å se nærmere på dette og finne de mest populære og mest upopulære knutepunktene. Til slutt skal jeg se på geektimes-effekten mer detaljert, og avslutter med et nytt utvalg av de beste artiklene basert på nye rangeringer.

Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

For de som er interessert i hva som skjedde, er fortsettelsen under kuttet.

La meg minne deg nok en gang om at statistikken og vurderingene ikke er offisielle, jeg har ingen innsideinformasjon. Det er heller ikke garantert at jeg ikke har gjort en feil et sted eller gått glipp av noe. Men likevel synes jeg det ble interessant. Vi starter med koden først, de som ikke er interessert i dette kan hoppe over de første delene.

Datainnsamling

I den første versjonen av parseren ble det kun tatt hensyn til antall visninger, kommentarer og artikkelvurderinger. Dette er allerede bra, men det lar deg ikke gjøre mer komplekse spørsmål. Det er på tide å analysere de tematiske delene av nettstedet; dette vil tillate deg å gjøre ganske interessant forskning, for eksempel se hvordan populariteten til "C++"-delen har endret seg over flere år.

Artikkelparseren har blitt forbedret, nå returnerer den knutepunktene som artikkelen tilhører, i tillegg til forfatterens kallenavn og hans vurdering (mange interessante ting kan gjøres her også, men det kommer senere). Dataene lagres i en csv-fil som ser omtrent slik ut:

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 vil motta en liste over de viktigste tematiske knutepunktene på nettstedet.

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-funksjonen og Str-klassen velger en streng mellom to tagger, jeg brukte dem tidligere. Tematiske hubs er merket med en "*" slik at de enkelt kan fremheves, og du kan også fjerne kommentarer til de tilsvarende linjene for å få deler av andre kategorier.

Utdataene til get_hubs-funksjonen er en ganske imponerende liste, som vi lagrer som en ordbok. Jeg presenterer spesifikt listen i sin helhet slik at du kan anslå volumet.

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

Til sammenligning ser geektimes-delene mer beskjedne 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 resterende navene ble bevart på samme måte. Nå er det enkelt å skrive en funksjon som returnerer resultatet enten artikkelen tilhører geektimes eller en profilhub.

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

Lignende funksjoner ble laget for andre seksjoner ("utvikling", "administrasjon", etc.).

Processing

Det er på tide å begynne å analysere. Vi laster inn datasettet og behandler hubdataene.

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)

Nå kan vi gruppere dataene etter dag og vise antall publikasjoner for ulike hubs.

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 viser antall publiserte artikler ved hjelp av Matplotlib:

Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

Jeg delte artiklene "geektimes" og "geektimes only" i diagrammet, fordi En artikkel kan tilhøre begge seksjonene samtidig (for eksempel "DIY" + "microcontrollers" + "C++"). Jeg brukte betegnelsen «profil» for å fremheve profilartikler på siden, selv om kanskje det engelske uttrykket profile for dette ikke er helt korrekt.

I forrige del spurte vi om "geektimes-effekten" knyttet til endringen i betalingsreglene for artikler for nerdetider som starter i sommer. La oss vise artiklene om geektimes 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 er interessant. Det omtrentlige forholdet mellom visninger av geektimes-artikler og totalen er et sted rundt 1:5. Men mens det totale antallet visninger svingte merkbart, holdt visningen av «underholdning»-artikler seg på omtrent samme nivå.

Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

Du kan også legge merke til at det totale antallet visninger av artikler i "geektimes"-delen fortsatt falt etter å ha endret reglene, men "etter øyet", med ikke mer enn 5% av de totale verdiene.

Det er interessant å se på gjennomsnittlig antall visninger per artikkel:

Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

For «underholdning»-artikler er det omtrent 40 % over gjennomsnittet. Dette er nok ikke overraskende. Feilen i begynnelsen av april er uklar for meg, kanskje det er det som skjedde, eller det er en slags parsefeil, eller kanskje en av geektime-forfatterne dro på ferie ;).

Grafen viser forresten to mer merkbare topper i antall visninger av artikler - nyttårs- og maiferien.

Huber

La oss gå videre til den lovede analysen av huber. La oss liste de 20 beste knutepunktene etter antall visninger:

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:

Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

Overraskende nok var det mest populære knutepunktet når det gjelder synspunkter "Informasjonssikkerhet"; topp 5-lederne inkluderte også "Programmering" og "Populærvitenskap".

Antitop okkuperer Gtk og Cocoa.

Habrastatistikk: utforske de mest og minst besøkte delene av nettstedet

Jeg skal fortelle deg en hemmelighet, toppnavene kan også sees her, selv om antall visninger ikke vises der.

Rangering

Og til slutt, den lovede vurderingen. Ved å bruke hubanalysedata kan vi vise de mest populære artiklene for de mest populære hubene for dette året 2019.

Informasjonssikkerhet

Programmering

Populærvitenskap

karriere

Lovverk innen IT

webutvikling

GTK

Og til slutt, slik at ingen blir fornærmet, vil jeg gi vurderingen av det minst besøkte navet "gtk". I løpet av et år ble den publisert одна Artikkelen, som også "automatisk" opptar den første linjen i vurderingen.

Konklusjon

Det blir ingen konklusjon. God lesing alle sammen.

Kilde: www.habr.com

Legg til en kommentar