بناء أداة التحقق من المستلمين لشركة Bird بشكل غير متزامن وبالجملة
زاكاري صامويلز
26/05/2022
البريد الإلكتروني
1 min read

النقاط الرئيسية
قام المؤلف ببناء أداة لتأكيد المستلمين بكميات كبيرة للتحقق من ملايين عناوين البريد الإلكتروني بكفاءة باستخدام Bird’s Recipient Validation API.
ثبت أن Node.js أسرع وأكثر قابلية للتوسع من Python بسبب عدم وجود تأثير حظر في عمليات الإدخال/الإخراج وغياب قيود GIL.
تقرأ الأداة ملفات CSV بشكل غير متزامن، وتستدعي API التحقق لكل بريد إلكتروني، وتكتب النتائج إلى CSV جديد في الوقت الفعلي.
تتجنب الطريقة الاختناقات الذاكرية وتحسن الإنتاجية إلى حوالي 100,000 عملية تحقق في أقل من دقيقة.
يمكن أن تشمل التحسينات المستقبلية تحسين معالجة محاولات إعادة المحاولة، واجهة مستخدم سهلة الاستخدام، أو الترحيل إلى بيئات بلا خادم للتوسع.
أبرز الأسئلة والأجوبة
ما هو الغرض من أداة التحقق من المستلمين بالتزامن الضخم؟
يقوم بالتحقق من كميات كبيرة من عناوين البريد الإلكتروني من خلال التكامل المباشر مع واجهة برمجة التطبيقات للتحقق من المستلمين في Bird، حيث يتم إخراج النتائج المحققة بسرعة بدون تحميل يدوي.
لماذا تم استخدام Python في البداية وتم استبداله لاحقًا بـ Node.js؟
الـ(Python's) قيد المترجم العالمي (GIL) يحد من التزامن، بينما يتيح (Node.js) تنفيذًا غير متزامن حقيقيًا، مما يؤدي إلى مكالمات واجهة برمجة التطبيقات المتوازية بسرعة أكبر بكثير.
كيف يتعامل الأداة مع الملفات الكبيرة دون نفاد الذاكرة؟
بدلاً من تحميل جميع البيانات مرة واحدة، يعالج البرنامج النصي كل سطر CSV على حدة—يرسل طلب التحقق ويكتب النتائج فوراً إلى ملف CSV جديد.
ما المشكلة التي يحلها الأداة للمطورين؟
يمكنه تنفيذ التحقق من صحة قائمة البريد الإلكتروني على نطاق واسع، وتجاوز الحد الأقصى 20 ميجابايت لمشغل SparkPost's القائم على واجهة المستخدم، وإزالة الحاجة إلى تحميل ملفات متعددة يدوياً.
ما مدى سرعة النسخة النهائية من البرنامج؟
حوالي 100,000 عملية تحقق تُكتمل في 55 ثانية، مقارنة بأكثر من دقيقة باستخدام النسخة العملية.
ما المشكلات التي تمت مواجهتها على أنظمة Windows؟
تسببت تجميع اتصالات العميل في Node.js HTTP في حدوث أخطاء "ENOBUFS" بعد العديد من الطلبات المتزامنة، والتي تم إصلاحها عن طريق تكوين إعادة استخدام اتصال axios.
ما التحسينات المستقبلية المقترحة؟
إضافة معالجة الأخطاء والمحاولات المتكررة، وإنشاء واجهة أمامية، أو تنفيذ الأداة كوظيفة Azure بدون خادم لتحسين القدرة على التوسع والمرونة.
بالنسبة للشخص الذي يبحث عن برنامج بسيط وسريع يقبل ملف بصيغة CSV، ويستدعي واجهة برمجة تطبيقات التحقق من المستلم، ويُخرج ملف CSV، فهذا البرنامج مناسب لك.
عند بناء تطبيقات البريد الإلكتروني، غالبًا ما يحتاج المطورون إلى دمج خدمات وواجهات برمجة التطبيقات متعددة. يوفر فهم أساسيات واجهة برمجة تطبيقات البريد الإلكتروني في البنية التحتية السحابية الأساس لبناء أدوات قوية مثل نظام التحقق الشامل الذي سنقوم بإنشائه في هذا الدليل.
أحد الأسئلة التي نتلقاها أحيانًا هو، كيف يمكنني التحقق من قوائم البريد الإلكتروني بكميات كبيرة مع التحقق من المستلم? هناك خياران هنا، أحدهما هو تحميل ملف من خلال واجهة مستخدم SparkPost للتحقق، والآخر هو إجراء مكالمات فردية لكل بريد إلكتروني إلى API (حيث أن API هي تحقق من بريد إلكتروني فردي).
الخيار الأول يعمل بشكل جيد ولكن لديه حد 20 ميجابايت (حوالي 500,000 عنوان). ماذا لو كان لدى شخص ما قائمة بريد إلكتروني تحتوي على ملايين العناوين؟ قد يعني ذلك تقسيمها إلى 1,000's من تحميل ملفات 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 للتحقق، والآخر هو إجراء مكالمات فردية لكل بريد إلكتروني إلى API (حيث أن API هي تحقق من بريد إلكتروني فردي).
الخيار الأول يعمل بشكل جيد ولكن لديه حد 20 ميجابايت (حوالي 500,000 عنوان). ماذا لو كان لدى شخص ما قائمة بريد إلكتروني تحتوي على ملايين العناوين؟ قد يعني ذلك تقسيمها إلى 1,000's من تحميل ملفات 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 للتحقق، والآخر هو إجراء مكالمات فردية لكل بريد إلكتروني إلى API (حيث أن API هي تحقق من بريد إلكتروني فردي).
الخيار الأول يعمل بشكل جيد ولكن لديه حد 20 ميجابايت (حوالي 500,000 عنوان). ماذا لو كان لدى شخص ما قائمة بريد إلكتروني تحتوي على ملايين العناوين؟ قد يعني ذلك تقسيمها إلى 1,000's من تحميل ملفات CSV.
نظرًا لأن تحميل آلاف ملفات CSV يبدو بعيد المنال قليلاً، أخذت هذه الحالة وبدأت أتساءل عن مدى سرعة تشغيل واجهة برمجة التطبيقات. في هذه المشاركة على المدونة، سأشرح ما حاولت وكيف وصلت في النهاية إلى برنامج يمكنه الحصول على حوالي 100,000 التحقق في 55 ثانية (في حين حصلت في واجهة المستخدم على حوالي 100,000 التحقق في دقيقة و10 ثواني).
النهج | التحقق المختبر | الوقت للإكمال | التدفق التقريبي |
|---|---|---|---|
أداة Node.js غير متزامنة بكميات كبيرة | 100,000 | 55 ثانية | ~1,818 تحقق/ثانية |
تحميل واجهة مستخدم SparkPost | 100,000 | 1 دقيقة و10 ثواني | ~1,428 تحقق/ثانية |
وعلى الرغم من أن هذا سيستغرق حوالي 100 ساعة للانتهاء مع حوالي 654 مليون تحقق، إلا أن هذا البرنامج النصي يمكن أن يعمل في الخلفية، مما يوفر وقتًا كبيرًا.
يمكن العثور على الإصدار النهائي من هذا البرنامج هنا.
أول خطأ لي: استخدام Python
بايثون هي واحدة من لغات البرمجة المفضلة لدي. إنها تتميز في العديد من المجالات وهي مباشرة بشكل لا يصدق. ومع ذلك، فإن إحدى المجالات التي لا تبرز فيها هي العمليات المتزامنة. بينما لدى بايثون القدرة على تشغيل الوظائف غير المتزامنة، لديها ما يُعرف بقفل المترجم للغة بايثون أو 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 بسبب قدرتها على أداء عمليات الإدخال والإخراج غير المحظورة بشكل ممتاز. خيار رائع آخر لمعالجة عمليات الـ API غير المتزامنة هو بناء مستهلكي webhook بدون خادم باستخدام 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 بسبب قدرتها على أداء عمليات الإدخال والإخراج غير المحظورة بشكل ممتاز. خيار رائع آخر لمعالجة عمليات الـ API غير المتزامنة هو بناء مستهلكي webhook بدون خادم باستخدام 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 بسبب قدرتها على أداء عمليات الإدخال والإخراج غير المحظورة بشكل ممتاز. خيار رائع آخر لمعالجة عمليات الـ API غير المتزامنة هو بناء مستهلكي webhook بدون خادم باستخدام Azure Functions، والتي يمكنها التعامل بكفاءة مع الأحمال المتغيرة. كما أنني مألوف جدًا بالبرمجة في NodeJS.
باستخدام الجوانب غير المتزامنة من Node.js، عمل هذا الحل بشكل جيد. لمزيد من التفاصيل حول البرمجة غير المتزامنة في Node.js، انظر دليل RisingStack للبرمجة غير المتزامنة في Node.js.
خطأي الثاني: محاولة قراءة الملف في الذاكرة
كانت فكرتي الأولية كما يلي:

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

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

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

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

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

أولاً، إدخال قائمة من الرسائل الإلكترونية بصيغة 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 } }
النص البرمجي التالي هو في الحقيقة الجزء الأكبر من البرنامج، لذا سأقوم بتقسيمه وشرح ما يحدث. لكل سطر من الملف المدخل:
بشكل غير متزامن قم بأخذ هذا السطر واستدعاء API لعملية التحقق من المستقبل.
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)
التحقق مما إذا كان السبب null، وفي هذه الحالة، تعبئة قيمة فارغة (هذا لضمان أن صيغة 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` ); } });
مسألة أخيرة وجدت أن هذه الطريقة عملت بشكل رائع على Mac، ولكن صادفت الخطأ التالي عند استخدام Windows بعد حوالي 10,000 تحقق:
خطأ: connect ENOBUFS XX.XX.XXX.XXX:443 – محلي (غير محدد:غير محدد) مع البريد الإلكتروني XXXXXXX@XXXXXXXXXX.XXX
بعد القيام ببعض الأبحاث الإضافية، يبدو أن المشكلة تتعلق بحزمة اتصالات HTTP الخاصة بـ NodeJS التي لا تعيد استخدام الاتصالات. وجدت هذا المقال في Stackoverflow بخصوص هذه المسألة، وبعد البحث الإضافي، وجدت اعداداً افتراضياً جيدًا لمكتبة axios التي حلت هذه المشكلة. لا زلت غير متأكد من سبب حدوث هذه المشكلة على Windows فقط وليس على Mac.
بعد قراءة والتحقق من صحة معطيات الترمينال، أقوم بتشغيل الكود التالي. أولًا، أقرأ ملف 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 } }
النص البرمجي التالي هو في الحقيقة الجزء الأكبر من البرنامج، لذا سأقوم بتقسيمه وشرح ما يحدث. لكل سطر من الملف المدخل:
بشكل غير متزامن قم بأخذ هذا السطر واستدعاء API لعملية التحقق من المستقبل.
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)
التحقق مما إذا كان السبب null، وفي هذه الحالة، تعبئة قيمة فارغة (هذا لضمان أن صيغة 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` ); } });
مسألة أخيرة وجدت أن هذه الطريقة عملت بشكل رائع على Mac، ولكن صادفت الخطأ التالي عند استخدام Windows بعد حوالي 10,000 تحقق:
خطأ: connect ENOBUFS XX.XX.XXX.XXX:443 – محلي (غير محدد:غير محدد) مع البريد الإلكتروني XXXXXXX@XXXXXXXXXX.XXX
بعد القيام ببعض الأبحاث الإضافية، يبدو أن المشكلة تتعلق بحزمة اتصالات HTTP الخاصة بـ NodeJS التي لا تعيد استخدام الاتصالات. وجدت هذا المقال في Stackoverflow بخصوص هذه المسألة، وبعد البحث الإضافي، وجدت اعداداً افتراضياً جيدًا لمكتبة axios التي حلت هذه المشكلة. لا زلت غير متأكد من سبب حدوث هذه المشكلة على Windows فقط وليس على Mac.
بعد قراءة والتحقق من صحة معطيات الترمينال، أقوم بتشغيل الكود التالي. أولًا، أقرأ ملف 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 } }
النص البرمجي التالي هو في الحقيقة الجزء الأكبر من البرنامج، لذا سأقوم بتقسيمه وشرح ما يحدث. لكل سطر من الملف المدخل:
بشكل غير متزامن قم بأخذ هذا السطر واستدعاء API لعملية التحقق من المستقبل.
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)
التحقق مما إذا كان السبب null، وفي هذه الحالة، تعبئة قيمة فارغة (هذا لضمان أن صيغة 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` ); } });
مسألة أخيرة وجدت أن هذه الطريقة عملت بشكل رائع على Mac، ولكن صادفت الخطأ التالي عند استخدام Windows بعد حوالي 10,000 تحقق:
خطأ: connect ENOBUFS XX.XX.XXX.XXX:443 – محلي (غير محدد:غير محدد) مع البريد الإلكتروني XXXXXXX@XXXXXXXXXX.XXX
بعد القيام ببعض الأبحاث الإضافية، يبدو أن المشكلة تتعلق بحزمة اتصالات HTTP الخاصة بـ NodeJS التي لا تعيد استخدام الاتصالات. وجدت هذا المقال في Stackoverflow بخصوص هذه المسألة، وبعد البحث الإضافي، وجدت اعداداً افتراضياً جيدًا لمكتبة axios التي حلت هذه المشكلة. لا زلت غير متأكد من سبب حدوث هذه المشكلة على Windows فقط وليس على Mac.
الخطوات التالية
لشخص يبحث عن برنامج بسيط وسريع يستقبل ملف CSV، ويستدعي API التحقق من المستلم، ويُصدر ملف CSV، هذا البرنامج مناسب لك.
بعض الإضافات لهذا البرنامج ستكون كما يلي:
إنشاء واجهة أمامية أو واجهة مستخدم أسهل للاستخدام
تحسين معالجة الأخطاء والمحاولات لأن البرنامج حالياً لا يعيد المحاولة إذا حدث خطأ في استدعاء API لسبب ما
اعتبر التنفيذ كـ وظيفة Azure بدون خادم لتحقيق التوسع التلقائي وتقليل إدارة البنية التحتية
سأكون فضولياً أيضًا لمعرفة ما إذا كان يمكن تحقيق نتائج أسرع باستخدام لغة أخرى مثل Golang أو Erlang/Elixir. إلى جانب اختيار اللغة، يمكن أن تؤثر قيود البنية التحتية أيضًا على الأداء - لقد تعلمنا ذلك بأنفسنا عندما واجهنا قيود DNS غير موثقة في AWS التي أثرت على أنظمة معالجة البريد الإلكتروني ذات الحجم الكبير لدينا.
للمطورين المهتمين بدمج معالجة API مع أدوات سير العمل البصرية، اطلع على كيفية دمج Flow Builder مع Google Cloud Functions لأتمتة سير العمل بدون كود.
يرجى عدم التردد في تقديم أي ملاحظات أو اقتراحات لتوسيع هذا المشروع.
لشخص يبحث عن برنامج بسيط وسريع يستقبل ملف CSV، ويستدعي API التحقق من المستلم، ويُصدر ملف CSV، هذا البرنامج مناسب لك.
بعض الإضافات لهذا البرنامج ستكون كما يلي:
إنشاء واجهة أمامية أو واجهة مستخدم أسهل للاستخدام
تحسين معالجة الأخطاء والمحاولات لأن البرنامج حالياً لا يعيد المحاولة إذا حدث خطأ في استدعاء API لسبب ما
اعتبر التنفيذ كـ وظيفة Azure بدون خادم لتحقيق التوسع التلقائي وتقليل إدارة البنية التحتية
سأكون فضولياً أيضًا لمعرفة ما إذا كان يمكن تحقيق نتائج أسرع باستخدام لغة أخرى مثل Golang أو Erlang/Elixir. إلى جانب اختيار اللغة، يمكن أن تؤثر قيود البنية التحتية أيضًا على الأداء - لقد تعلمنا ذلك بأنفسنا عندما واجهنا قيود DNS غير موثقة في AWS التي أثرت على أنظمة معالجة البريد الإلكتروني ذات الحجم الكبير لدينا.
للمطورين المهتمين بدمج معالجة API مع أدوات سير العمل البصرية، اطلع على كيفية دمج Flow Builder مع Google Cloud Functions لأتمتة سير العمل بدون كود.
يرجى عدم التردد في تقديم أي ملاحظات أو اقتراحات لتوسيع هذا المشروع.
لشخص يبحث عن برنامج بسيط وسريع يستقبل ملف CSV، ويستدعي API التحقق من المستلم، ويُصدر ملف CSV، هذا البرنامج مناسب لك.
بعض الإضافات لهذا البرنامج ستكون كما يلي:
إنشاء واجهة أمامية أو واجهة مستخدم أسهل للاستخدام
تحسين معالجة الأخطاء والمحاولات لأن البرنامج حالياً لا يعيد المحاولة إذا حدث خطأ في استدعاء API لسبب ما
اعتبر التنفيذ كـ وظيفة Azure بدون خادم لتحقيق التوسع التلقائي وتقليل إدارة البنية التحتية
سأكون فضولياً أيضًا لمعرفة ما إذا كان يمكن تحقيق نتائج أسرع باستخدام لغة أخرى مثل Golang أو Erlang/Elixir. إلى جانب اختيار اللغة، يمكن أن تؤثر قيود البنية التحتية أيضًا على الأداء - لقد تعلمنا ذلك بأنفسنا عندما واجهنا قيود DNS غير موثقة في AWS التي أثرت على أنظمة معالجة البريد الإلكتروني ذات الحجم الكبير لدينا.
للمطورين المهتمين بدمج معالجة API مع أدوات سير العمل البصرية، اطلع على كيفية دمج Flow Builder مع Google Cloud Functions لأتمتة سير العمل بدون كود.
يرجى عدم التردد في تقديم أي ملاحظات أو اقتراحات لتوسيع هذا المشروع.



