تفصيل المقال
معالجة ملفات CSV الضخمة في Laravel
استكشف المقال كاملاً ضمن تدفق قراءة منظم وفهرس محتويات.
معالجة ملفات CSV ضخمة في Laravel.أعمل بانتظام مع ملفات CSV المكونة من ملايين الأسطر وعشرات الحقول. يمكن أن يصل وزن هذه الملفات إلى 2 إلى 3 غيغابايت. يجب التعامل مع هذه الكميات بعناية وكفاءة. سأشارككم الإستراتيجية التي طورتها لهذا الغرض خلال العامين الماضيين.
استخدم مكتبة CSV.
قد تبدو توصيتي الأولى والأكثر أهمية بسيطة، ولكن بالتأكيد كانت لدينا جميعًا فكرة ” هذا مجرد ملف CSV، ما الذي يمكن أن يكون صعبًا هناك – سأقوم بتحليله بنفسي! “.
الإجابة الصحيحة: الأمر صعب!
إذا كنت لا تصدقني، فاقرأ كتاب “مفاهيم المبرمجين الخاطئة حول CSV” واسأل نفسك: هل أنت مستعد حقًا للتعامل مع كل هذه التفاصيل الدقيقة؟ لم أكن أعرف حتى عن معظمهم!
لذلك فقط استخدم أي محلل CSV مناسب. أوصي بـ League/CSV. إنه سهل الاستخدام بشكل رائع وسهل الاستخدام واختباره ودعمه بشكل فعال.
لا تثق أبدًا بمزود البيانات.
يتم تصدير ملفات CSV التي نقوم بتحميلها إلى نظامنا بشكل أساسي من أنظمة أخرى، بما في ذلك الأنظمة الحكومية. قد يبدو الأمر قاسيًا، “لا تثق أبدًا بمزود البيانات”، ولكن كن مطمئنًا أنهم سيغيرون شيئًا ما بشأنهم باستمرار. ومن الأفضل أن تكون مستعدًا لذلك.
وبدون إشعار، يمكنهم:
- تغيير ترتيب الحقول
- تغيير اسم الحقل
- إضافة/إزالة حقل
- البدء في الهروب من بعض الأحرف
نقوم بتنزيل البيانات تلقائيًا وفقًا لجدول زمني خارج ساعات العمل. يتلقى النظام الملف ويفتحه ويستورد البيانات. وهذا يعني أننا بحاجة للتأكد من أن الملف بالتنسيق الصحيح قبل أن نبدأ بتنزيل ملايين الأسطر.
تناسق الرؤوس.
إذا كنت تستخدم حزمة League / CSV، فلن يهم ترتيب الحقول في المصدر، لأنك ستنتهي بمصفوفة ترابطية لكل حقل. ولكن لا يهم إذا تغير اسم الحقل أو إذا تم حذفه تمامًا. وقد يؤدي ذلك إلى حدوث أخطاء عند عدم توفر البيانات الضرورية.
من المهم تحديد هذه المشكلات بدلاً من تخطي البيانات الفارغة.
نقوم بذلك بالطريقة التالية: نقوم بإعداد “توقعاتنا” لرؤوس الحقول، وإذا لم تتطابق، فإننا نطرح استثناءً ونوقف عملية الاستيراد.
عادةً ما نحصل عليها مباشرةً من الملف:
وظيفة محمية validateHeaders ( مسار $، $ متوقع رؤوس )
{
$ headersFromFile = Trim ( shell_exec (“head -1 $ path”) );
إذا ( $ المتوقع الرؤوس! == $ الرؤوس من الملف ) {
السجل:: خطأ ( ‘المتوقع:’ . $ ExpectedHeaders ) ;
السجل :: خطأ ( ‘Actual:’ . $ HeadersFromFile ) ;
طرح استثناء جديد (“الرؤوس غير متطابقة. تحقق من السجلات.” ) ;
يمكنك أيضًا التحقق من السجل الأول الذي تم إرجاعه بواسطة قارئ الكائن.
وظيفة محمية validateHeaders (قارئ $، رؤوس متوقعة $)
{
$ headersFromFile = array_keys ( $ Reader- > fetchOne ()) ;
إذا ( $ المتوقع الرؤوس! == $ الرؤوس من الملف ) {
السجل :: خطأ ( ‘المتوقع:’ . $ هذا – > originalHeaders ) ;
السجل :: خطأ ( ‘Actual:’ . $ HeadersFromFile ) ;
طرح استثناء جديد (“الرؤوس غير متطابقة. تحقق من السجلات.” ) ;
أوصي بالتحقق من الرؤوس للتأكد من أنك تعمل مع البيانات الصحيحة.
لا توجد عناوين رئيسية؟
من الصعب التحقق من الرؤوس إذا لم تكن موجودة على الإطلاق!
يحدث هذا غالبًا. في هذه الحالة، نتعامل مع الصف الأول من البيانات كرأس ونتتبع التغييرات التي تطرأ عليه كما لو كان رأسًا. معظم البيانات التي نتلقاها مستقرة نسبيًا، لذلك بينما نحصل على نتائج إيجابية كاذبة من حين لآخر، لا نحتاج إلى تحديث هذه البيانات “الثابتة” بشكل متكرر.
التدريع الصحيح.
تقوم حزمة League / CSV بمعظم العمل نيابةً عنك، ولكن هناك أوقات يقوم فيها موفر البيانات بتشفير البيانات بطريقة غير قياسية. ويجب عليك دائمًا التحقق منها، وليس تحميلها بشكل أعمى في النظام.
لقد واجهنا مؤخرًا مشكلة حيث لم يكن البائع يهرب من إحدى الشخصيات بشكل صحيح، مما تسبب في دمج إدخالين في إدخال واحد. وبطبيعة الحال، لم تتوقف العملية مع وجود خطأ، لأنه كيف تعرف المكتبة بذلك، فهي تتبع فقط مواصفات CSV. اكتشفنا هذا فقط عندما حاولنا إدراج القيمة الناتجة في قاعدة البيانات وحصلنا على خطأ “القيمة طويلة جدًا”. أصبح من الواضح أن التحليل لم يكن يعمل بشكل صحيح.
إن الطريقة التي نتعامل بها الآن مع هذا الأمر صعبة بعض الشيء، لكنني سأغطيها باختصار.
أول شيء نقوم به عند تحميل ملف CSV جديد هو التحقق من عدد السجلات التي يحتوي عليها. في السابق، كنا نحسب فقط عدد الأسطر، ولكن قد تحتوي بعض الحقول على واصلات، لذلك كان علينا التوصل إلى طريقة أخرى. يحتوي كل سطر في ملف معين على قالب يمكن التعرف عليه بسهولة، على سبيل المثال، | 043,2020، وإذا أحصينا عدد مرات ظهوره سنعرف عدد الإدخالات التي يجب أن تكون.
الوظيفة العامةlinesInFile ( $ path )
{
نمط $ = ” | 043,2020،” ;
&نبسب;
// ‘E’ – يقوم بتشغيل التعبير العادي
// ‘a’ – اجعله يعمل مع الملف كما هو الحال مع النص (وهو كذلك).
// ‘o’ – يطلب منه إظهار التطابقات فقط، وليس السطر بأكمله
return ( int ) Trim ( exec (“grep -oaE ‘$ Pattern’ $ path | wc -l” )) ;
بمعرفة عدد السجلات التي يجب أن تكون، نقوم بمقارنتها بعدد السجلات التي حصلنا عليها بالفعل:
الوظيفة العامةتضمنCsvParsed (مسار $)
{
قارئ $ = $ هذا – > getReader ( مسار $ ) ;
خطوط $ = $ هذا – > LinesInFile ( $ path ) ;
إذا ( خطوط $ == $ Reader- > count ()) {
// يحتوي مستند CSV على عدد السجلات
// كم عدد الأسطر الموجودة في الملف.
العودة ;
// اكتشف السطر الذي يحتوي على الخطأ…
طرح استثناء جديد ( ‘تحتوي السلسلة [X] على خطأ.’ ) ;
إذا كانت هذه الأرقام غير متطابقة، فهذا يعني حدوث خطأ في مكان ما.
نكتشف الخط الذي به خطأ عن طريق تقسيم الملف إلى النصف والتحقق مرة أخرى. ونتيجة لذلك، حصلنا على رقم السطر مع الهروب الخاطئ.
لن أنشر كل التعليمات البرمجية التي تؤدي هذا العمل هنا، ولكن بشكل عام، سأشرح كيف يتم ذلك. نبدأ من نهاية الملف ونحصل على سطر واحد:
$ line from file = exec (“sed -n ‘{$ line}, {$ line} p’ $ path” ) ;
ثم نحصل على السلسلة الموجودة على العنوان $line – 2(اطرح الرأس وعوض الفهارس الصفرية).
$ lineFromReader = $ Reader- > fetchOne ( خط $ – 2 ) ;
قارن بين هاتين السلسلتين وتحقق مما إذا كانت تحتوي على نفس القيم. إذا كان الأمر كذلك، فإن الخطأ موجود قبل هذا السطر، لذا انتقل إلى منتصف الملف وحاول مرة أخرى. إذا تطابقوا، فقد ذهبنا بعيدا جدا. حد ذاتهانتقل بالمتغير إلى السطر “الجيد” الأخير واقفز للأمام.
نكرر هذا حتى نصل إلى “السلسلة السيئة”، والتي عادةً ما تكون مليئة بعدد عشوائي من الخطوط المائلة العكسية وعلامات الاقتباس. تهانينا، لقد وجدت ذلك!
البث لحفظ الذاكرة.
إذا كان حجم الملف أكثر من عشرة ميغابايت، فأنت بحاجة إلى التأكد من أنك تستخدم وضع الدفق، وعدم تحميل كل شيء مرة واحدة في الذاكرة.
يعني دفق الملف أنه سيتم تحميله في الذاكرة في أجزاء صغيرة. يعد هذا أمرًا سهلاً إلى حد ما وسيؤدي إلى زيادة عرض النطاق الترددي الخاص بك بشكل كبير.
// قم بتحميل كل شيء في الذاكرة (لا تفعل هذا!)
$ Reader = Reader :: createFromString ( file_get_contents ( ‘/path/to/my/file.csv’ )) ;
// يفتح كدفق (افعل هذا!)
$ Reader = Reader :: createFromPath ( ‘/path/to/my/file.csv’ , ‘r’ ) ;
لحسن الحظ، فإن حزمة PHP League تحل جميع المشاكل بالنسبة لنا. تحتاج فقط إلى التأكد من استدعاء الطريقة الصحيحة.
حتى إذا لم تكن ملفاتك موجودة محليًا، فلا يزال بإمكانك استخدام وضع البث.
على سبيل المثال، إذا كانوا على S3، فيمكنك استخدام مجمّع دفق AWS. إليك كيفية الحصول على دفق من S3 إلى Laravel:
الوظيفة العامة getStream ( $ disk, $ path )
{
// احصل على محول S3.
محول $ = قرص $- > getDriver () – > getAdapter () ;
// سجل غلاف الدفق s3: //
محول $- > getClient () – > RegisterStreamWrapper () ;
// الآن يمكننا استخدام بروتوكول s3: //
$ المسار = ‘s3: //’ . محول $- > احصل على دلو () . ‘/’ . مسار $;
// إعادة الدفق
إرجاع fopen ( $ path, ‘r’ , false ,stream_context_create ([
‘s3’ = > [‘يمكن البحث عنه’ = > صحيح ]
])) ;
//…
$ تيار = $ هذا – > getStream ( $ disk, $ path ) ;
قارئ $ = قارئ :: createFromStream ( $stream ) ;
يمكنك الآن العمل مع ملفات S3 قطعة تلو الأخرى بدلاً من تنزيلها.
الرجاء ملاحظة أن العديد من الطرق الموجودة في هذا البرنامج التعليمي تعمل فقط مع الملفات المحلية. لذلك، في حالة استخدام S3، تحتاج إلى تنزيل الملف بنفسك والعمل معه محليًا.
معالجة الصفحة.
إذا كان لديك بضعة ملايين من الأسطر التي تحاول معالجتها في فترة زمنية معقولة، فستجد نفسك مقيدًا بمحاولة قراءة سطر الملف بأكمله في وقت واحد في دفق واحد.
بدلاً من ذلك، خذ صفحة كبيرة تحتوي على 50000 إلى 100000 سجل وقم بمعالجتها في عملية منفصلة. يمكنك إنشاء عمليات متعددة لمعالجة صفحات متعددة بالتوازي، بدلاً من العمل معها بشكل تسلسلي.
يتم ذلك باستخدام أمر واحد مسؤول عن إنشاء أوامر “فرعية” لاستيراد الصفحات الفردية:
ملف استيراد الوظيفة العامة (مسار $)
{
// يعتمد كليًا عليك وعلى متطلباتك
$maxProcesses = 10 ;
$ لكل صفحة = 50000 ;
$ pages = ceil ( $ this – >linesInFile ( $ path ) / $ perPage ) ;
صفحة $ = 1 ;
عمليات $ = [] ;
بينما ( صفحة $ < = صفحات $ || العد ( عمليات $ )) {
عمليات $ = $ this – > getRunningProcesses (عمليات $)؛
// إذا كان هناك مجال لبدء عملية أخرى
// ونحتاج إلى استيراد صفحة أخرى
// ثم ابدأ عملية جديدة
إذا ( صفحة $ < = $ صفحات && العد ( $ العمليات ) < $ maxProcesses ) {
$ Command = “استيراد حرفي PHP: الصفحة $ الصفحة $ المسار” ;
// تنفيذ الأمر في الخلفية
exec ( $ command.’> / dev / null 2> & 1 & echo $!’ , $ العمليات ) ;
صفحة $ ++;
النوم ( 10 ) ;
وبعد ذلك، في العملية الفرعية، يمكنك استخدام عوامل التشغيل لاسترداد سجلات صفحة معينة:
سجلات الوظائف العامة (قارئ $)
{
إرجاع (بيان جديد)
-> الإزاحة ( $ this – > page * $ this – > perPage )
-> الحد ( $ this – > perPage )
-> العملية (قارئ $) ;
باستخدام نظام الترحيل، يمكنك التحكم في السرعة وعرض النطاق الترددي بناءً على موارد النظام المتوفرة لديك.
المعالجة عبر المهام والأوامر والشياطين
ما هو المكان الأفضل للتعامل مع ملفات CVS؟ لا يزال السؤال مفتوحًا. ولكن لدي بعض النصائح.
المهام
المعالجة داخل قائمة الانتظار. أسهل طريقة. أطلقت ونسيت. مثالية لعمليات الاستيراد الصغيرة التي تبدأ بإجراء منفصل، مثل قيام المستخدم بتحميل ملف.
ولكن هناك عيوب. الأول هو مهلة المهمة. إذا كانت الواردات ضخمة، فسوف تتجاوز بسرعة المهلة الافتراضية البالغة 90 ثانية للمهام الموضوعة في قائمة الانتظار. وبطبيعة الحال، يمكنك زيادة الاستيراد الأطول، ولكن على الأرجح ستصطدم بالمحدد مرة أخرى. على سبيل المثال، بعض وارداتنا تستمر لعدة ساعات.
قد يكون من الممكن الالتفاف حول هذا الأمر إذا قمت بتقسيم المهام الطويلة إلى مهام صغيرة، ولكن بعد ذلك عليك الاهتمام بالتنسيق بينها. كيف تعرف أي عملية استيراد قد اكتملت بالفعل؟ ولعل ب الجديديمكن أن يساعد ربط المهام من Laravel 8 هنا، لكن ليس لدي هذه الخبرة بعد.
الأوامر
إذا كانت البيانات المستوردة متاحة برمجيًا، على سبيل المثال من خلال واجهة برمجة التطبيقات (API) أو موجودة على FTP، فيمكنك جدولة أمر يأخذها ويبدأ معالجتها. يعد هذا مناسبًا لأنه لا داعي للقلق بشأن المهلات. تأكد من الاتصال ->runInBackground()->بدونOverlapping()إذا تم حساب وقت العمل المخطط بالساعات.
نستخدم هذه الطريقة لاستيراد أكبر الملفات ليلاً. نقوم خلال النهار بتحميل الملفات إلى S3، وفي الليل يستيقظ الفريق ليرى ما إذا كان هناك عمل من أجل ذلك.
كل شيء يعمل بشكل جيد، ليست هناك حاجة للقلق بشأن المهلات، يمكننا إنتاج العديد من العمليات الفرعية باستخدام أمر “أصل” واحد واستقبال الواردات المتوازية.
الشياطين
كما تعلم، فإن البرنامج الخفي هو مجرد عملية يتم تشغيلها في الخلفية، وليست عملية تخضع للتحكم المباشر للمستخدم. أفق الأمر:workin Laravel عادةً ما يتم تكوينه كبرنامج خفي.
يعد استخدام برنامج الاستيراد أمرًا مناسبًا لعدة أسباب. أولا، هم، مثل الفرق، ليس لديهم مهلة. ثانيًا – وهذا ما تفتقر إليه الأوامر – يمكن للبرنامج الخفي أن يبدأ العمل على الفور.
إذا كانت عملية الاستيراد الخاصة بك تعتمد على الملفات التي تم تحميلها بواسطة المستخدم، فمن المحتمل أنك لن ترغب في الانتظار حتى منتصف الليل لبدء استيرادها. في الواقع، حتى الفحص دقيقة بدقيقة ليس بالسرعة الكافية. يريد المستخدمون رؤية ردود الفعل الفورية.
بالنسبة للاستيراد المخصص، نحتفظ ببرنامج خفي في انتظار البيانات الأولية الجديدة في قاعدة البيانات. بمجرد رؤيتهم، يبدأ بالمعالجة.
إذا كنت تنفذ استراتيجية كهذه، فتأكد من استخدام القفل، وإلا فقد يبدأ برنامجان خفيان في استيراد نفس المستند، مما يؤدي إلى بيانات مكررة.
الوظيفة العامة HandleNextImport ()
{
// تأكد من أننا العملية الوحيدة التي تبحث عن الواردات الجديدة
معالج $ = ذاكرة التخزين المؤقت:: lock ( ‘selectNextImport’) – > الحصول على (الدالة () {
$ import = Import :: قابل للمعالجة () – > الأول () ;
إذا ( ! $ import ) {
العودة ;
استيراد $-> markAsProcessing () ;
إرجاع $ import- > makeHandler () ;
}) ;
اختياري ( معالج $ ) – > مقبض () ;
في الحقيقة، نحن لا نستخدم شيطانًا حقيقيًا لأن إعداده يعد بمثابة جوهرة. نبدأ البرنامج الخفي الزائف الذي كتبت عنه في المقال ” Laravel Pseudo-Daemons “.
تنقية البيانات.
سيتم تقديم جميع بياناتك كسلاسل لأن هذه هي الطريقة التي يعمل بها ملف CSV. على الأرجح، لا ترغب في تخزين البيانات بهذا التنسيق، ولكن تحويلها إلى الأنواع البدائية اللازمة. سأخبرك عن بعض المشاكل التي واجهتني أثناء القيام بذلك.
إزالة المسافات الزائدة
أول شيء أفعله دائمًا هو utf8_encodeالتخلص من المسافات الزائدة. يقوم ترميز UTF-8 بإزالة بعض القيم غير القياسية التي قد تكون موجودة في النص، على سبيل المثال، x00.
في هذه المرحلة، أقوم أيضًا بتحويل أي سلاسل فارغة إلى قيمة خالية كما هي في الأساس.
وظيفة عامة نظيفة (سجل $){
foreach ( سجل $ كمفتاح $ = > قيمة $ ) {
قيمة $ = utf8_encode ( قيمة $ ) ;
قيمة $ = القطع (قيمة $) ;
قيمة $ = قيمة $ === ”؟ خالية: قيمة $؛
سجل $ [مفتاح $] = قيمة $;
إرجاع سجل $؛
الآن بعد أن أصبحت البيانات موثوقة إلى حد ما، يمكننا المضي قدمًا في تحويلها إلى العناصر الأولية الصحيحة.
منطقية (منطقية)
إذا كان حقلك صحيح/ خطأ، فيجب عليك الانتباه إلى ما يلي:
نعم / ص / ص / نعم
لا / ن / لا / ن
صحيح / صحيح
خطأ / خطأ
1
0
(فارغ)
الأرقام
يعتمد الإرسال هنا على ما إذا كان الرقم الخاص بك يجب أن يكون عددًا صحيحًا أو نقطة عائمة. ولكن على أية حال، تحتاج إلى إزالة الفواصل ( ,) ومؤشرات العملة ( $).
إذا كنت تريد أرقامًا صحيحة فقط، فسأزيل الكسور العشرية ثم استخدم الدالة digits_only.
أرقام الدالة_فقط ( $ val, $ استبدال = ” )
{
إرجاع preg_replace ( ‘/ D /’، استبدال $، $ val ) ;
قيمة $ = تنفجر ( ‘.’ , $ value ) ;
قيمة $ = الرأس (قيمة $) ;
قيمة $ = digits_only ( $ value ) ;
إذا كنت بحاجة إلى التعامل مع كل من الأعداد الصحيحة والفاصلة العائمة، فيمكنك إزالة الفواصل ومؤشرات العملة، ثم استخدام قالب PHP بسيط.
قيمة $ = 0 + قيمة $;
إذا كانت قيمة $ عبارة عن سلسلة تعويم (123.45)، فإننا نحصل على تعويم، وإذا كانت سلسلة int (123)، فإننا نحصل على int.
التواريخ
نأمل أن يكون تنسيق التاريخ هو نفسه في ملف البيانات بأكمله. إذا كان الأمر كذلك، فإننا نستخدم طريقة createFromFormatfrom Carbon.
&نبسب;
تاريخ الوظيفة العامة (سجل $، مفتاح $، تنسيق $)
{
إذا ( Arr :: يحتوي على ( سجل $، مفتاح $ )) {
سجل $ [ مفتاح $ ] = كربون :: createFromFormat ( تنسيق $، سجل $ [ مفتاح $ ]) ;
إرجاع سجل $؛
إذا لم يتمكن Carbon من إنشاء تاريخ من السلسلة الناتجة، فسوف يطرح استثناءً، وهو ما نحتاج إليه. من الأفضل الحصول على خطأ صريح بدلاً من الحصول على بيانات سيئة.
EOF
يعد CSV تنسيقًا رائعًا لنقل البيانات. يمكن لكل نظام تقريبًا التصدير إليه. وإذا كنت تعرف بعض التفاصيل الدقيقة، فسيتم أيضا استيراد هذه البيانات دون مشاكل.
للحصول على خدمات احترافية أو استشارة من GCC Marketing Dubai يُرجى الاتصال بنا أو الاتصال بنا مباشرة على 00971567300683
تعرف على وكالة تسويق رقمي في دبي لتحقيق نتائج قابلة للقياس.
روابط مفيدة ذات صلة
يمكنك التوسع أكثر عبر: مزود حلول أنظمة تخطيط موارد المؤسسات للشركات الصغيرة في دبي الإمارات, مزود حلول أنظمة تخطيط موارد المؤسسات للشركات الصغيرة في دبي الإمارات, مزود حلول أنظمة تخطيط موارد المؤسسات للشركات الصغيرة في دبي الإمارات, مزود حلول أنظمة تخطيط موارد المؤسسات للشركات الصغيرة في دبي الإمارات, مزود حلول أنظمة تخطيط موارد المؤسسات للشركات الصغيرة في دبي الإمارات.
كما ننصح بقراءة: مزود حلول أنظمة تخطيط موارد المؤسسات للشركات الصغيرة في دبي الإمارات, كيف تعمل التطبيقات التي تدعم نظام تحديد المواقع العالمي (GPS) على تحسين عمليات التعدين, إطار عمل تطوير التطبيقات عبر الأنظمة الأساسية باستخدام PhoneGap: النصائح والحيل, كيف تعمل تطبيقات الصيانة التنبؤية على تقليل وقت التوقف عن العمل في عمليات النفط.
روابط مفيدة ذات صلة: باقات تحسين سرعة المواقع دبي الامارات، شركة تطوير تطبيقات التجارة الإلكترونية في دبي، اتصل بنا، تطوير وكلاء الذكاء الاصطناعي وروبوتات الدردشة في دبي، شركة تصميم مواقع ويب في دبي.
اترك تعليقاً
لن يتم نشر بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *