يراقب كل مكتب يحترم نفسه بانتظام الأجور من أجل التنقل في قطاع سوق العمل الذي يهمه. ومع ذلك ، على الرغم من حقيقة أن المهمة ضرورية ومهمة ، ليس الجميع مستعدًا لدفع خدمات الطرف الثالث مقابل ذلك.
في هذه الحالة ، من أجل توفير الموارد البشرية من الحاجة إلى الفرز يدويًا بشكل منتظم من خلال مئات الوظائف الشاغرة والسير الذاتية ، يكون من الأكثر كفاءة كتابة طلب صغير بمجرد أن تفعل ذلك بنفسك ، وعند الإخراج ، تقدم النتيجة في شكل لوحة تحكم جميلة مع جداول ورسوم بيانية ، والقدرة على تصفية البيانات وتحميلها. على سبيل المثال ، هذا:
يمكنك مشاهدة البث المباشر (وحتى الضغط على الأزرار) هنا .
في هذه المقالة ، سأتحدث عن كيف كتبت مثل هذا الطلب ، وما المزالق التي واجهتها على طول الطريق.
بيان المشكلة
مطلوب كتابة تطبيق يقوم بجمع بيانات مهمة hh.ru ويستأنف لمواقع محددة (مطور خلفي / أمامي / كامل المكدس ، DevOps ، QA ، مدير المشروع ، محلل الأنظمة ، إلخ) في سان بطرسبرج وإعطاء الحد الأدنى والمتوسط والقيمة القصوى لتوقعات الراتب وعروض للمتخصصين في المستويات الإعدادية والمتوسطة والعليا لكل من هذه المهن.
كان من المفترض أن يتم تحديث البيانات كل ستة أشهر تقريبًا ، ولكن ليس أكثر من مرة في الشهر.
النموذج الأول
مكتوب بلغة لامعة نقية ، مع تصميم جميل لأحذية التمهيد ، للوهلة الأولى لم يخرج شيء كثير: بسيط ، والأهم من ذلك - مفهومة. تحتوي الصفحة الرئيسية للتطبيق على الأكثر ضرورة: لكل تخصص ، يتوفر متوسط قيمة المرتبات وتوقعات الراتب (المستوى المتوسط) ، وهناك أيضًا تاريخ آخر تحديث للبيانات وزر التحديث. تحتوي علامات التبويب في الرأس - حسب عدد التخصصات قيد النظر - على جداول تحتوي على بيانات ورسوم بيانية مجمعة كاملة.
إذا رأى المستخدم أن البيانات لم يتم تحديثها لفترة طويلة ، فاضغط على زر "تحديث" للتخصص المقابل. يترك التطبيق في اللاوعي فكر لمدة 5 دقائق ، يغادر الموظف لشرب القهوة. عند عودته ، ينتظر بيانات محدثة على الصفحة الرئيسية وعلى علامة التبويب المقابلة.
سؤال للاختبار الذاتي: ما هو الخطأ في هذا النموذج الأولي؟على الأقل ، من أجل تحديث البيانات في جميع التخصصات التسعة ، يحتاج المستخدم إلى النقر فوق الزر تحديث في كل مربع - وهكذا تسع مرات.
لماذا لا تجعل زر "تحديث" واحد لكل شيء؟ الحقيقة - وهذه هي المشكلة الثانية - أنه لكل طلب ("تحديث ومعالجة البيانات على المديرين" ، "تحديث ومعالجة البيانات على ضمان الجودة" ، إلخ.) استغرق 5-10 دقائق ، وهو في حد ذاته غير مسموح به لفترة طويلة. طلب واحد لتحديث جميع البيانات سيتحول 5 دقائق إلى 45 ، أو حتى كل 60. لا يمكن للمستخدم الانتظار كثيرا.
حتى العديد من وظائف withProgress()
التي withProgress()
عمليات جمع البيانات ومعالجتها وجعلت توقعات المستخدم أكثر withProgress()
بهذه الطريقة لم تحفظ الموقف كثيرًا.
المشكلة الثالثة مع هذا النموذج الأولي هي أنه إذا أضفنا عشرات المهن الأخرى (حسنًا ، ماذا لو) سنواجه حقيقة أن المكان في الرأس ينتهي .
كانت هذه الأسباب الثلاثة كافية بالنسبة لي لإعادة التفكير تمامًا في نهج إنشاء تطبيق وتجربة مستخدم. إذا وجدت المزيد - فلا تتردد في التعليق.
كان لهذا النموذج الأولي أيضًا نقاط قوة ، وهي:
- نهج عام للواجهة ومنطق الأعمال: بدلاً من النسخ واللصق ، نزيل نفس القطع في دالة منفصلة مع معلمات.
على سبيل المثال ، هذه هي الطريقة التي يظهر بها "بلاط" أحد الاختصاصات في الصفحة الرئيسية:
كود tile <- function(title, midsal = NA, midsalres = NA, total.res = NA, total.vac = NA, updated = NA) { return( column(width = 4, h2(title), strong(" (middle):"), midsal, br(), strong(" (middle):"), midsalres, br(), strong(" :"), total.res, br(), strong(" : "), total.vac, br(), strong(" : "), updated, br(), br(), actionButton(inputId = paste0(tolower(prof), "Btn"), label = "Update", class = "btn-primary") ) ) }
- التكوين الديناميكي لواجهة المستخدم حتى المعرفات (inputId) في الكود ، من خلال
inputId = paste0(, "Btn")
، انظر المثال أعلاه. أثبت هذا النهج أنه ملائم للغاية ، لأنه كان من الضروري التهيئة بعشرات الضوابط ، مضروبًا في عدد المهن. - عملت :)
تم تخزين البيانات التي تم جمعها في ملفات .csv للمهن المختلفة ( append = TRUE
) ، ثم قراءتها من هناك عند تشغيل التطبيق. عندما ظهرت بيانات جديدة ، تمت إضافتها إلى الملف المقابل ، وتم إعادة حساب متوسط القيم.
بضع كلمات حول الفواصل
فارق بسيط مهم: الفواصل القياسية لملفات csv - فاصلة أو فاصلة منقوطة - ليست مناسبة جدًا لحالتنا ، لأنه غالبًا ما يمكنك العثور على وظائف شاغرة واستئنافات بعناوين مثل "Shvets ، ريبر ، igrets (duda ؛ html / css)". لذلك ، قررت على الفور اختيار شيء أكثر غرابة ، ووقع اختياري على |.
سار كل شيء على ما يرام حتى في المرة التالية التي بدأت فيها ، لم أجد التاريخ في العمود الذي يحتوي على العملة ، ثم انتقلت الأعمدة لأسفل ، ونتيجة لذلك ، كانت مخططات الإغلاق. بدأت أفهم. كما اتضح ، تم كسر نظامي من قبل فتاة جميلة - "محلل البيانات | محلل الأعمال". منذ ذلك الحين وأنا أستخدم \x1B
كمحدد ، الحرف ESC. لا يزال يخذل.
تعيين أم عدم التعيين؟
أثناء العمل في هذا المشروع ، أصبحت وظيفة التعيين اكتشافًا حقيقيًا بالنسبة لي: يمكنك إنشاء أسماء المتغيرات وإطارات التاريخ الأخرى بشكل ديناميكي ، رائع!
بالطبع أرغب في الاحتفاظ ببيانات المصدر في إطارات بيانات منفصلة للوظائف الشاغرة المختلفة. ولا أريد أن أكتب "designer.vac = data.frame (...)، analyst.vac = data.frame (...)". لذلك ، بدا الرمز لتهيئة هذه الكائنات عندما بدأت التطبيق كما يلي:
تعيين profs <- c("analyst", "designer", "developer", "devops", "manager", "qa") for (name in profs) { if (!exists(paste0(name, ".vac"))) assign(x = paste0(name, ".vac"), value = data.frame( URL = character() # , id = numeric() # id , Name = character() # , City = character() , Published = character() , Currency = character() , From = numeric() # . , To = numeric() # . , Level = character() # jun/mid/sen , Salary = numeric() , stringsAsFactors = FALSE )) }
لكن فرحي لم يدم طويلا. لم يعد من الممكن الوصول إلى مثل هذه الكائنات في المستقبل من خلال معلمة معينة ، وهذا ، حسب القوة ، أدى إلى ازدواجية التعليمات البرمجية. في الوقت نفسه ، ازداد عدد الكائنات بشكل كبير ، ونتيجة لذلك ، أصبح من السهل الخلط بينها وتعيين المكالمات.
لذلك اضطررت إلى استخدام نهج مختلف ، والذي انتهى به الأمر ليكون أبسط بكثير: استخدام القوائم.
هل تريد تهيئة حزمة من إطارات البيانات؟ سهل! profs <- list( devops = "devops" , analyst = c("systems+analyst", "business+analyst") , dev.full = "full+stack+developer" , dev.back = "back+end+developer" , dev.front = "front+end+developer" , designer = "ux+ui+designer" , qa = "QA+tester" , manager = "project+manager" , content = c("mathematics+teacher", "physics+teacher") ) for (name in names(profs)) { proflist[[name]] <- data.frame( URL = character() # , id = numeric() # id , Name = character() # , City = character() , Published = character() , Currency = character() , From = numeric() # . , To = numeric() # . , Level = character() # jun/mid/sen , Salary = numeric() , stringsAsFactors = FALSE ) }
يرجى ملاحظة أنه بدلاً من المتجه المعتاد مع أسماء المهن ، كما كان من قبل ، أستخدم قائمة ، والتي تتضمن في نفس الوقت استعلامات البحث ، والتي تبحث عن بيانات عن الوظائف الشاغرة وتستأنف لمهنة معينة. لذلك تمكنت من التخلص من المفتاح القبيح عند الاتصال بوظيفة البحث عن وظيفة.
تقديم جداول N والرسوم البيانية N من إطارات البيانات هذه في ضربة واحدة؟ جلالة ...أيضا ، بشكل عام ، ليس صعبا. هنا مثال كروي في الفراغ للخادم. R:
lapply(seq_along(my.list.of.data.frames), function(x) { output[[paste0(names(my.list.of.data.frames)[x], ".dt")]] <- renderDataTable({ datatable(data = my.list.of.data.frames[[names(my.list.of.data.frames)[x]]]() , style = 'bootstrap', selection = 'none' , escape = FALSE) }) output[[paste0(names(my.list.of.data.frames)[x], ".plot")]] <- renderPlot( ggplot(na.omit(my.list.of.data.frames[[names(my.list.of.data.frames)[x]]]()), aes(...)) ) })
ومن هنا الاستنتاج: القوائم هي شيء مريح للغاية يسمح لك بتقليل كمية التعليمات البرمجية والوقت الذي تستغرقه في معالجتها. (لذلك ، لا تعين.)
وفي تلك اللحظة عندما كنت مشتتًا عن إعادة البناء في حديث جو تشنغ عن لوحات المعلومات ، جاء ...
إعادة التفكير
اتضح أنه في R هناك حزمة خاصة ، شحذت لإنشاء لوحات العدادات - shinydashboard . كما يستخدم Bootstrap ويسهل تنظيم واجهة المستخدم بشريط جانبي موجز يمكن إخفاؤه تمامًا بدون أي لوحة conditionalPanel()
، مما يسمح للمستخدم بالتركيز على دراسة البيانات.
اتضح أنه إذا كانت الموارد البشرية تتحقق من البيانات مرة واحدة كل ستة أشهر ، فلن تحتاج إلى زر التحديث. لا شيء على الإطلاق. هذه ليست "لوحة معلومات ثابتة" بالضبط ، ولكنها قريبة من ذلك. يمكن تنفيذ البرنامج النصي لتحديث البيانات بشكل منفصل تمامًا عن التطبيق اللامع وتشغيله وفقًا للجدول الزمني مع برنامج الجدولة القياسي نوافذ نظام التشغيل الخاص بك.
يحل هذا مشكلتين في وقت واحد: انتظار طويل (إذا كنت تقوم بتشغيل النص البرمجي في الخلفية بانتظام ، فلن يلاحظ المستخدم عمله حتى ، ولكن دائمًا ما يرى بيانات جديدة فقط) والإجراءات الزائدة المطلوبة من المستخدم لتحديث البيانات. اعتادت أن تأخذ تسع نقرات (نقرة لكل تخصص) ، والآن لا تحتاج إلى صفر. يبدو أننا وصلنا إلى مكاسب في الكفاءة ، نسعى إلى اللانهاية!
اتضح أن الرمز في أجزاء مختلفة من التطبيق يتم تنفيذه لعدد غير متساوٍ من المرات. لن أتطرق إلى هذا بالتفصيل ؛ إذا كنت ترغب في ذلك ، فمن الأفضل أن تتعرف على التفسير المرئي في التقرير . سأحدد فقط الفكرة الرئيسية: معالجة البيانات داخل ggplot () ، والشر على الفور ، والمزيد من التعليمات البرمجية التي يمكنك إحضارها إلى المستويات العليا من التطبيق ، كان ذلك أفضل. الإنتاجية في نفس الوقت تنمو في بعض الأحيان.
في الواقع ، كلما نظرت إلى التقرير أبعد ، أدركت بشكل أوضح مدى عدم تنظيم فنغ شوي للكود في النموذج الأولي الأول الخاص بي ، وفي مرحلة ما أصبح من الواضح أن إعادة كتابة المشروع أسهل من إعادة البناء. لكن كيف تتركين عقلك عندما تستثمر فيه الكثير من الجهد؟
ما مات لا يموت
- فكرت وأعدت كتابة المشروع من الصفر وهذه المرة
- تسليم الرمز بالكامل لجمع البيانات عن الوظائف الشاغرة والسير الذاتية (في الواقع ، عملية ETL بالكامل) في برنامج نصي منفصل يمكن تشغيله بشكل مستقل عن تطبيق لامع ، مما يحفظ المستخدم من الانتظار الشاق ؛
- استخدمت ReaiveFileReader () لقراءة البيانات التي تم جمعها مسبقًا من ملفات csv ، مما يضمن ملاءمة بيانات المصدر في تطبيقي دون الحاجة إلى إعادة التشغيل وإجراءات المستخدم غير الضرورية ؛
- تخلص من تعيين () لصالح العمل مع القوائم واستخدام lapply بنشاط () حيث كانت هناك حلقات من قبل ؛
- إعادة تصميم تطبيقات واجهة المستخدم باستخدام shinydashboard ، كمكافأة - لا داعي للقلق بشأن نقص المساحة على الشاشة ؛
- عدة مرات خفضت الحجم الإجمالي للتطبيق (من 1800 إلى 360 سطرًا من التعليمات البرمجية).
الآن يعمل الحل على النحو التالي.
- يتم تشغيل البرنامج النصي ETL مرة واحدة في الشهر (إليك التعليمات حول كيفية القيام بذلك) ويمر بضمير من خلال جميع المهن ، وجمع البيانات الأولية عن الوظائف الشاغرة والسير الذاتية من سمو.
علاوة على ذلك ، يتم أخذ البيانات عن الوظائف الشاغرة من خلال واجهة برمجة التطبيقات الخاصة بالموقع (تمكنت من إعادة استخدام الشفرة جزئيًا من المشروع السابق ) ، ولكن كان عليّ ، في كل سيرة ذاتية ، تحليل صفحات الويب باستخدام حزمة rvest ، لأن الوصول إلى طريقة API المقابلة أصبح الآن مدفوعًا. يمكنك تخمين كيفية تأثير ذلك على سرعة البرنامج النصي. - يتم تجميع البيانات التي تم جمعها - يتم وصف العملية بالتفصيل مع أمثلة التعليمات البرمجية هنا . يتم حفظ البيانات التي تمت معالجتها على القرص في ملفات منفصلة من النموذج hist / profession-hist-vac.csv و hist / profession-hist-res.csv. بالمناسبة ، القيم المتطرفة في بيانات مثل هذه يمكن أن تؤدي إلى أشياء مضحكة ، كن حذراً :)
بالنسبة لكل مهنة ، يأخذ البرنامج النصي ملفًا مضافًا به بيانات تاريخية ، ويحدد الأكثر صلة - تلك التي لم يتجاوز عمرها شهرًا من تاريخ التحديث الأخير - ويقوم بإنشاء ملفات csv جديدة لنموذج data.res / profession-res-Recent.csv و data.vac / profession -vac-Recent.csv. يعمل التطبيق النهائي أيضًا مع هذه البيانات ... - ... والتي ، بعد البدء ، تقرأ محتويات السيرة الذاتية ومجلدات المهام (data.res و data.vac ، على التوالي) ، ثم تتحقق كل ساعة من التغييرات التي تطرأ على الملفات. يعد القيام بذلك باستخدام ReactiveFileReader () أكثر فاعلية من حيث الموارد وسرعة التنفيذ من استخدام validateLater (). إذا كانت هناك تغييرات في الملفات ، فسيتم تحديث الجداول التي تحتوي على بيانات المصدر تلقائيًا ، ويتم إعادة حساب متوسط القيم والرسوم البيانية ، لأنها تعتمد على القيم التفاعلية () ، أي أنه لا يلزم رمز إضافي للتعامل مع هذا الموقف.
- يوجد في الصفحة الرئيسية الآن جدول يوضح القيم الدنيا والمتوسطة والقصوى لتوقعات الراتب والعروض لكل تخصص لكل مستوى من المستويات الموجودة (كلها للمعارف التقليدية). بالإضافة إلى ذلك ، يمكنك رؤية الرسوم البيانية في علامات التبويب بمعلومات تفصيلية وتحميل البيانات بتنسيق xlsx. (أنت لا تعرف أبدًا ما ستكون هذه الأرقام مطلوبة للموارد البشرية).
هذا كل شيء. اتضح أن الزر الوحيد المتاح الآن للمستخدم على لوحة التحكم هو زر التنزيل. وهذا للأفضل: كلما قل عدد أزرار المستخدم ، قل احتماله رمي استثناء غير معالج يختلط عليهم.
بدلاً من الخاتمة
اليوم ، يقوم التطبيق بجمع وتحليل البيانات فقط لسان بطرسبرغ. مع الأخذ في الاعتبار أن أصحاب المصلحة الرئيسيين كانوا راضين ، وكان رد الفعل الأكثر تكرارًا "رائعًا ، ولكن هل يمكن القيام بذلك لموسكو؟" ، أعتبر التجربة ناجحة.
يمكنك عرض التطبيق على هذا الرابط ، وجميع التعليمات البرمجية المصدر (إلى جانب أمثلة للملفات النهائية) متاحة هنا .
بالمناسبة ، التطبيق يسمى مراقب الرواتب ، السلمون المختصر - "السلمون".