Привіт Хабр.
В
Кому цікаво, що вийшло, продовження під катом.
Ще раз нагадаю, що статистика та рейтинг не є офіційними, жодної інсайдерської інформації у мене немає. Також не гарантується, що я десь не схибив або щось не пропустив. Але все ж таки, думаю, вийшло цікаво. Ми почнемо спочатку код, кому це неактуально, перші розділи можуть пропустити.
Збір даних
У першій версії парсера враховувалася лише кількість переглядів, коментарів та рейтинг статей. Це вже непогано, але не дозволяє робити складніші запити. Настав час проаналізувати тематичні розділи сайту, це дозволить робити досить цікаві дослідження, наприклад, подивитися як змінювалася популярність розділу «С++» за кілька років.
Парсер статей був покращений, тепер він повертає хаби, до яких належить стаття, а також нік автора та його рейтинг (тут також можна зробити багато цікавого, але це потім). Дані збережені в csv-файлі приблизно такого вигляду:
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
...
Отримаємо перелік основних тематичних хабів веб-сайту.
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 і клас Str виділяють рядок між двома тегами, я використовував їх
На виході функції get_hubs отримуємо досить значний список, який зберігаємо як dictionary. Спеціально наводжу список повністю, щоб можна було оцінити його обсяг.
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'}
Для порівняння, розділи geektimes виглядають скромніше:
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'}
Аналогічно було збережено інші хаби. Тепер нескладно написати функцію, яка повертає результат, відноситься стаття до geektimes або профільного хаба.
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
Аналогічні функції були зроблені для інших розділів (розробка, адміністрування тощо).
Обробка
Настав час приступати до аналізу. Завантажуємо датасет та обробляємо дані хабів.
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)
Тепер ми можемо згрупувати дані по днях і вивести кількість публікацій з різних хаб.
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()
Виводимо кількість опублікованих статей за допомогою Matplotlib:
Я розділив у графіку статті "geektimes" і "geektimes only", т.к. стаття може належати до обох розділів одночасно (наприклад, «DIY» + «мікроконтролери» + «С++»). Позначенням profile я виділив профільні статті сайту, хоча можливо, англійський термін profile для цього не зовсім вірний.
У попередній частині запитували про "geektimes-ефект", пов'язаний зі зміною правил оплати статей для geektimes з цього літа. Виведемо окремо статті 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()
Результат цікавий. Зразкове співвідношення переглядів статей geektimes до загального приблизно 1:5. Але якщо загальна кількість переглядів помітно вагалася, перегляд «розважальних» статей тримався приблизно на одному рівні.
Також можна помітити, що загальна кількість переглядів статей розділу «geektimes» після зміни правил все ж таки впала, але «на око», не більше ніж на 5% від загальних значень.
Цікаво подивитися середню кількість переглядів на статтю:
Для «розважальних» статей воно приблизно на 40% вище за середнє. Напевно, це не дивно. Провал на початку квітня мені незрозумілий, може так і було, чи це якась помилка парсингу, а може хтось із авторів geektimes пішов у відпустку;).
До речі, на графіку видно ще два помітні піки числа переглядів статей — новорічні та травневі свята.
хаби
Перейдемо до обіцяного аналізу хабів. Виведемо топ 20 хабів за кількістю переглядів:
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()
Результат:
На диво, найпопулярнішим за переглядами виявився хаб «Інформаційна безпека», також до топ-5 лідерів входять «Програмування» та «Popular science».
Антитоп займає Gtk та Cocoa.
Скажу по секрету, топ хабів також можна побачити
Рейтинг
І нарешті обіцяний рейтинг. Використовуючи дані аналізу хабів, ми можемо вивести найпопулярніші статті з найпопулярніших хабів за цей 2019 рік.
Інформаційна безпека
Як я рік не працював у Ощадбанку 304000 переглядів, 599 коментарів, рейтинг +457.0/-14.0Викинуті на смітник розумні лампочки — цінне джерело особистої інформації 232000 переглядів, 147 коментарів, рейтинг +75.0/-11.0Шахраї та ЕЦП – все дуже погано 176000 переглядів, 778 коментарів, рейтинг +356.0/-0.0Як Мегафон спалив на мобільних підписках 166000 переглядів, 676 коментарів, рейтинг +624.0/-2.0Злом вк, двофакторна автентифікація не врятує 148000 переглядів, 332 коментарі, рейтинг +124.0/-17.0Як браузер допомагає товаришу майору 132000 переглядів, 321 коментар, рейтинг +246.0/-19.0Найбільший дамп в історії: 2,7 млрд акаунтів, з них 773 млн унікальних 123000 переглядів, 154 коментарі, рейтинг +86.0/-5.0Дорога, ми вбиваємо Інтернет 121000 переглядів, 933 коментарі, рейтинг +392.0/-83.0'Мобільний контент' безкоштовно, без смс та реєстрацій. Подробиці шахрайства від Мегафону 114000 переглядів, 478 коментарів, рейтинг +488.0/-8.0Сканер портів в особистому кабінеті Ростелекому 111000 переглядів, 194 коментарі, рейтинг +300.0/-8.0
Програмування
Про одного хлопця 167000 переглядів, 249 коментарів, рейтинг +239.0/-33.0Чим швидше ви забудете ОВП, тим краще для вас та ваших програм 129000 переглядів, 1271 коментар, рейтинг +131.0/-63.0Чому Senior Developer'и не можуть влаштуватися на роботу 119000 переглядів, 901 коментар, рейтинг +151.0/-14.0Старим тут не місце? Програмуємо після тридцяти п'яти 116000 переглядів, 649 коментарів, рейтинг +222.0/-16.0Нові мови програмування непомітно вбивають наш зв'язок із реальністю 106000 переглядів, 764 коментарі, рейтинг +164.0/-52.0Чому я навчився на своєму гіркому досвіді (за 30 років у розробці ПЗ) 101000 переглядів, 128 коментарів, рейтинг +178.0/-9.0Найрідкісніші та найдорожчі мови програмування 82900 переглядів, 119 коментарів, рейтинг +38.0/-10.0Курс лекцій з JavaScript та Node.js у КПІ 80300 переглядів, 14 коментарів, рейтинг +34.0/-2.0ІТ терміни на прикладі процесу вирощування картоплі 78000 переглядів, 86 коментарів, рейтинг +84.0/-14.0256 рядків голого C++: пишемо трасувальник променів з нуля за кілька годин 77600 переглядів, 124 коментарі, рейтинг +241.0/-0.0
Науково-популярне
Що курив конструктор: незвичайна вогнепальна зброя 236000 переглядів, 123 коментарі, рейтинг +119.0/-9.0Вчені знайшли найстаріше живе хребетне на Землі 234000 переглядів, 212 коментарів, рейтинг +82.0/-14.0Серіал 'Чорнобиль': дивитися та думати 173000 переглядів, 803 коментарі, рейтинг +164.0/-25.012-річний підліток провів реакцію ядерного синтезу у домашній лабораторії 145000 переглядів, 280 коментарів, рейтинг +126.0/-29.0Оповідь про сплав Троянди і відвалилася КРЕНКА 134000 переглядів, 244 коментарі, рейтинг +217.0/-1.0Збільши це! Сучасне збільшення дозволу 134000 переглядів, 235 коментарів, рейтинг +377.0/-1.0Софт для Boeing-737 Max писався аутсорсерами, що заробляють $9 за годину 126000 переглядів, 560 коментарів, рейтинг +153.0/-6.0Не нервуй, не поспішай, не перебивай: історія однієї трагедії 121000 переглядів, 384 коментарі, рейтинг +242.0/-4.0Математики виявили ідеальний спосіб перемноження чисел 108000 переглядів, 222 коментарі, рейтинг +173.0/-10.0Нові мови програмування непомітно вбивають наш зв'язок із реальністю 106000 переглядів, 764 коментарі, рейтинг +164.0/-52.0
Кар'єра
Як я рік не працював у Ощадбанку 304000 переглядів, 599 коментарів, рейтинг +457.0/-14.0I ruin developers' lives with my code reviews and I'm sorry 187000 переглядів, 21 коментар, рейтинг +37.0/-3.0Король розробки 179000 переглядів, 668 коментарів, рейтинг +315.0/-60.0Про одного хлопця 167000 переглядів, 249 коментарів, рейтинг +239.0/-33.0На пенсію о 22 158000 переглядів, 927 коментарів, рейтинг +259.0/-100.0Як замінити лампочку на робочому місці так, щоб тебе не звільнили? 139000 переглядів, 762 коментарі, рейтинг +200.0/-20.0Інновації російською 128000 переглядів, 612 коментарів, рейтинг +480.0/-33.0Чому Senior Developer'и не можуть влаштуватися на роботу 119000 переглядів, 901 коментар, рейтинг +151.0/-14.0'згорілі' співробітники: чи є вихід? 117000 переглядів, 398 коментарів, рейтинг +210.0/-14.0Старим тут не місце? Програмуємо після тридцяти п'яти 116000 переглядів, 649 коментарів, рейтинг +222.0/-16.0
Законодавство в IT
Шахраї та ЕЦП – все дуже погано 176000 переглядів, 778 коментарів, рейтинг +356.0/-0.0Як Мегафон спалив на мобільних підписках 166000 переглядів, 676 коментарів, рейтинг +624.0/-2.0Інновації російською 128000 переглядів, 612 коментарів, рейтинг +480.0/-33.0'Мобільний контент' безкоштовно, без смс та реєстрацій. Подробиці шахрайства від Мегафону 114000 переглядів, 478 коментарів, рейтинг +488.0/-8.0Як влада Казахстану намагається прикрити свій провал із запровадженням сертифіката 111000 переглядів, 77 коментарів, рейтинг +122.0/-14.0Як Protonmail блокується в Росії 102000 переглядів, 398 коментарів, рейтинг +418.0/-7.0Закон про ізоляцію Рунету ухвалено Держдумою у трьох читаннях 88200 переглядів, 878 коментарів, рейтинг +73.0/-18.0Як програміст банк вибирав та договори читав 87200 переглядів, 611 коментарів, рейтинг +166.0/-9.0Мінкомзв'язку схвалило законопроект про ізоляцію рунету 83600 переглядів, 364 коментарі, рейтинг +79.0/-9.0Розгорнута відповідь на коментар, а також трохи про життя провайдерів у РФ 74700 переглядів, 389 коментарів, рейтинг +290.0/-1.0
Веб-девелопмент
Старим тут не місце? Програмуємо після тридцяти п'яти 116000 переглядів, 649 коментарів, рейтинг +222.0/-16.0Як робити сайти у 2019 році 110000 переглядів, 278 коментарів, рейтинг +233.0/-11.0Вивчаємо Docker, частина 1: основи 91300 переглядів, 24 коментарі, рейтинг +52.0/-10.0Курс лекцій з JavaScript та Node.js у КПІ 80300 переглядів, 14 коментарів, рейтинг +34.0/-2.0Стажер Вася та його історії про ідемпотентність API 68900 переглядів, 160 коментарів, рейтинг +216.0/-3.0Розуміння джойнів зламано. Це точно не перетин кіл, чесно 65900 переглядів, 223 коментарі, рейтинг +138.0/-41.0Чому не потрібно витрачати свій час на створення нішевих тематичних сайтів 62700 переглядів, 243 коментарі, рейтинг +179.0/-13.0Робимо сучасний веб-додаток з нуля 62200 переглядів, 122 коментарі, рейтинг +56.0/-8.0Темний день для Vue.js 60800 переглядів, 133 коментарі, рейтинг +77.0/-6.0Навіщо сучасну веб-розробку так ускладнили? Частина 1 57700 переглядів, 319 коментарів, рейтинг +101.0/-6.0
GTK
І нарешті, щоб нікому не було прикро, наведу рейтинг найвідвідуванішого хаба «gtk». У ньому за рік було опубліковано одна стаття, вона ж "автоматом" займає перший рядок рейтингу.
Використання GtkApplication. Особливості малювання librsvg 1700 переглядів, 9 коментарів, рейтинг +9.0/-1.0
Висновок
Висновків не буде. Усім приємне читання.
Джерело: habr.com