دورة حياة مقالة عن هابري: نكتب هاباربراسر

مرحباً هبر!

ربما فكر العديد من القراء والمؤلفين العاديين في دورة حياة المقالات المنشورة هنا. وعلى الرغم من حدسي هذا واضح أكثر أو أقل (من الواضح ، على سبيل المثال ، أن المقالة في الصفحة الأولى لديها الحد الأقصى لعدد المشاهدات) ، ولكن كم على وجه التحديد؟



لجمع الإحصاءات ، سوف نستخدم بيثون ، بانداس ، ماتبلوتليب و Raspberry Pi.

أولئك الذين يهتمون بما جاء منه ، من فضلك ، تحت القط.

جمع البيانات


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

يمكن لأولئك الذين يرغبون في رؤية النتائج على الفور ، الانتقال إلى الجزء الثالث ، ولكن في الوقت الحالي سيكون الأمر يتعلق بالبرمجة.

الخطة العامة: سنقوم بتحليل البيانات اللازمة من صفحة الويب ، وحفظها باستخدام ملف CSV ، ومعرفة ما نحصل عليه لمدة عدة أيام. أولاً ، قم بتحميل نص المقالة (تم حذف الاستثناء من أجل الوضوح):

link = "https://habr.com/ru/post/000001/" f = urllib.urlopen(link) data_str = f.read() 

نحتاج الآن إلى استخراج البيانات من سطر data_str (بالطبع ، بتنسيق HTML). افتح الكود المصدري في المتصفح (تمت إزالة العناصر غير المبدئية):

 <ul class="post-stats post-stats_post js-user_" id="infopanel_post_438514"> <li class="post-stats__item post-stats__item_voting-wjt"> <span class="voting-wjt__counter voting-wjt__counter_positive js-score" title="  448: ↑434  ↓14">+420</span> </li> <span class="btn_inner"><svg class="icon-svg_bookmark" width="10" height="16"><use xlink:href="https://habr.com/images/1550155671/common-svg-sprite.svg#book" /></svg><span class="bookmark__counter js-favs_count" title=" ,    ">320</span></span> <li class="post-stats__item post-stats__item_views"> <div class="post-stats__views" title=" "> <span class="post-stats__views-count">219k</span> </div> </li> <li class="post-stats__item post-stats__item_comments"> <a href="https://habr.com/ru/post/438514/#comments" class="post-stats__comments-link" <span class="post-stats__comments-count" title=" ">577</span> </a> </li> <li class="post-stats__item"> <span class="icon-svg_report"><svg class="icon-svg" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true" version="1.1" role="img"><path d="M0 0h32v32h-32v-32zm14 6v12h4v-12h-4zm0 16v4h4v-4h-4z"/></svg> </span> </li> </ul> 

من السهل أن نرى أن النص الذي نحتاجه موجود داخل الكتلة '<ul class = "post-stats post-stats_post js-user_>' ، وأن العناصر الضرورية في كتل تحتوي على أسماء تصويت - wjt__counter ، bookmark__counter ، post-stats__views-count و post- stats__comments-count. بالاسم ، كل شيء واضح تماما.

سنرث الطبقة str ونضيف إليها طريقة استخراج السلسلة الفرعية الموجودة بين العلامتين:

 class Str(str): def find_between(self, first, last): try: start = self.index(first) + len(first) end = self.index(last, start) return Str(self[start:end]) except ValueError: return Str("") 

يمكنك الاستغناء عن الميراث ، لكن هذا سيسمح لك بكتابة كود أكثر إيجازًا. مع ذلك ، يناسب كل استخراج البيانات في 4 خطوط:

 votes = data_str.find_between('span class="voting-wjt__counter voting-wjt__counter_positive js-score"', 'span').find_between('>', '<') bookmarks = data_str.find_between('span class="bookmark__counter js-favs_count"', 'span').find_between('>', '<') views = data_str.find_between('span class="post-stats__views-count"', 'span').find_between('>', '<') comments = data_str.find_between('span class="post-stats__comments-count"', 'span').find_between('>', '<') 

لكن هذا ليس كل شيء. كما ترون ، يمكن تخزين عدد التعليقات أو المشاهدات كسلسلة مثل "12.1k" ، والتي لا تترجم مباشرة إلى int.

أضف وظيفة لتحويل مثل هذه السلسلة إلى رقم:

 def to_int(self): s = self.lower().replace(",", ".") if s[-1:] == "k": # "1.23k" => 1.23*1000 => 1230 return int(1000*float(s.replace("k", ""))) return int(self) 

يبقى فقط لإضافة الطابع الزمني ، ويمكنك حفظ البيانات في ملف CSV:

 timestamp = strftime("%Y-%m-%dT%H:%M:%S.000", gmtime()) str_out = "{},votes:{},bookmarks:{},views:{},comments:{};".format(timestamp, votes.to_int(), bookmarks.to_int(), views.to_int(), comments.to_int()) 

نظرًا لأننا مهتمون بتحليل العديد من المقالات ، فإننا نضيف القدرة على تحديد رابط من خلال سطر الأوامر. سنقوم أيضًا بإنشاء اسم ملف السجل بمعرف المقالة:

 link = sys.argv[1] # "https://habr.com/ru/post/000001/" link_path = urlparse.urlparse(link.strip("/")).path # https://habr.com/ru/post/000001/ => /ru/post/000001 link_parts = os.path.split(link_path) # /ru/post/000001=> ('/ru/post', '000001') log_file = "habr_log%s.txt" % link_parts[1] 

والخطوة الأخيرة جدا. نحن نخرج الشفرة في الوظيفة ، في الحلقة التي نقوم باستطلاع البيانات بها ، ونكتب النتائج في السجل.

 delay_s = 5*60 while True: # Get data data_str = get_as_str(link) data_csv = extract_as_csv(data_str) print data_csv # Append data to log with open(log_file, "a") as logfile: logfile.write(data_csv + '\n') sleep(delay_s) 

كما ترى ، تم تحديث البيانات كل 5 دقائق حتى لا يتم إنشاء حمل على الخادم. قمت بحفظ ملف البرنامج تحت اسم habr_parse.py ، عند بدء تشغيله ، سيتم حفظ البيانات حتى يتم إغلاق البرنامج.

علاوة على ذلك ، يُنصح بحفظ البيانات ، على الأقل لبضعة أيام. بسبب نحن مترددون في إبقاء الكمبيوتر قيد التشغيل لعدة أيام ، فنحن نأخذ Raspberry Pi - سيكون لديه طاقة كافية لمثل هذه المهمة ، وعلى عكس الكمبيوتر الشخصي ، فإن Raspberry Pi لا يصدر ضوضاء ولا يستهلك كهرباءًا تقريبًا. نذهب إلى SSH ونشغل نصنا:

 nohup python habr_parse.py https://habr.com/ru/post/0000001/ & 

يترك الأمر nohup البرنامج النصي في الخلفية بعد إغلاق وحدة التحكم.

على سبيل المكافأة ، يمكنك تشغيل خادم http في الخلفية عن طريق إدخال الأمر "nuhup python -m SimpleHTTPServer 8000 &". سيتيح لك ذلك مشاهدة النتائج مباشرة في المتصفح في أي وقت ، وفتح رابط النموذج http://192.168.1.101:8000 (قد يكون العنوان مختلفًا بالطبع).

يمكنك الآن تشغيل Raspberry Pi والعودة إلى المشروع في غضون أيام قليلة.

تحليل البيانات


إذا كان كل شيء قد تم بشكل صحيح ، فيجب أن يكون الإخراج مثل هذا السجل:

 2019-02-12T22:26:28.000,votes:12,bookmarks:0,views:448,comments:1; 2019-02-12T22:31:29.000,votes:12,bookmarks:0,views:467,comments:1; 2019-02-12T22:36:30.000,votes:14,bookmarks:1,views:482,comments:1; 2019-02-12T22:41:30.000,votes:14,bookmarks:2,views:497,comments:1; 2019-02-12T22:46:31.000,votes:14,bookmarks:2,views:513,comments:1; 2019-02-12T22:51:32.000,votes:14,bookmarks:2,views:527,comments:1; 2019-02-12T22:56:32.000,votes:14,bookmarks:2,views:543,comments:1; 2019-02-12T23:01:33.000,votes:14,bookmarks:2,views:557,comments:2; 2019-02-12T23:06:34.000,votes:14,bookmarks:2,views:567,comments:3; 2019-02-12T23:11:35.000,votes:13,bookmarks:2,views:590,comments:4; ... 2019-02-13T02:47:03.000,votes:15,bookmarks:3,views:1100,comments:20; 2019-02-13T02:52:04.000,votes:15,bookmarks:3,views:1200,comments:20; 

دعونا نرى كيف يمكن معالجتها. للبدء ، قم بتحميل csv في dataframe الباندا:

 import pandas as pd import numpy as np import datetime log_path = "habr_data.txt" df = pd.read_csv(log_path, header=None, names=['datetime', 'votes', 'bookmarks', 'views', 'comments']) 

أضف وظائف للتحويل والتوسيط ، واستخرج البيانات اللازمة:

 def to_float(s): # "bookmarks:22" => 22.0 num = ''.join(i for i in s if i.isdigit()) return float(num) def running_mean(l, N=2): sum = 0 result = len(l)*[0] for i in range(0, N): sum = sum + l[i] result[i] = sum / (i + 1) for i in range(N, len(l)): sum = sum - l[i - N] + l[i] result[i] = sum / N return result log_path = "habr_data.txt" df = pd.read_csv(log_path, header=None, names=['datetime', 'votes', 'bookmarks', 'views', 'comments']) print df.head() dates = pd.to_datetime(df['datetime'], format='%Y-%m-%dT%H:%M:%S.%f') dates += datetime.timedelta(hours=3) views = df["views"].map(to_float, na_action=None) views_avg = running_mean(views.values.tolist()) votes = df["votes"].map(to_float, na_action=None) bookmarks = df["bookmarks"].map(to_float, na_action=None) comments = df["comments"].map(to_float, na_action=None) viewspervotes = views/votes viewsperbookmarks = views/bookmarks 

يعد التشغيل ضروريًا لأن عدد مرات المشاهدة على الموقع معروض بزيادات 100 ، مما يؤدي إلى جدول "تمزيقه". من حيث المبدأ ، هذا ليس ضروريًا ، ولكن مع المتوسط ​​يبدو أفضل. تتم إضافة منطقة موسكو الزمنية أيضًا في الكود (الوقت الذي أصبح فيه Raspberry Pi هو GMT).

أخيرًا ، يمكنك عرض الرسوم البيانية ومعرفة ما حدث.

 import matplotlib.pyplot as plt # Draw fig, ax = plt.subplots() # plt.plot(dates, votes, 'ro', markersize=1, label='Votes') # plt.plot(dates, bookmarks, 'go', markersize=1, label='Bookmarks') # plt.plot(dates, comments, 'go', markersize=1, label='Comments') ax.plot(dates, views_avg, 'bo', markersize=1, label='Views') # plt.plot(dates, views_g, 'bo', markersize=1, label='Views') # plt.plot(dates, viewspervotes, 'ro', markersize=1, label='Views/Votes') # plt.plot(dates, viewsperbookmarks, 'go', markersize=1, label='Views/Bookmarks') ax.xaxis.set_major_formatter(mdates.DateFormatter("%d-%d-%Y")) ax.xaxis.set_major_locator(mdates.DayLocator()) ax.xaxis.set_minor_locator(mdates.HourLocator(interval=1)) fig.autofmt_xdate() plt.legend(loc='best') plt.xticks(rotation=45, ha="right") plt.tight_layout() plt.show() 

النتائج


في بداية كل رسم بياني ، توجد مساحة فارغة ، والتي يتم شرحها ببساطة - عندما تم إطلاق البرنامج النصي ، تم نشر المقالات بالفعل ، وبالتالي لم يتم جمع البيانات من نقطة الصفر. تمت إضافة نقطة "الصفر" يدويًا من وصف وقت نشر المقال.

يتم إنشاء جميع المخططات المنصوص عليها بواسطة matplotlib والرمز أعلاه.

وفقا للنتائج ، قسمت المقالات التي تم التحقيق فيها إلى 3 مجموعات. الانقسام مشروط ، رغم أنه لا يزال لديه بعض المعنى.

المادة الساخنة


يتناول هذا المقال موضوعًا شائعًا وذو صلة بالموضوع ، بعنوان "How MTS يقتطع المال" أو "Roskomnadzor block porn git hub".

تحتوي هذه المقالات على عدد كبير من المشاهدات والتعليقات ، لكن "الضجيج" يستمر كحد أقصى لعدة أيام. يمكنك أيضًا رؤية اختلاف بسيط في نمو عدد مرات المشاهدة خلال النهار والليل (ولكن ليس بنفس الأهمية كما هو متوقع - على ما يبدو ، تتم قراءة Habr من جميع المناطق الزمنية تقريبًا).





ينمو عدد "الإعجابات" بشكل كبير ، بينما ينمو عدد الإشارات المرجعية بشكل أبطأ. هذا منطقي ، لأنه قد يعجب شخص ما بالمقال ، لكن خصوصية النص لا تحتاج إلى وضع إشارة مرجعية عليه.



تظل نسبة مرات المشاهدة والإعجابات كما هي تقريبًا وهي 400: 1:



مقالة "فنية"


هذه مقالة أكثر تخصصًا ، مثل "إعداد البرامج النصية للعقدة JS". مثل هذه المقالة ، بالطبع ، تكتسب مرات أقل عددًا من الآراء "الساخنة" ، وعدد التعليقات أيضًا أصغر بشكل ملحوظ (في هذه الحالة كان هناك 4 فقط).





لكن النقطة التالية أكثر إثارة للاهتمام: عدد "الإعجابات" لمثل هذه المقالات يزداد بشكل أبطأ بشكل ملحوظ من عدد "الإشارات المرجعية". في ما يلي الاتجاه الآخر مقارنة بالإصدار السابق - يجد الكثيرون أن هذه المقالة مفيدة لحفظها في المستقبل ، لكن لا يتعين على القارئ النقر على "أعجبني" على الإطلاق.



بالمناسبة ، في هذه المرحلة أود أن ألفت انتباه مسؤولي الموقع - عند حساب تصنيفات المقالات ، يجب أن تحسب الإشارات المرجعية بالتوازي مع الإعجابات (على سبيل المثال ، دمج المجموعات حسب OR). بخلاف ذلك ، يؤدي هذا إلى تحيز في التصنيف ، عندما يكون لمقال مشهور العديد من الإشارات المرجعية (أي أن القراء أعجبهم بالتأكيد) ، لكن هؤلاء الناس نسوا أو كانوا كسالى للغاية للنقر فوق "أعجبني".

وأخيرًا ، نسبة المشاهدات وما يعجبه: يمكنك أن ترى أنها أعلى بشكل ملحوظ مما كانت عليه في التجسيد الأول وهي تقريبًا 150: 1 ، أي يمكن أيضًا اعتبار جودة المحتوى بشكل غير مباشر أعلى.



مقالة "مشبوهة" (لكن هذا غير دقيق)


بالنسبة للمقال التالي الذي تم فحصه ، زاد عدد "الإعجابات" بمقدار الثلث في فاصل زمني مدته 5 دقائق (على الفور بواقع 10 مع ما مجموعه 30 سجلًا لعدة أيام).



يمكن للمرء أن يشك في الغش ، ولكن "نظرية الطابور" من حيث المبدأ تسمح لمثل هذه الزيادات. أو ربما أرسل المؤلف الرابط إلى جميع أصدقائه العشرة ، وهو أمر لا تحظره بالطبع.

الاستنتاجات


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

خلاف ذلك ، من المرجح أن يكون هذا التحليل "الجمعة" بطبيعته ، وبطبيعة الحال ، لا يتظاهر بأنه دراسة جادة. آمل أيضًا أن يجد شخص ما شيئًا جديدًا في استخدام Pandas و Matplotlib.

شكرا لاهتمامكم

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


All Articles