بناء أداة للتحقق من مستلمي الطيور بشكل غير متزامن بالجملة

زاكاري سامويلز

26‏/05‏/2022

البريد الإلكتروني

1 min read

بناء أداة للتحقق من مستلمي الطيور بشكل غير متزامن بالجملة

النقاط الرئيسية

    • قام المؤلف بإنشاء أداة تحقق من المستلمين بكميات كبيرة للتحقق من ملايين عناوين البريد الإلكتروني بكفاءة باستخدام واجهة برمجة التطبيقات للتحقق من المستلمين من Bird.

    • ثبت أن Node.js أسرع وأكثر قابلية للتوسع من Python نظرًا لوظائف الإدخال/الإخراج غير المت阻نة ونقص قيود GIL.

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

    • تجنب هذه الطريقة اختناقات الذاكرة وتحسن من الإنتاجية لتصل إلى حوالي 100,000 تحقق في أقل من دقيقة.

    • يمكن أن تشمل التحسينات المستقبلية معالجة أفضل لإعادة المحاولة، وواجهة مستخدم سهلة الاستخدام، أو الانتقال إلى بيئات بدون خادم من أجل القابلية للتوسع.

أهم النقاط في الأسئلة والأجوبة

  • ما الغرض من أداة التحقق من صحة المستلمين بشكل غير متزامن بالجملة؟

    يقوم بالتحقق من كميات كبيرة من عناوين البريد الإلكتروني من خلال التكامل مباشرة مع واجهة برمجة تطبيقات التحقق من المستلمين الخاصة بـ Bird، مما يخرج نتائج موثوقة بسرعة دون أي تحميلات يدوية.

  • لماذا تم استخدام بايثون في البداية ثم تم استبداله بـ Node.js؟

    كان قفل المترجم العالمي لبايثون (GIL) يحد من التزامن، بينما سمح Node.js بالتنفيذ غير المتزامن الحقيقي، مما أدى إلى إجراء مكالمات API متوازية بشكل أسرع بكثير.

  • كيف تتعامل الأداة مع الملفات الكبيرة دون النفاد من الذاكرة؟

    بدلاً من تحميل كل البيانات دفعة واحدة، يقوم البرنامج النصي بمعالجة كل سطر من CSV بشكل فردي - حيث يرسل طلب التحقق ويكتب النتائج على الفور إلى ملف CSV جديد.

  • ما المشكلة التي يحلها هذا الأداة للمطورين؟

    يتيح التحقق من صحة قوائم البريد الإلكتروني على نطاق واسع، متجاوزًا حد 20 ميغابايت لمُحقق SparkPost القائم على واجهة المستخدم وإلغاء الحاجة إلى تحميل ملفات متعددة يدويًا.

  • ما مدى سرعة الإصدار النهائي من البرنامج؟

    حوالي 100,000 تحقق مكتمل في 55 ثانية، مقارنة بأكثر من دقيقة عند استخدام النسخة الخاصة بالواجهة.

  • ما القضايا التي واجهت في أنظمة ويندوز؟

    تسبب تجمع اتصالات عميل HTTP في Node.js في حدوث أخطاء “ENOBUFS” بعد العديد من الطلبات المتزامنة، والتي تم إصلاحها عن طريق تكوين إعادة استخدام اتصال axios.

  • ما التحسينات المستقبلية المقترحة؟

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

لمن يبحث عن برنامج سريع وبسيط يستقبل ملف CSV، يتصل بواجهة برمجة التطبيقات للتحقق من المستلم، ويخرج ملف CSV، فهذا البرنامج مناسب لك.

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

واحدة من الأسئلة التي نتلقاها أحيانًا هي، كيف يمكنني التحقق من قوائم البريد الإلكتروني بشكل جماعي مع تحقق المستلم? هناك خياران هنا، أحدهما هو رفع ملف عبر واجهة SparkPost للتحقق، والآخر هو إجراء مكالمات فردية لكل بريد إلكتروني إلى واجهة برمجة التطبيقات (حيث إن واجهة برمجة التطبيقات هي التحقق من بريد إلكتروني واحد).

يعمل الخيار الأول بشكل رائع ولكنه لديه حدود 20 ميجابايت (حوالي 500,000 عنوان). ماذا لو كان لدى شخص ما قائمة بريد إلكتروني تحتوي على ملايين العناوين؟ قد يعني ذلك تقسيم ذلك إلى آلاف من عمليات رفع ملفات CSV.

نظرًا لأن رفع آلاف ملفات CSV يبدو بعيد المنال بعض الشيء، أخذت تلك الحالة وبدأت أتساءل كم من السرعة يمكنني الحصول على واجهة برمجة التطبيقات لتعمل. في هذه المقالة، سأشرح ما حاولت وكيف انتهيت أخيرًا إلى برنامج يمكنه الحصول على حوالي 100,000 تحقق في 55 ثانية (بينما حصلت في واجهة المستخدم على حوالي 100,000 تحقق في دقيقة و 10 ثوان).

النهج

التحققات التي تم اختبارها

الوقت لإكمال

العدد التقريبي للمعالجة

أداة Node.js للتحقق الجماعي غير المتزامن

100,000

55 ثانية

~1,818 تحقق/ثانية

تحميل واجهة SparkPost

100,000

1 دقيقة و 10 ثوان

~1,428 تحقق/ثانية

وعلى الرغم من أن هذا لا يزال سيستغرق حوالي 100 ساعة لإنجازه مع حوالي 654 مليون تحقق، يمكن لهذا السكربت العمل في الخلفية موفرًا وقتًا كبيرًا.

يمكن العثور على النسخة النهائية من هذا البرنامج هنا.

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

واحدة من الأسئلة التي نتلقاها أحيانًا هي، كيف يمكنني التحقق من قوائم البريد الإلكتروني بشكل جماعي مع تحقق المستلم? هناك خياران هنا، أحدهما هو رفع ملف عبر واجهة SparkPost للتحقق، والآخر هو إجراء مكالمات فردية لكل بريد إلكتروني إلى واجهة برمجة التطبيقات (حيث إن واجهة برمجة التطبيقات هي التحقق من بريد إلكتروني واحد).

يعمل الخيار الأول بشكل رائع ولكنه لديه حدود 20 ميجابايت (حوالي 500,000 عنوان). ماذا لو كان لدى شخص ما قائمة بريد إلكتروني تحتوي على ملايين العناوين؟ قد يعني ذلك تقسيم ذلك إلى آلاف من عمليات رفع ملفات CSV.

نظرًا لأن رفع آلاف ملفات CSV يبدو بعيد المنال بعض الشيء، أخذت تلك الحالة وبدأت أتساءل كم من السرعة يمكنني الحصول على واجهة برمجة التطبيقات لتعمل. في هذه المقالة، سأشرح ما حاولت وكيف انتهيت أخيرًا إلى برنامج يمكنه الحصول على حوالي 100,000 تحقق في 55 ثانية (بينما حصلت في واجهة المستخدم على حوالي 100,000 تحقق في دقيقة و 10 ثوان).

النهج

التحققات التي تم اختبارها

الوقت لإكمال

العدد التقريبي للمعالجة

أداة Node.js للتحقق الجماعي غير المتزامن

100,000

55 ثانية

~1,818 تحقق/ثانية

تحميل واجهة SparkPost

100,000

1 دقيقة و 10 ثوان

~1,428 تحقق/ثانية

وعلى الرغم من أن هذا لا يزال سيستغرق حوالي 100 ساعة لإنجازه مع حوالي 654 مليون تحقق، يمكن لهذا السكربت العمل في الخلفية موفرًا وقتًا كبيرًا.

يمكن العثور على النسخة النهائية من هذا البرنامج هنا.

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

واحدة من الأسئلة التي نتلقاها أحيانًا هي، كيف يمكنني التحقق من قوائم البريد الإلكتروني بشكل جماعي مع تحقق المستلم? هناك خياران هنا، أحدهما هو رفع ملف عبر واجهة SparkPost للتحقق، والآخر هو إجراء مكالمات فردية لكل بريد إلكتروني إلى واجهة برمجة التطبيقات (حيث إن واجهة برمجة التطبيقات هي التحقق من بريد إلكتروني واحد).

يعمل الخيار الأول بشكل رائع ولكنه لديه حدود 20 ميجابايت (حوالي 500,000 عنوان). ماذا لو كان لدى شخص ما قائمة بريد إلكتروني تحتوي على ملايين العناوين؟ قد يعني ذلك تقسيم ذلك إلى آلاف من عمليات رفع ملفات CSV.

نظرًا لأن رفع آلاف ملفات CSV يبدو بعيد المنال بعض الشيء، أخذت تلك الحالة وبدأت أتساءل كم من السرعة يمكنني الحصول على واجهة برمجة التطبيقات لتعمل. في هذه المقالة، سأشرح ما حاولت وكيف انتهيت أخيرًا إلى برنامج يمكنه الحصول على حوالي 100,000 تحقق في 55 ثانية (بينما حصلت في واجهة المستخدم على حوالي 100,000 تحقق في دقيقة و 10 ثوان).

النهج

التحققات التي تم اختبارها

الوقت لإكمال

العدد التقريبي للمعالجة

أداة Node.js للتحقق الجماعي غير المتزامن

100,000

55 ثانية

~1,818 تحقق/ثانية

تحميل واجهة SparkPost

100,000

1 دقيقة و 10 ثوان

~1,428 تحقق/ثانية

وعلى الرغم من أن هذا لا يزال سيستغرق حوالي 100 ساعة لإنجازه مع حوالي 654 مليون تحقق، يمكن لهذا السكربت العمل في الخلفية موفرًا وقتًا كبيرًا.

يمكن العثور على النسخة النهائية من هذا البرنامج هنا.

خطأي الأول: استخدام بايثون

بايثون هي واحدة من لغات البرمجة المفضلة لدي. إنها تتفوق في العديد من المجالات وسهلة للغاية. ومع ذلك، فإن أحد المجالات التي لا تتفوق فيها هو العمليات المتزامنة. على الرغم من أن بايثون لديها القدرة على تشغيل الوظائف غير المتزامنة، إلا أنها تحتوي على ما يُعرف بقفل المترجم العالمي بايثون أو GIL.

"قفل المترجم العالمي بايثون أو GIL، بعبارات بسيطة، هو قفل (أو قفل) يسمح لخيط واحد فقط بالتحكم في مترجم بايثون.

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

نظرًا لأن قفل المترجم العالمي (GIL) يسمح بخيط واحد فقط بالتنفيذ في وقت واحد، حتى على الأنظمة متعددة النوى، فقد اكتسب سمعة كخاصية "سيئة السمعة" في بايثون (انظر مقالة Real Python حول GIL).

في البداية، لم أكن على علم بـ GIL، لذا بدأت البرمجة في بايثون. في النهاية، على الرغم من أن برنامجي كان غير متزامن، إلا أنه كان يتعرض للحظر، وبغض النظر عن عدد الخيوط التي أضفتها، حصلت فقط على حوالي 12-15 تكرار في الثانية.

يمكن رؤية الجزء الرئيسي من الوظيفة غير المتزامنة في بايثون أدناه:

async def validateRecipients(f, fh, apiKey, snooze, count):
    h = {
        'Authorization': apiKey,
        'Accept': 'application/json'
    }
    with tqdm(total=count) as pbar:
        async with aiohttp.ClientSession() as session:
            for address in f:
                for i in address:
                    thisReq = requests.compat.urljoin(url, i)
                    async with session.get(thisReq, headers=h, ssl=False) as resp:
                        content = await resp.json()
                        row = content['results']
                        row['email'] = i
                        fh.writerow(row)
                        pbar.update(1)

لذا تخلصت من استخدام بايثون وعدت إلى لوحة الرسم...

استقريت على استخدام NodeJS نظرًا لقدرته على أداء عمليات الإدخال/الإخراج غير المتزامنة بشكل جيد للغاية. خيار ممتاز آخر لمعالجة واجهات برمجة التطبيقات غير المتزامنة هو بناء مستهلكات ويب هوك بدون خادم باستخدام Azure Functions، والتي يمكن أن تتعامل بكفاءة مع أحمال العمل المتغيرة. كما أنني على دراية جيدة بالبرمجة في NodeJS.

باستخدام الجوانب غير المتزامنة لـ Node.js، كانت هذه الطريقة تعمل بشكل جيد. لمزيد من التفاصيل حول البرمجة غير المتزامنة في Node.js، انظر دليل RisingStack للبرمجة غير المتزامنة في Node.js.

بايثون هي واحدة من لغات البرمجة المفضلة لدي. إنها تتفوق في العديد من المجالات وسهلة للغاية. ومع ذلك، فإن أحد المجالات التي لا تتفوق فيها هو العمليات المتزامنة. على الرغم من أن بايثون لديها القدرة على تشغيل الوظائف غير المتزامنة، إلا أنها تحتوي على ما يُعرف بقفل المترجم العالمي بايثون أو GIL.

"قفل المترجم العالمي بايثون أو GIL، بعبارات بسيطة، هو قفل (أو قفل) يسمح لخيط واحد فقط بالتحكم في مترجم بايثون.

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

نظرًا لأن قفل المترجم العالمي (GIL) يسمح بخيط واحد فقط بالتنفيذ في وقت واحد، حتى على الأنظمة متعددة النوى، فقد اكتسب سمعة كخاصية "سيئة السمعة" في بايثون (انظر مقالة Real Python حول GIL).

في البداية، لم أكن على علم بـ GIL، لذا بدأت البرمجة في بايثون. في النهاية، على الرغم من أن برنامجي كان غير متزامن، إلا أنه كان يتعرض للحظر، وبغض النظر عن عدد الخيوط التي أضفتها، حصلت فقط على حوالي 12-15 تكرار في الثانية.

يمكن رؤية الجزء الرئيسي من الوظيفة غير المتزامنة في بايثون أدناه:

async def validateRecipients(f, fh, apiKey, snooze, count):
    h = {
        'Authorization': apiKey,
        'Accept': 'application/json'
    }
    with tqdm(total=count) as pbar:
        async with aiohttp.ClientSession() as session:
            for address in f:
                for i in address:
                    thisReq = requests.compat.urljoin(url, i)
                    async with session.get(thisReq, headers=h, ssl=False) as resp:
                        content = await resp.json()
                        row = content['results']
                        row['email'] = i
                        fh.writerow(row)
                        pbar.update(1)

لذا تخلصت من استخدام بايثون وعدت إلى لوحة الرسم...

استقريت على استخدام NodeJS نظرًا لقدرته على أداء عمليات الإدخال/الإخراج غير المتزامنة بشكل جيد للغاية. خيار ممتاز آخر لمعالجة واجهات برمجة التطبيقات غير المتزامنة هو بناء مستهلكات ويب هوك بدون خادم باستخدام Azure Functions، والتي يمكن أن تتعامل بكفاءة مع أحمال العمل المتغيرة. كما أنني على دراية جيدة بالبرمجة في NodeJS.

باستخدام الجوانب غير المتزامنة لـ Node.js، كانت هذه الطريقة تعمل بشكل جيد. لمزيد من التفاصيل حول البرمجة غير المتزامنة في Node.js، انظر دليل RisingStack للبرمجة غير المتزامنة في Node.js.

بايثون هي واحدة من لغات البرمجة المفضلة لدي. إنها تتفوق في العديد من المجالات وسهلة للغاية. ومع ذلك، فإن أحد المجالات التي لا تتفوق فيها هو العمليات المتزامنة. على الرغم من أن بايثون لديها القدرة على تشغيل الوظائف غير المتزامنة، إلا أنها تحتوي على ما يُعرف بقفل المترجم العالمي بايثون أو GIL.

"قفل المترجم العالمي بايثون أو GIL، بعبارات بسيطة، هو قفل (أو قفل) يسمح لخيط واحد فقط بالتحكم في مترجم بايثون.

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

نظرًا لأن قفل المترجم العالمي (GIL) يسمح بخيط واحد فقط بالتنفيذ في وقت واحد، حتى على الأنظمة متعددة النوى، فقد اكتسب سمعة كخاصية "سيئة السمعة" في بايثون (انظر مقالة Real Python حول GIL).

في البداية، لم أكن على علم بـ GIL، لذا بدأت البرمجة في بايثون. في النهاية، على الرغم من أن برنامجي كان غير متزامن، إلا أنه كان يتعرض للحظر، وبغض النظر عن عدد الخيوط التي أضفتها، حصلت فقط على حوالي 12-15 تكرار في الثانية.

يمكن رؤية الجزء الرئيسي من الوظيفة غير المتزامنة في بايثون أدناه:

async def validateRecipients(f, fh, apiKey, snooze, count):
    h = {
        'Authorization': apiKey,
        'Accept': 'application/json'
    }
    with tqdm(total=count) as pbar:
        async with aiohttp.ClientSession() as session:
            for address in f:
                for i in address:
                    thisReq = requests.compat.urljoin(url, i)
                    async with session.get(thisReq, headers=h, ssl=False) as resp:
                        content = await resp.json()
                        row = content['results']
                        row['email'] = i
                        fh.writerow(row)
                        pbar.update(1)

لذا تخلصت من استخدام بايثون وعدت إلى لوحة الرسم...

استقريت على استخدام NodeJS نظرًا لقدرته على أداء عمليات الإدخال/الإخراج غير المتزامنة بشكل جيد للغاية. خيار ممتاز آخر لمعالجة واجهات برمجة التطبيقات غير المتزامنة هو بناء مستهلكات ويب هوك بدون خادم باستخدام Azure Functions، والتي يمكن أن تتعامل بكفاءة مع أحمال العمل المتغيرة. كما أنني على دراية جيدة بالبرمجة في NodeJS.

باستخدام الجوانب غير المتزامنة لـ Node.js، كانت هذه الطريقة تعمل بشكل جيد. لمزيد من التفاصيل حول البرمجة غير المتزامنة في Node.js، انظر دليل RisingStack للبرمجة غير المتزامنة في Node.js.

خطأ الثاني: محاولة قراءة الملف في الذاكرة

كانت فكرتي الأولية كما يلي:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


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

لقد عمل ذلك بشكل جيد جدًا للملفات الصغيرة. لكن المشكلة ظهرت عندما حاولت تشغيل 100,000 بريد إلكتروني. توقفت البرنامج حوالي 12,000 عملية تحقق. مع مساعدة أحد مطوري الواجهة الأمامية لدينا، رأيت أن المشكلة كانت في تحميل جميع النتائج في متغير (وهكذا نفدت الذاكرة بسرعة). إذا كنت ترغب في رؤية النسخة الأولى من هذا البرنامج، فقد قمت بربطها هنا: الإصدار 1 (غير موصى به).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


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

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

كانت فكرتي الأولية كما يلي:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


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

لقد عمل ذلك بشكل جيد جدًا للملفات الصغيرة. لكن المشكلة ظهرت عندما حاولت تشغيل 100,000 بريد إلكتروني. توقفت البرنامج حوالي 12,000 عملية تحقق. مع مساعدة أحد مطوري الواجهة الأمامية لدينا، رأيت أن المشكلة كانت في تحميل جميع النتائج في متغير (وهكذا نفدت الذاكرة بسرعة). إذا كنت ترغب في رؤية النسخة الأولى من هذا البرنامج، فقد قمت بربطها هنا: الإصدار 1 (غير موصى به).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


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

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

كانت فكرتي الأولية كما يلي:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


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

لقد عمل ذلك بشكل جيد جدًا للملفات الصغيرة. لكن المشكلة ظهرت عندما حاولت تشغيل 100,000 بريد إلكتروني. توقفت البرنامج حوالي 12,000 عملية تحقق. مع مساعدة أحد مطوري الواجهة الأمامية لدينا، رأيت أن المشكلة كانت في تحميل جميع النتائج في متغير (وهكذا نفدت الذاكرة بسرعة). إذا كنت ترغب في رؤية النسخة الأولى من هذا البرنامج، فقد قمت بربطها هنا: الإصدار 1 (غير موصى به).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


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

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

تحليل الشيفرة النهائية

بعد قراءة وفحص المعلمات الطرفية، أستخدم الكود التالي. أولاً، أقرأ ملف CSV الذي يحتوي على البريد الإلكتروني وأعد كل سطر. هناك غرضان من هذه الوظيفة، 1) يتيح لي الإبلاغ بدقة عن تقدم الملف [كما سنرى لاحقًا]، و 2) يتيح لي إيقاف مؤقت عندما يكون عدد رسائل البريد الإلكتروني في الملف يساوي التحقق المكتمل. لقد أضفت مؤقتًا حتى أتمكن من إجراء قياسات التأثير وضمان أنني أحصل على نتائج جيدة.

let count = 0; // Line count
require("fs")
    .createReadStream(myArgs[1])
    .on("data", function (chunk) {
        for (let i = 0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
    })
    // Reads the infile and increases the count for each line
    .on("close", function () {
        // At the end of the infile, after all lines have been counted, run the recipient validation function
        validateRecipients.validateRecipients(count, myArgs);
    });


 ثم أنادي وظيفة validateRecipients. لاحظ أن هذه الوظيفة غير متزامنة. بعد التأكد من أن ملف الإدخال وملف الإخراج هما CSV، أكتب صف رأس، وأبدأ مؤقت البرنامج باستخدام مكتبة JSDOM.

async function validateRecipients(email_count, myArgs) {
    if (
        // If both the infile and outfile are in .csv format
        extname(myArgs[1]).toLowerCase() == ".csv" &&
        extname(myArgs[3]).toLowerCase() == ".csv"
    ) {
        let completed = 0; // Counter for each API call
        email_count++; // Line counter returns #lines - 1, this corrects the number of lines
        // Start a timer
        const { window } = new JSDOM();
        const start = window.performance.now();
        const output = fs.createWriteStream(myArgs[3]); // Outfile
        output.write(
            "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n"
        ); // Write the headers in the outfile
    }
}

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

fs.createReadStream(myArgs[1])
    .pipe(csv.parse({ headers: false }))
    .on("data", async (email) => {
        let url =
            SPARKPOST_HOST +
            "/api/v1/recipient-validation/single/" +
            email;
        await axios
            .get(url, {
                headers: {
                    Authorization: SPARKPOST_API_KEY,
                },
            });
        // For each row read in from the infile, call the SparkPost Recipient Validation API
    });

ثم، عند الاستجابة

  • أضف البريد الإلكتروني إلى JSON (لتتمكن من طباعته في ملف CSV)

  • تحقق إذا كان السبب فارغًا، وإذا كان كذلك، قم بتعبئة قيمة فارغة (هذا لضمان تنسيق CSV متسق، حيث يتم تقديم السبب في بعض الحالات في الاستجابة)

  • قم بتعيين الخيارات والمفاتيح لوحدة json2csv.

  • تحويل JSON إلى CSV وإخراجه (باستخدام json2csv)

  • كتابة التقدم في الطرفية

  • أخيرًا، إذا كان عدد رسائل البريد الإلكتروني في الملف يساوي التحقيات المكتملة، أوقف المؤقت واطبع النتائج


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON for output
    response.data.results.reason ? null : (response.data.results.reason = ""); 
    // If reason is null, set it to blank so the CSV is uniform
    // Utilizes json-2-csv to convert the JSON to CSV format and output
    let options = {
        prependHeader: false, // Disables JSON values from being added as header rows for every line
        keys: [
            "results.email",
            "results.valid",
            "results.result",
            "results.reason",
            "results.is_role",
            "results.is_disposable",
            "results.is_free",
            "results.delivery_confidence",
        ], // Sets the order of keys
    };
    let json2csvCallback = function (err, csv) {
        if (err) throw err;
        output.write(`${csv}\n`);
    };
    converter.json2csv(response.data, json2csvCallback, options);
    completed++; // Increase the API counter
    process.stdout.write(`Done with ${completed} / ${email_count}\r`); 
    // Output status of Completed / Total to the console without showing new lines
    // If all emails have completed validation
    if (completed == email_count) {
        const stop = window.performance.now(); // Stop the timer
        console.log(
            `All emails successfully validated in ${(stop - start) / 1000} seconds`
        );
    }
});

 

مشكلة أخيرة وجدتها هي أنه بينما كان هذا يعمل بشكل رائع على نظام ماك، واجهت الخطأ التالي عند استخدام ويندوز بعد حوالي 10,000 تحقق:

خطأ: الاتصال ENOBUFS XX.XX.XXX.XXX:443 – محلي (undefined:undefined) مع البريد الإلكتروني XXXXXXX@XXXXXXXXXX.XXX

بعد القيام ببعض الأبحاث الإضافية، يبدو أن هناك مشكلة في تجمع اتصالات HTTP في NodeJS الذي لا يعيد استخدام الاتصالات. وجدت هذه المقالة في Stackoverflow حول المشكلة، وبعد المزيد من البحث، وجدت تكوين افتراضي جيد لمكتبة axios التي حلت هذه المشكلة. لا أزال غير متأكد لماذا تحدث هذه المشكلة فقط على ويندوز وليس على ماك.

بعد قراءة وفحص المعلمات الطرفية، أستخدم الكود التالي. أولاً، أقرأ ملف CSV الذي يحتوي على البريد الإلكتروني وأعد كل سطر. هناك غرضان من هذه الوظيفة، 1) يتيح لي الإبلاغ بدقة عن تقدم الملف [كما سنرى لاحقًا]، و 2) يتيح لي إيقاف مؤقت عندما يكون عدد رسائل البريد الإلكتروني في الملف يساوي التحقق المكتمل. لقد أضفت مؤقتًا حتى أتمكن من إجراء قياسات التأثير وضمان أنني أحصل على نتائج جيدة.

let count = 0; // Line count
require("fs")
    .createReadStream(myArgs[1])
    .on("data", function (chunk) {
        for (let i = 0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
    })
    // Reads the infile and increases the count for each line
    .on("close", function () {
        // At the end of the infile, after all lines have been counted, run the recipient validation function
        validateRecipients.validateRecipients(count, myArgs);
    });


 ثم أنادي وظيفة validateRecipients. لاحظ أن هذه الوظيفة غير متزامنة. بعد التأكد من أن ملف الإدخال وملف الإخراج هما CSV، أكتب صف رأس، وأبدأ مؤقت البرنامج باستخدام مكتبة JSDOM.

async function validateRecipients(email_count, myArgs) {
    if (
        // If both the infile and outfile are in .csv format
        extname(myArgs[1]).toLowerCase() == ".csv" &&
        extname(myArgs[3]).toLowerCase() == ".csv"
    ) {
        let completed = 0; // Counter for each API call
        email_count++; // Line counter returns #lines - 1, this corrects the number of lines
        // Start a timer
        const { window } = new JSDOM();
        const start = window.performance.now();
        const output = fs.createWriteStream(myArgs[3]); // Outfile
        output.write(
            "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n"
        ); // Write the headers in the outfile
    }
}

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

fs.createReadStream(myArgs[1])
    .pipe(csv.parse({ headers: false }))
    .on("data", async (email) => {
        let url =
            SPARKPOST_HOST +
            "/api/v1/recipient-validation/single/" +
            email;
        await axios
            .get(url, {
                headers: {
                    Authorization: SPARKPOST_API_KEY,
                },
            });
        // For each row read in from the infile, call the SparkPost Recipient Validation API
    });

ثم، عند الاستجابة

  • أضف البريد الإلكتروني إلى JSON (لتتمكن من طباعته في ملف CSV)

  • تحقق إذا كان السبب فارغًا، وإذا كان كذلك، قم بتعبئة قيمة فارغة (هذا لضمان تنسيق CSV متسق، حيث يتم تقديم السبب في بعض الحالات في الاستجابة)

  • قم بتعيين الخيارات والمفاتيح لوحدة json2csv.

  • تحويل JSON إلى CSV وإخراجه (باستخدام json2csv)

  • كتابة التقدم في الطرفية

  • أخيرًا، إذا كان عدد رسائل البريد الإلكتروني في الملف يساوي التحقيات المكتملة، أوقف المؤقت واطبع النتائج


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON for output
    response.data.results.reason ? null : (response.data.results.reason = ""); 
    // If reason is null, set it to blank so the CSV is uniform
    // Utilizes json-2-csv to convert the JSON to CSV format and output
    let options = {
        prependHeader: false, // Disables JSON values from being added as header rows for every line
        keys: [
            "results.email",
            "results.valid",
            "results.result",
            "results.reason",
            "results.is_role",
            "results.is_disposable",
            "results.is_free",
            "results.delivery_confidence",
        ], // Sets the order of keys
    };
    let json2csvCallback = function (err, csv) {
        if (err) throw err;
        output.write(`${csv}\n`);
    };
    converter.json2csv(response.data, json2csvCallback, options);
    completed++; // Increase the API counter
    process.stdout.write(`Done with ${completed} / ${email_count}\r`); 
    // Output status of Completed / Total to the console without showing new lines
    // If all emails have completed validation
    if (completed == email_count) {
        const stop = window.performance.now(); // Stop the timer
        console.log(
            `All emails successfully validated in ${(stop - start) / 1000} seconds`
        );
    }
});

 

مشكلة أخيرة وجدتها هي أنه بينما كان هذا يعمل بشكل رائع على نظام ماك، واجهت الخطأ التالي عند استخدام ويندوز بعد حوالي 10,000 تحقق:

خطأ: الاتصال ENOBUFS XX.XX.XXX.XXX:443 – محلي (undefined:undefined) مع البريد الإلكتروني XXXXXXX@XXXXXXXXXX.XXX

بعد القيام ببعض الأبحاث الإضافية، يبدو أن هناك مشكلة في تجمع اتصالات HTTP في NodeJS الذي لا يعيد استخدام الاتصالات. وجدت هذه المقالة في Stackoverflow حول المشكلة، وبعد المزيد من البحث، وجدت تكوين افتراضي جيد لمكتبة axios التي حلت هذه المشكلة. لا أزال غير متأكد لماذا تحدث هذه المشكلة فقط على ويندوز وليس على ماك.

بعد قراءة وفحص المعلمات الطرفية، أستخدم الكود التالي. أولاً، أقرأ ملف CSV الذي يحتوي على البريد الإلكتروني وأعد كل سطر. هناك غرضان من هذه الوظيفة، 1) يتيح لي الإبلاغ بدقة عن تقدم الملف [كما سنرى لاحقًا]، و 2) يتيح لي إيقاف مؤقت عندما يكون عدد رسائل البريد الإلكتروني في الملف يساوي التحقق المكتمل. لقد أضفت مؤقتًا حتى أتمكن من إجراء قياسات التأثير وضمان أنني أحصل على نتائج جيدة.

let count = 0; // Line count
require("fs")
    .createReadStream(myArgs[1])
    .on("data", function (chunk) {
        for (let i = 0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
    })
    // Reads the infile and increases the count for each line
    .on("close", function () {
        // At the end of the infile, after all lines have been counted, run the recipient validation function
        validateRecipients.validateRecipients(count, myArgs);
    });


 ثم أنادي وظيفة validateRecipients. لاحظ أن هذه الوظيفة غير متزامنة. بعد التأكد من أن ملف الإدخال وملف الإخراج هما CSV، أكتب صف رأس، وأبدأ مؤقت البرنامج باستخدام مكتبة JSDOM.

async function validateRecipients(email_count, myArgs) {
    if (
        // If both the infile and outfile are in .csv format
        extname(myArgs[1]).toLowerCase() == ".csv" &&
        extname(myArgs[3]).toLowerCase() == ".csv"
    ) {
        let completed = 0; // Counter for each API call
        email_count++; // Line counter returns #lines - 1, this corrects the number of lines
        // Start a timer
        const { window } = new JSDOM();
        const start = window.performance.now();
        const output = fs.createWriteStream(myArgs[3]); // Outfile
        output.write(
            "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n"
        ); // Write the headers in the outfile
    }
}

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

fs.createReadStream(myArgs[1])
    .pipe(csv.parse({ headers: false }))
    .on("data", async (email) => {
        let url =
            SPARKPOST_HOST +
            "/api/v1/recipient-validation/single/" +
            email;
        await axios
            .get(url, {
                headers: {
                    Authorization: SPARKPOST_API_KEY,
                },
            });
        // For each row read in from the infile, call the SparkPost Recipient Validation API
    });

ثم، عند الاستجابة

  • أضف البريد الإلكتروني إلى JSON (لتتمكن من طباعته في ملف CSV)

  • تحقق إذا كان السبب فارغًا، وإذا كان كذلك، قم بتعبئة قيمة فارغة (هذا لضمان تنسيق CSV متسق، حيث يتم تقديم السبب في بعض الحالات في الاستجابة)

  • قم بتعيين الخيارات والمفاتيح لوحدة json2csv.

  • تحويل JSON إلى CSV وإخراجه (باستخدام json2csv)

  • كتابة التقدم في الطرفية

  • أخيرًا، إذا كان عدد رسائل البريد الإلكتروني في الملف يساوي التحقيات المكتملة، أوقف المؤقت واطبع النتائج


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON for output
    response.data.results.reason ? null : (response.data.results.reason = ""); 
    // If reason is null, set it to blank so the CSV is uniform
    // Utilizes json-2-csv to convert the JSON to CSV format and output
    let options = {
        prependHeader: false, // Disables JSON values from being added as header rows for every line
        keys: [
            "results.email",
            "results.valid",
            "results.result",
            "results.reason",
            "results.is_role",
            "results.is_disposable",
            "results.is_free",
            "results.delivery_confidence",
        ], // Sets the order of keys
    };
    let json2csvCallback = function (err, csv) {
        if (err) throw err;
        output.write(`${csv}\n`);
    };
    converter.json2csv(response.data, json2csvCallback, options);
    completed++; // Increase the API counter
    process.stdout.write(`Done with ${completed} / ${email_count}\r`); 
    // Output status of Completed / Total to the console without showing new lines
    // If all emails have completed validation
    if (completed == email_count) {
        const stop = window.performance.now(); // Stop the timer
        console.log(
            `All emails successfully validated in ${(stop - start) / 1000} seconds`
        );
    }
});

 

مشكلة أخيرة وجدتها هي أنه بينما كان هذا يعمل بشكل رائع على نظام ماك، واجهت الخطأ التالي عند استخدام ويندوز بعد حوالي 10,000 تحقق:

خطأ: الاتصال ENOBUFS XX.XX.XXX.XXX:443 – محلي (undefined:undefined) مع البريد الإلكتروني XXXXXXX@XXXXXXXXXX.XXX

بعد القيام ببعض الأبحاث الإضافية، يبدو أن هناك مشكلة في تجمع اتصالات HTTP في NodeJS الذي لا يعيد استخدام الاتصالات. وجدت هذه المقالة في Stackoverflow حول المشكلة، وبعد المزيد من البحث، وجدت تكوين افتراضي جيد لمكتبة axios التي حلت هذه المشكلة. لا أزال غير متأكد لماذا تحدث هذه المشكلة فقط على ويندوز وليس على ماك.

الخطوات التالية

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

تشمل بعض الإضافات إلى هذا البرنامج ما يلي:

  • بناء واجهة أمامية أو واجهة مستخدم أسهل للاستخدام

  • تحسين التعامل مع الأخطاء وإعادة المحاولة لأنه إذا حدثت مشكلة وظهرت رسالة خطأ من واجهة برمجة التطبيقات، فإن البرنامج حالياً لا يقوم بإعادة المحاولة

  • النظر في تنفيذ كخدمة وظيفة Azure بدون خادم من أجل التوسع التلقائي وتقليل إدارة البنية التحتية


وأود أيضاً أن أكون فضولياً لمعرفة ما إذا كان من الممكن تحقيق نتائج أسرع مع لغة أخرى مثل Golang أو Erlang/Elixir. بخلاف اختيار اللغة، يمكن أن تؤثر قيود البنية التحتية أيضًا على الأداء - لقد تعلمنا هذا عن كثب عندما واجهنا حدود DNS غير موثقة في AWS أثرت على أنظمة معالجة البريد الإلكتروني ذات الحجم الكبير لدينا.

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

لا تتردد في تزويدي بأي تعليقات أو اقتراحات لتوسيع هذا المشروع.

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

تشمل بعض الإضافات إلى هذا البرنامج ما يلي:

  • بناء واجهة أمامية أو واجهة مستخدم أسهل للاستخدام

  • تحسين التعامل مع الأخطاء وإعادة المحاولة لأنه إذا حدثت مشكلة وظهرت رسالة خطأ من واجهة برمجة التطبيقات، فإن البرنامج حالياً لا يقوم بإعادة المحاولة

  • النظر في تنفيذ كخدمة وظيفة Azure بدون خادم من أجل التوسع التلقائي وتقليل إدارة البنية التحتية


وأود أيضاً أن أكون فضولياً لمعرفة ما إذا كان من الممكن تحقيق نتائج أسرع مع لغة أخرى مثل Golang أو Erlang/Elixir. بخلاف اختيار اللغة، يمكن أن تؤثر قيود البنية التحتية أيضًا على الأداء - لقد تعلمنا هذا عن كثب عندما واجهنا حدود DNS غير موثقة في AWS أثرت على أنظمة معالجة البريد الإلكتروني ذات الحجم الكبير لدينا.

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

لا تتردد في تزويدي بأي تعليقات أو اقتراحات لتوسيع هذا المشروع.

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

تشمل بعض الإضافات إلى هذا البرنامج ما يلي:

  • بناء واجهة أمامية أو واجهة مستخدم أسهل للاستخدام

  • تحسين التعامل مع الأخطاء وإعادة المحاولة لأنه إذا حدثت مشكلة وظهرت رسالة خطأ من واجهة برمجة التطبيقات، فإن البرنامج حالياً لا يقوم بإعادة المحاولة

  • النظر في تنفيذ كخدمة وظيفة Azure بدون خادم من أجل التوسع التلقائي وتقليل إدارة البنية التحتية


وأود أيضاً أن أكون فضولياً لمعرفة ما إذا كان من الممكن تحقيق نتائج أسرع مع لغة أخرى مثل Golang أو Erlang/Elixir. بخلاف اختيار اللغة، يمكن أن تؤثر قيود البنية التحتية أيضًا على الأداء - لقد تعلمنا هذا عن كثب عندما واجهنا حدود DNS غير موثقة في AWS أثرت على أنظمة معالجة البريد الإلكتروني ذات الحجم الكبير لدينا.

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

لا تتردد في تزويدي بأي تعليقات أو اقتراحات لتوسيع هذا المشروع.

أخبار أخرى

اقرأ المزيد من هذه الفئة

A person is standing at a desk while typing on a laptop.

المنصة الكاملة المدعومة بالذكاء الاصطناعي التي تتوسع مع أعمالك.

A person is standing at a desk while typing on a laptop.

المنصة الكاملة المدعومة بالذكاء الاصطناعي التي تتوسع مع أعمالك.

A person is standing at a desk while typing on a laptop.

المنصة الكاملة المدعومة بالذكاء الاصطناعي التي تتوسع مع أعمالك.