Habrastatistique: explorer les sections les plus et les moins visitées du site

Salut, Habr.

Dans la partie précédente , la fréquentation de Habr a été analysée par les principaux paramètres - le nombre d'articles, leurs opinions et leurs notes. Cependant, la question de la popularité des sections du site n'a pas été examinée. Il est devenu intéressant d'examiner cela plus en détail et de trouver les hubs les plus populaires et les plus impopulaires. Enfin, j'examinerai plus en détail «l'effet geektimes» et, à la fin, les lecteurs recevront une nouvelle sélection des meilleurs articles sur les nouvelles cotes.



Peu importe ce qui s'est passé, a continué sous la coupe.

Je vous rappelle encore une fois que les statistiques et les notes ne sont pas officielles, je n'ai pas d'informations privilégiées. Il n'est pas non plus garanti que je ne me sois pas trompé quelque part ou que je n'ai rien manqué. Mais quand même, je pense que cela s'est avéré intéressant. Nous allons commencer par le code, pour qui cela n'est pas pertinent, les premières sections peuvent être ignorées.

Collecte de données


Dans la première version de l'analyseur, seuls le nombre de vues, de commentaires et la note des articles étaient pris en compte. C'est déjà bien, mais cela ne vous permet pas de faire des requêtes plus complexes. Il est temps d'analyser les sections thématiques du site, cela vous permettra de faire des recherches assez intéressantes, par exemple, de voir comment la popularité de la section "C ++" a changé au fil des ans.

L'analyseur d'article a été amélioré, il renvoie maintenant les hubs auxquels l'article appartient, ainsi que le surnom de l'auteur et sa note (ici, vous pouvez également faire beaucoup de choses intéressantes, mais plus tard). Les données sont enregistrées dans un fichier csv d'environ le type suivant:

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

Obtenez une liste des principaux centres thématiques du site.

 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 fonction find_between et la classe Str mettent en évidence une ligne entre deux balises, je les ai utilisées plus tôt . Les hubs thématiques sont marqués d'un "*", donc ils sont faciles à mettre en évidence, vous pouvez également décommenter les lignes correspondantes pour obtenir des sections d'autres catégories.

À la sortie de la fonction get_hubs, nous obtenons une liste assez impressionnante, que nous enregistrons sous forme de dictionnaire. Je cite spécialement toute la liste pour que son volume puisse être estimé.

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

À titre de comparaison, les sections geektimes semblent plus modestes:

 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 même, les hubs restants ont été enregistrés. Maintenant, il est facile d'écrire une fonction qui renvoie le résultat, l'article fait référence à des geektimes ou à un hub de profil.

 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 

Des fonctions similaires ont été faites pour d'autres sections («développement», «administration», etc.).

Traitement


Il est temps de commencer l'analyse. Téléchargez l'ensemble de données et traitez les données à partir des concentrateurs.

 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) 

Nous pouvons maintenant regrouper les données par jour et afficher le nombre de publications par différents 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() 

Affichez le nombre d'articles publiés à l'aide de Matplotlib:



J'ai divisé les articles «geektimes» et «geektimes only» dans le graphique, car un article peut appartenir aux deux sections simultanément (par exemple, "DIY" + "microcontrôleurs" + "C ++"). Avec la désignation «profile», j'ai mis en évidence les articles de profil du site, bien qu'il soit possible que le terme anglais profile ne soit pas tout à fait correct pour cela.

Dans la partie précédente, nous avons posé des questions sur «l'effet geektimes» associé au changement des règles de paiement des articles pour les geektimes à partir de cet été. Nous dérivons des articles geektimes distincts:

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

Le résultat est intéressant. Le rapport approximatif des vues des articles geektimes au total quelque part autour de 1: 5. Mais si le nombre total de vues fluctuait sensiblement, alors le visionnage d'articles "divertissants" était maintenu à peu près au même niveau.



Vous pouvez également remarquer que le nombre total de vues d'articles dans la section "geektimes" après avoir changé les règles a encore baissé, mais "à l'oeil nu", pas plus de 5% des valeurs totales.

Il est intéressant de voir le nombre moyen de vues par article:



Pour les articles "divertissants", il est supérieur d'environ 40% à la moyenne. Ce n'est probablement pas surprenant. L'échec au début du mois d'avril n'est pas clair pour moi, peut-être que c'était, ou est-ce une sorte d'erreur d'analyse, ou peut-être qu'un des auteurs geektimes est parti en vacances;).

Soit dit en passant, sur le graphique, il y a deux pics plus visibles dans le nombre de vues d'articles - Nouvel an et vacances de mai.

Hubs


Passons à l'analyse promise des hubs. Nous afficherons les 20 principaux hubs par le nombre de vues:

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

Résultat:



Étonnamment, le hub «Sécurité de l'information» s'est avéré être le plus populaire en termes de visualisation, également «Programmation» et «Science populaire» sont dans le top 5 des leaders.

Antitope prend Gtk et Cocoa.



Je vais vous dire un secret, les hubs supérieurs peuvent également être vus ici , bien que le nombre de vues n'y soit pas affiché.

Évaluation


Et enfin, la note promise. En utilisant les données de l'analyse des hubs, nous pouvons afficher les articles les plus populaires sur les hubs les plus populaires pour cette année 2019.

Sécurité de l'information


Programmation


Science populaire


Carrière


Législation informatique


Développement Web


GTK

Et enfin, pour ne choquer personne, je vais vous donner la note du hub le moins visité "gtk". Dans ce document, un article a été publié au cours de l'année, il occupe également «automatiquement» la première ligne de la note.


Conclusion


Il n'y aura aucune conclusion. Bonne lecture à tout le monde.

Source: https://habr.com/ru/post/fr467429/


All Articles