Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

يا هبر.

В الجزء السابق تم تحليل حركة مرور حبر وفقًا للمعايير الرئيسية - عدد المقالات ومشاهداتها وتقييماتها. ومع ذلك، ظلت مسألة شعبية أقسام الموقع دون دراسة. أصبح من المثير للاهتمام النظر إلى هذا بمزيد من التفصيل والعثور على المحاور الأكثر شهرة والأكثر شعبية. أخيرًا، سألقي نظرة على تأثير geektimes بمزيد من التفصيل، وانتهي بمجموعة جديدة من أفضل المقالات بناءً على تصنيفات جديدة.

Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

بالنسبة لأولئك الذين يهتمون بما حدث، فإن الاستمرار قيد الخفض.

اسمحوا لي أن أذكركم مرة أخرى أن الإحصائيات والتقييمات ليست رسمية، وليس لدي أي معلومات داخلية. كما أنه ليس مضمونًا أنني لم أرتكب خطأً في مكان ما أو فاتني شيء ما. ولكن مع ذلك، أعتقد أن الأمر أصبح مثيرًا للاهتمام. سنبدأ بالكود أولًا، ومن لا يهتم بهذا يمكنه تخطي الأقسام الأولى.

جمع البيانات

في الإصدار الأول من المحلل اللغوي، تم أخذ عدد المشاهدات والتعليقات وتقييمات المقالات فقط في الاعتبار. وهذا جيد بالفعل، لكنه لا يسمح لك بإجراء استعلامات أكثر تعقيدًا. حان الوقت لتحليل الأقسام المواضيعية للموقع، وهذا سيسمح لك بإجراء بحث مثير للاهتمام، على سبيل المثال، معرفة كيف تغيرت شعبية قسم "C++" على مدار عدة سنوات.

تم تحسين محلل المقالة، وهو الآن يُرجع المحاور التي تنتمي إليها المقالة، بالإضافة إلى لقب المؤلف وتقييمه (يمكن القيام بالكثير من الأشياء المثيرة للاهتمام هنا أيضًا، ولكن سيأتي ذلك لاحقًا). يتم حفظ البيانات في ملف 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 عبارة عن قائمة مثيرة للإعجاب إلى حد ما، والتي نحفظها كقاموس. أنا أقدم القائمة بأكملها على وجه التحديد حتى تتمكن من تقدير حجمها.

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:

Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

لقد قمت بتقسيم المقالات "geektimes" و"geektimes فقط" في المخطط، لأن يمكن أن تنتمي المقالة إلى كلا القسمين في نفس الوقت (على سبيل المثال، "DIY" + "وحدات التحكم الدقيقة" + "C++"). لقد استخدمت تسمية "الملف الشخصي" لتسليط الضوء على مقالات الملف الشخصي على الموقع، على الرغم من أن مصطلح الملف الشخصي باللغة الإنجليزية ربما ليس صحيحًا تمامًا.

في الجزء السابق سألنا عن "تأثير 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. لكن في حين تذبذب إجمالي عدد المشاهدات بشكل ملحوظ، فإن مشاهدة المقالات "الترفيهية" ظلت عند نفس المستوى تقريبًا.

Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

يمكنك أيضًا ملاحظة أن إجمالي عدد مشاهدات المقالات في قسم "geektimes" ما زال ينخفض ​​بعد تغيير القواعد، ولكن "بالعين المجردة"، بما لا يزيد عن 5% من إجمالي القيم.

ومن المثير للاهتمام أن ننظر إلى متوسط ​​عدد المشاهدات لكل مقال:

Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

أما بالنسبة للمقالات "الترفيهية" فهي أعلى بحوالي 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()

النتيجة:

Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

والمثير للدهشة أن المحور الأكثر شعبية من حيث وجهات النظر كان "أمن المعلومات"، كما ضمت المراكز الخمسة الأولى أيضًا "البرمجة" و"العلوم الشعبية".

Antitop يحتل Gtk والكاكاو.

Habrastatistics: استكشاف الأقسام الأكثر والأقل زيارة في الموقع

سأخبرك سرًا، يمكن أيضًا رؤية المحاور العلوية هناعلى الرغم من عدم ظهور عدد المشاهدات هناك.

تصنيف

وأخيرا، التصنيف الموعود. باستخدام بيانات تحليل المحور، يمكننا عرض المقالات الأكثر شعبية للمحاور الأكثر شعبية لهذا العام 2019.

أمن المعلومات

البرمجة

العلوم الشعبية

مهنة

التشريع في مجال تكنولوجيا المعلومات

تطوير الشبكة

جتك

وأخيرا، حتى لا يشعر أحد بالإهانة، سأقدم تصنيف المركز الأقل زيارة "GTK". وفي غضون عام تم نشره واحد المقالة، التي تحتل أيضًا "تلقائيًا" السطر الأول من التصنيف.

اختتام

لن يكون هناك استنتاج. قراءة سعيدة للجميع.

المصدر: www.habr.com

إضافة تعليق