English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
هناك بعض الحيل لمساعدة في استخراج ملفات السجلات. لنفترض أننا ننظر إلى بعض استخراجات Enterprise Splunk. يمكننا استخدام Splunk لاستكشاف البيانات. أو يمكننا الحصول على استخراج بسيط ونلعب بهذه البيانات في Python.
يبدو أن تشغيل تجارب مختلفة في Python أكثر فعالية من محاولة القيام بعمليات استكشافية مثل هذه في Splunk. لأننا نستطيع القيام بأي شيء نريد بلا قيود مع البيانات. يمكننا إنشاء نماذج إحصائية معقدة في مكان واحد.
نظريًا، يمكننا القيام بالكثير من الاستكشاف في Splunk. لديه العديد من وظائف التقارير والتحليل.
لكن...
لإستخدام Splunk نحتاج إلى الافتراض أننا نعرف ما نبحث عنه. في كثير من الحالات، لا نعرف ما نبحث عنه: نحن نستكشف. قد تكون هناك بعض الإشارات تشير إلى أن بعض عمليات API RESTful تتم ببطء، ولكن ليس هذا فقط. كيف يمكننا المضي قدمًا؟
الخطوة الأولى هي الحصول على البيانات الأصلية بتنسيق CSV. كيف يمكننا القيام بذلك؟
قراءة البيانات الأصلية
سنقوم أولاً بتغليف CSV.DictReader باستخدام بعض الوظائف الإضافية.
المحبين للبرمجة الموجهة بالأوبجكتات سيقومون بالاعتراض على هذه الاستراتيجية. "لماذا لا نوسع DictReader؟" يسألون. ليس لدي إجابة جيدة. أنا منسج في البرمجة الوظيفية وتناظر المكونات. بالنسبة لأسلوب البرمجة الموجهة بالأوبجكتات، ستحتاج إلى استخدام خليط معقد للغاية لتحقيق ذلك.
هيكلنا العامة لمعالجة السجلات هو كالتالي.
مع مفتاح open("somefile.csv") as source: rdr = csv.DictReader(source)
هذا يجعلنا قادرين على قراءة المستخرجات بتنسيق CSV من Splunk. يمكننا تمرير السطور من خلال القارئ. هذا هو الخدعة رقم 1. هذا ليس بالغاية صعب، ولكني أحبها.
مع مفتاح open("somefile.csv") as source: rdr = csv.DictReader(source) لـ row in rdr: print("{{مضيف}} {{وقت الرد}} {{مصدر}} {{خدمة}}".format_map(row))
يمكننا - إلى حد ما - تقديم البيانات الأصلية بتنسيق مفيد. إذا أردنا تحسين مظهر الناتج، يمكننا تغيير سطر التنسيق. قد يكون ذلك "{{مضيف:30s}} {{وقت الرد:8s}} {{مصدر:s}}" أو شيء مشابه.
الفرز
الحالة الشائعة هي أننا نستخرج الكثير، ولكننا نحتاج فعلاً فقط إلى النظر في مجموعة فرعية. يمكننا تغيير مرشحات Splunk، ولكن، قبل أن نكمل استكشافنا، فإن استخدام المرشحات بكثرة يبدو مزعجاً. من السهل بكثير التحقق في Python. بمجرد أن نعرف ما نحتاج إليه، يمكننا إكماله في Splunk.
مع مفتاح open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') لـ row in rdr_perf_log: print("{host} {ResponseTime} {Service}".format_map(row))
لقد أضفنا تعبيراً بيانياً لفرز الصفوف المصدر، وهو قادر على معالجة مجموعة فرعية معناة.
التصوير
في بعض الحالات، قد نضيف صفوف بيانات مصدر إضافية، ونحن لا نريد استخدامها. لذا، سنقوم بإزالة هذه البيانات من خلال تصوير كل سطر.
بشكل عام، Splunk لا ينتج صفوف فارغة. ولكن، سجلات API RESTful قد تؤدي إلى وجود الكثير من عناوين الصفوف في مجموعة البيانات، وتلك العناوين تعتمد على مفتاح الوصول البديل جزء من URI الطلب. ستتضمن هذه الصفوف سطر بيانات من الطلب الذي يستخدم هذا المفتاح البديل. للصفوف الأخرى، لا يكون هناك أي استخدام لهذا السطر في هذا الصف. لذا يجب حذف هذه الصفوف الفارغة.
يمكننا أيضًا القيام بذلك باستخدام تعبير توليد، ولكن سيكون الطول طويلاً بعض الشيء. وظيفة توليد أكثر وضوحًا في القراءة.
def project(reader): for row in reader: yield {k:v for k,v in row.items() if v}
لقد قمنا ببناء دفتر جديد من جزء من القارئ الأصلي. يمكننا استخدامه لتغليف مخرجات مرشحاتنا.
مع مفتاح open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') for row in project(rdr_perf_log): print("{host} {ResponseTime} {Service}".format_map(row))
سيقلل هذا من عدد الأعمدة غير المستخدمة التي تكون مرئية داخل جملة for.
تغيير الرموز
سيصبح الرمز row['source'] ثقيلًا قليلاً. من الأفضل استخدام types.SimpleNamespace مقارنة بالمفاتيح. هذا يجعل من الممكن استخدام row.source.
هذه هي تقنية رائعة لإنشاء أشياء أكثر فائدة.
rdr_ns= (types.SimpleNamespace(**row) forrowinreader)
يمكننا طويها إلى سلسلة من الخطوات مثل هذه.
مع مفتاح open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) for row in rdr_ns: print("{host} {ResponseTime} {Service}".format_map(vars(row)))
لاحظوا التغيير البسيط الذي قمنا به في طريقة format_map (). من خصائص SimpleNamespace، أضفنا وظيفة vars () لاستخراج المفاتيح.
يمكننا استخدام وظيفة أخرى لتحويلها إلى وظيفة للحفاظ على تناظر النصوص.
def ns_reader(reader): return (types.SimpleNamespace(**row) for row in reader)
بالطبع، يمكننا كتابتها كبنية lambda يمكن استخدامها مثل الوظيفة
ns_reader = lambda reader: (types.SimpleNamespace(**row) for row in reader)
على الرغم من أن استخدام وظيفة ns_reader () وفئة ns_reader () lambda متشابه، ولكن كتابة الوثائق الوصفية والاختبارات الوحدية doctest لـ lambda أمر صعب بعض الشيء. لهذا السبب، يجب تجنب استخدام بنية lambda.
يمكننا استخدام map(lambda row: types.SimpleNamespace(** row), reader). يحب بعض الناس هذه التعبيرات المولدة.
يمكننا استخدام جملة for مناسبة و语句 yield داخلي، ولكن يبدو أن كتابة جمل كبيرة من الأشياء الصغيرة ليس له فائدة.
لدينا العديد من الخيارات لأن Python يقدم العديد من وظائف البرمجة الوظيفية. على الرغم من أننا لا نرى Python غالباً كلغة وظيفية، لدينا عدة طرق لمعالجة التحويلات البسيطة.
التحويل: بيانات التحويل والانتشار
غالباً ما يكون لدينا قائمة واضحة للغاية من تحويلات البيانات. بالإضافة إلى ذلك، سنكون لدينا قائمة تتزايد من مشاريع البيانات المشتقة. ستكون المشاريع المشتقة ديناميكية وتعتمد على الفرضيات التي نختبرها. كلما كان لدينا تجربة أو مشكلة، قد نغير البيانات المشتقة.
كل خطوة من هذه الخطوات: الفلاتر، العرض، التحويل والانتشار هي مراحل جزء "map" من قناة map-reduce. يمكننا إنشاء بعض الدوال الصغيرة واستخدامها في map(). لأننا نقوم بتحديث عنصر ذو حالة، لا يمكننا استخدام دالة map() العادية. إذا أردنا تحقيق أسلوب برمجة وظيفي أكثر تميزاً، سنستخدم namedtuple غير القابلة للتغيير بدلاً من SimpleNamespace القابلة للتغيير.
def convert(reader): for row in reader: row._time = datetime.datetime.strptime(row.Time, "%Y-%m-%dT%H:%M:%S.%F%Z") row.response_time = float(row.ResponseTime) yield row
في مسيرتنا الاستكشافية، سنقوم بتعديل جسم دالة التحويل هذه. قد نبدأ من بعض التحويلات والانتشارات الأصغر. سنستمر في الاستكشاف باستخدام بعض الأسئلة مثل "هل هذه صحيحة؟". عندما نجد أن بعضها لا يعمل، سنخرجه.
يبدو عملية معالجتنا كما يلي:
مع مفتاح open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) rdr_converted = convert(rdr_ns) for row in rdr_converted: row.start_time = row._time - datetime.timedelta(seconds=row.response_time) row.service = some_mapping(row.Service) print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
لاحظوا تغيير جسم الجملة. دالة convert() تنتج القيم التي نصنعها. لقد أضفنا بعض المتغيرات الإضافية في循环 for، لا يمكننا التأكد منها بنسبة 100%. قبل تحديث دالة convert()، سنرى إذا كانت مفيدة (أو حتى صحيحة).
تقليل الكمية
فيما يتعلق بتقليل الكمية، يمكننا اتخاذ طريقة معالجة مختلفة قليلاً. نحتاج إلى إعادة هيكلة مثالنا السابق وجعله دالة مولدة.
def converted_log(some_file): with open(some_file) as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) rdr_converted = convert(rdr_ns) for row in rdr_converted: row.start_time = row._time - datetime.timedelta(seconds=row.response_time) row.service = some_mapping(row.Service) yield row
ثم استبدلنا print() بـ yield.
هذا جزء من إعادة الهيكلة.
for row in converted_log("somefile.csv"): print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
في الحالة المثالية، كل برامجنا تكون هكذا. نستخدم دالة إنشاء مولدة لإنشاء البيانات. عرض البيانات النهائي يحافظ على الانفصال الكامل. هذا يجعلنا قادرين على إعادة الهيكلة والتغيير في المعالجة بحرية أكبر.
الآن يمكننا القيام ببعض الأشياء، مثل جمع الصفوف في obj Counter، أو ربما حساب بعض الإحصاءات. يمكننا استخدام defaultdict(list) لتجميع الصفوف حسب الخدمة.
by_service= defaultdict(list) for row in converted_log("somefile.csv"): by_service[row.service] = row.response_time for svc in sorted(by_service): m = statistics.mean( by_service[svc] ) print( "{svc:15s} {m:.2f}".format_map(vars()) )
قررنا إنشاء كائن قائمة محددة هنا. يمكننا استخدام itertools لتجميع الاستجابات حسب الخدمة. يبدو كأنه برمجة وظيفية صحيحة، ولكن هذا التنفيذ في شكل البرمجة الوظيفية في Python يحدد بعض القيود. إما أن ن排序 البيانات (إنشاء كائن قائمة) أو إنشاء قائمة عند تجميع البيانات. عادةً يكون من السهل القيام ببعض الإحصاءات المختلفة من خلال تجميع البيانات في قائمة محددة.
نحن نقوم الآن بعمل شيئين بدلاً من طباعة كائن السطر ببساطة.
يمكننا إنشاء بعض المتغيرات المحلية مثل svc وm. يمكننا إضافة التغيرات أو القياسات بسهولة.
استخدام دالة vars () بدون معلمات، سينشئ دفتر من المتغيرات المحلية.
سلوك استخدام vars () بدون أي معلمات يشبه locals () وسيلة مريحة. يسمح لنا بإنشاء أي متغيرات محلية نريدها بسهولة ودمجها في الصادرات المشكلة. يمكننا التدخل في جميع الأساليب الإحصائية التي نعتقد أنها قد تكون مرتبطة.
بما أن عملية المعالجة الأساسية لدينا موجهة ضد السطر في converted_log (“somefile.csv”)، يمكننا استكشاف العديد من خيارات المعالجة من خلال سكريبت صغير وسهل التغيير. يمكننا استكشاف بعض الافتراضات لتحديد لماذا يتم معالجة بعض RESTful API بسرعة، بينما يتم معالجة بعضها ببطء.
الخلاصة
ما ذكرته أعلاه هو تحليل البيانات الاستكشافي في Python (الطريقة الوظيفية) الذي قدمته لك الإدارة، وأتمنى أن يكون مفيدًا لكم، إذا كان لديكم أي أسئلة، فلا تترددوا في ترك تعليق، وسأقوم بالرد على أسئلتكم في أقرب وقت ممكن. أود أيضًا أن أعبر عن شكري لتعليمات دليل النفخ!
البيان: محتوى هذا المقال تم جمعه من الإنترنت، ويتمتع ملكية حقوق الطبع والنشر لأصحابها، ويتم جمعه من قبل المستخدمين عبر الإنترنت الذين يقدمون المساهمات بشكل تلقائي ويعملون على تحميلها، ويتمتع الموقع بعدم امتلاك حقوق الملكية، ولا يتم تعديل المحتوى بشكل إنساني، ولا يتحمل أي مسؤولية قانونية مرتبطة بذلك. إذا اكتشفتم أي محتوى يشتبه في حقوق الطبع والنشر، فيرجى إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (باستبدال # بـ @ عند إرسال البريد الإلكتروني) لإبلاغنا، وقدموا الأدلة ذات الصلة، وسيتم حذف المحتوى المشبوه بشكل فوري إذا تم التحقق من صحة الشكوى.