للعثور على إيميلات العمل باستخدام Python، أرسل اسم الشخص ومعلومات شركته، أو رابط ملفه العام على LinkedIn، إلى Waterfall Email Finder API من Datablist عبر مكتبة Requests. تُرجع API إيميل العمل الذي عثرت عليه، وحالة قابلية تسليم العنوان، ومزوّد خدمة البريد.

يبحث المثال الكامل أدناه عن Satya Nadella في microsoft.com وReid Hoffman في greylock.com. وينتظر اكتمال المهمة التي تعمل في الخلفية، ثم يسترجع جميع صفحات النتائج. بعد ذلك، سنعيد استخدام هذه النتائج لإعداد قائمة محلية بجهات الاتصال التي تم التحقق منها، تمهيدًا لمراجعتها قبل إطلاق الحملة.

📌 ملخص لمن لا يملكون وقتًا

استخدم دالة Python واحدة لإرسال جهات الاتصال المعروفة إلى Waterfall Email Finder API، وانتظر اكتمال المهمة، ثم اجمع إيميلات العمل التي تم العثور عليها. بعد ذلك، جهّز جهات الاتصال التي تم التحقق منها لمراجعة الحملة، مع الاحتفاظ بجهات الاتصال التي لم تُحسم نتائجها في قائمتك المصدرية.

  • المدخلات: الاسم الأول واسم العائلة مع نطاق الشركة، أو الاسم الكامل مع اسم الشركة، أو رابط عام لملف LinkedIn.
  • المخرجات: إيميل عمل مرفق بقيمتي emailStatus وmxProvider، ثم قائمة مراجعة محلية لا تتضمن سوى جهات الاتصال التي يتوفر لها إيميل.

💡 تفضّل حلًا دون برمجة؟ استخدم Waterfall Email Finder من Datablist للعثور على إيميلات العمل انطلاقًا من مجموعة بيانات، من دون كتابة أي كود.

إعداد Python وRequests ومفتاح API

في مثال Python هذا، نستخدم حزمة requests. أنشئ ملفًا باسم find_work_emails.py، ثم ثبّت Requests:

shell
python -m pip install requests

أنشئ Personal API Key من صفحة حساب Datablist API. يتطلب الوصول إلى REST API اشتراك Growth. صدّر المفتاح في نافذة Terminal نفسها التي ستشغّل منها السكربت:

shell
export DATABLIST_API_KEY="your-api-key"

تستهلك النتائج الناجحة من Waterfall Email Finder رصيد credits أيضًا. يوضّح قسم التكلفة أدناه الفرق بين هذا الرصيد والاشتراك الذي يتيح الوصول إلى API.

العثور على إيميلات العمل بدالة Python واحدة

الصق السكربت الكامل التالي في find_work_emails.py:

python
import json
import os
import time
import uuid
import requests
def find_emails(people, api_key, settings):
"""Submit one email-finder task, wait, and return every result."""
tasks_url = "https://api.datablist.com/v1/enrichments/tasks"
headers = {"X-API-Key": api_key}
unique_id = str(uuid.uuid4())
print(f"Task uniqueId: {unique_id}", flush=True)
payload = {
"enrichmentId": "waterfall-email-finder",
"uniqueId": unique_id,
"settings": settings,
"inputs": people,
}
response = requests.post(tasks_url, headers=headers, json=payload)
response.raise_for_status()
task = response.json()
task_id = task["@id"]
task_url = f"{tasks_url}/{task_id}"
while task["status"] not in ("completed", "failed", "stopped"):
time.sleep(3)
response = requests.get(task_url, headers=headers)
response.raise_for_status()
task = response.json()
if task["status"] != "completed":
raise RuntimeError(
f"Task {task_id} {task['status']}: {task.get('error')}"
)
results = []
results_url = f"{task_url}/results"
params = {"limit": 100}
while True:
response = requests.get(results_url, headers=headers, params=params)
response.raise_for_status()
page = response.json()
results.extend(page["results"])
next_cursor = page["nextCursor"]
if next_cursor is None:
break
params["cursor"] = next_cursor
return results
if __name__ == "__main__":
api_key = os.environ.get("DATABLIST_API_KEY")
if not api_key:
raise RuntimeError("Set DATABLIST_API_KEY before running this script.")
settings = {"sourceKind": "name_company"}
people = [
{
"inputId": "contact-satya-nadella",
"data": {
"firstName": "Satya",
"lastName": "Nadella",
"companyNameOrDomain": "microsoft.com",
},
},
{
"inputId": "contact-reid-hoffman",
"data": {
"firstName": "Reid",
"lastName": "Hoffman",
"companyNameOrDomain": "greylock.com",
},
},
]
lookup_results = find_emails(people, api_key, settings)
for result in lookup_results:
print(json.dumps(result, indent=2))

شغّله من نافذة Terminal نفسها:

shell
python find_work_emails.py

يمكنك أيضًا تنزيل مثال Requests المحدّث باستمرار. وبعد فهم النسخة المتزامنة التي تنتظر اكتمال التنفيذ، ستجد في المستودع بديلًا غير متزامن باستخدام HTTPX.

أولًا، ترسل الدالة المهمة. تمثّل كل قيمة inputId المعرّف الثابت لجهة الاتصال لديك. ويحتوي كائن data المتداخل على الحقول المستخدمة في البحث. يعيد طلب POST الاستجابة 202 Accepted مع @id، وهو معرّف المهمة الذي تستخدمه الطلبات اللاحقة. يطبع الكود قيمة uniqueId قبل الإرسال، حتى تظل لديك قيمة لاستعادة المهمة إذا فُقدت الاستجابة.

ثانيًا، تتحقق دوريًا من حالة المهمة. تعالج Datablist المهمة في الخلفية. ينتظر عميل Requests هذا ويتحقق من المهمة كل ثلاث ثوانٍ إلى أن تصبح حالتها completed أو failed أو stopped. يتوقف تنفيذ Requests مؤقتًا أثناء الانتظار، لكن مهمة API نفسها تظل غير متزامنة.

ثالثًا، تسترجع الدالة النتائج. قد تضم المهمة عدة صفحات من النتائج. تواصل الحلقة جلب الصفحات إلى أن تصبح قيمة nextCursor مساوية لـnull. استخدم inputId، لا ترتيب الاستجابة، لمطابقة كل نتيجة مع جهة الاتصال المصدرية.

اختيار مسار الإدخال المناسب لبيانات جهات الاتصال

تستخدم كل مهمة وضع إدخال واحدًا. احتفظ بدالة find_emails() نفسها، واستبدل قيمتي settings وpeople بالمسار الذي يناسب بياناتك المصدرية.

اختر قيمة inputId فريدة داخل المهمة، ويُفضّل أن تكون معرّفًا مرتبطًا أصلًا بجهة الاتصال المصدرية. يُعد معرّف سجل CRM أو أي معرّف Lead ثابت خيارًا مناسبًا. تعيد Datablist هذه القيمة مع النتيجة، ما يتيح لك مطابقة السجلات من دون مقارنة الأسماء أو افتراض أن ترتيب الاستجابة يطابق ترتيب الإدخال.

عندما تكون لديك حقول منفصلة للأسماء ونطاق الشركة، استخدم sourceKind: "name_company". يُعد النطاق المعروف أوضح معرّف للشركة:

python
settings = {"sourceKind": "name_company"}
people = [
{
"inputId": "contact-satya-nadella",
"data": {
"firstName": "Satya",
"lastName": "Nadella",
"companyNameOrDomain": "microsoft.com",
},
}
]
lookup_results = find_emails(people, api_key, settings)

عندما يحتوي مصدرك على حقل واحد للاسم واسم للشركة، فعّل useFullName:

python
settings = {"sourceKind": "name_company", "useFullName": True}
people = [
{
"inputId": "contact-satya-nadella",
"data": {
"fullName": "Satya Nadella",
"companyNameOrDomain": "Microsoft",
},
}
]
lookup_results = find_emails(people, api_key, settings)

عندما يتوفر لديك رابط عام لملف الشخص، استخدم مسار LinkedIn:

python
settings = {"sourceKind": "linkedin"}
people = [
{
"inputId": "contact-satya-nadella",
"data": {
"linkedInProfile": "https://www.linkedin.com/in/satyanadella/"
},
}
]
lookup_results = find_emails(people, api_key, settings)

يقبل هذا المسار رابط ملف عام بصيغة linkedin.com/in/... ليستخدمه كسياق للشخص أثناء البحث عن الإيميل. ولا تُقبل روابط ملفات Sales Navigator.

إذا كنت بحاجة إلى اختيار مفاتيح المزوّدين وترتيبهم، فراجع دليل إعداد Waterfall مخصّص. لا يتطلب Waterfall المُدار والمستخدم هنا أي إعداد خاص بكل مزوّد.

كيف يعثر Waterfall المُدار على الإيميل؟

في طلب يعتمد على الاسم والشركة، تستخدم API النطاق المرسل مباشرةً. أما عند إرسال اسم الشركة، فتحاول أولًا تحويله إلى نطاق شركة صالح للاستخدام. وفي طلب LinkedIn، تنظّف رابط الملف العام وتحول بيانات الشخص والشركة المتاحة إلى السياق الذي تحتاج إليه خدمات البحث عن الإيميلات المؤهلة.

يرسل Waterfall المُدار جهة الاتصال التي لم تُحسم نتيجتها إلى مزوّد مؤهل. وإذا لم يُرجع إيميلًا صالحًا للاستخدام، يمكن لمزوّد مؤهل آخر محاولة البحث عن جهة الاتصال نفسها. ويتوقف التسلسل لهذه الجهة فور قبول إيميل عمل. لا تذكر الاستجابة الافتراضية اسم مزوّد البحث.

لماذا يفيد Waterfall في حملات Outbound الدولية؟

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

بالنسبة إلى فريق المبيعات أو النمو، يمنح Waterfall كل جهة اتصال لم تُحسم نتيجتها فرصة إضافية للعثور عليها قبل استبعادها من قائمة Outbound. وبذلك يمكن استعادة جهات اتصال قد يفوّتها سير عمل يعتمد على مزوّد واحد، مع إبقاء العملية التقنية موحّدة للفريق.

تدير Datablist تسلسل المزوّدين المؤهلين من خلال تكامل API واحد. ترسل جهة الاتصال مرة واحدة وتتلقى النتائج بتنسيق موحّد، بدلًا من بناء تكامل مستقل لكل مزوّد وصيانته، ثم دمج الاستجابات بنفسك.

ينتقل البحث عن إيميل العمل إلى مزوّد آخر عند عدم العثور على نتيجة، ويتوقف عند العثور على إيميل
ينتقل البحث عن إيميل العمل إلى مزوّد آخر عند عدم العثور على نتيجة، ويتوقف عند العثور على إيميل

قراءة النتائج الكاملة

أعادت مهمة المثال نتيجتين ناجحتين. أُخفي جزء من عناوين الإيميل أدناه.

json
{
"taskId": "2286103ce43748828e27ce71b9cc526b",
"results": [
{
"inputId": "contact-satya-nadella",
"status": "success",
"outputs": {
"gender": null,
"mxProvider": "microsoft",
"emailFound": "s*******@microsoft.com",
"emailStatus": "verified"
},
"creditsUsed": "25",
"error": null
},
{
"inputId": "contact-reid-hoffman",
"status": "success",
"outputs": {
"gender": null,
"mxProvider": "mimecast",
"emailFound": "r*******@greylock.com",
"emailStatus": "verified"
},
"creditsUsed": "25",
"error": null
}
],
"nextCursor": null
}

تمثّل emailFound إيميل العمل المُعاد. وتصف emailStatus قابلية تسليمه وقت البحث، بينما تحدد mxProvider مزوّد استضافة البريد عند توفره. السجلان كلاهما بحالة verified، وقد استهلك كل منهما 25 credits.

يضع كود الحملة أدناه نتائج verified في campaign_contacts، ويحتفظ بالعناوين الأخرى التي عُثر عليها في needs_verification. وتمثّل قيمة creditsUsed التكلفة الفعلية المعتمدة لكل نتيجة.

لا تتضمن النتيجة المُدارة الافتراضية الحقل finderProvider. فهذا الحقل ناتج شرطي لسير عمل صالح يعتمد على الاسم والشركة ومفاتيح مخصّصة، لذا يجب ألا يستنتجه السكربت من mxProvider.

تجهيز الإيميلات لمراجعة الحملة

بعد التأكد من نجاح البحث المباشر، أضف الكتلة التالية إلى نهاية السكربت، بعد إرجاع lookup_results. تعمل هذه الكتلة على تجهيز جهات الاتصال محليًا لمراجعة الحملة، اعتمادًا على عملية البحث التي شغّلتها بالفعل.

python
results_by_id = {result["inputId"]: result for result in lookup_results}
campaign_contacts = []
needs_verification = []
for person in people:
result = results_by_id.get(person["inputId"])
if result is None:
continue
if result["status"] != "success":
continue
if not result["outputs"].get("emailFound"):
continue
contact = person.copy()
contact["result"] = result
if result["outputs"].get("emailStatus") == "verified":
campaign_contacts.append(contact)
else:
needs_verification.append(contact)
print(json.dumps({
"campaign_contacts": campaign_contacts,
"needs_verification": needs_verification,
}, indent=2))

تجعل results_by_id ترتيب الاستجابة غير مهم. بالنسبة إلى Satya، تقرأ الحلقة contact-satya-nadella من قائمة people الأصلية، وتعثر على النتيجة التي تحمل قيمة inputId نفسها، ثم تنسخ السجل المصدري وتُرفق كائن API الكامل ضمن result. وبما أن العنوان موجود وحالته verified، تُضاف جهة الاتصال إلى campaign_contacts. ويسلك Reid المسار نفسه، حتى لو وصلت نتيجته من API أولًا.

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

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

تقدير تكلفة Waterfall Email Finder API

يستهلك Waterfall المُدار الافتراضي 25 credits مقابل كل إيميل عمل يتم العثور عليه. أما البحث الافتراضي غير الناجح، فيستهلك 0 credits. اقرأ قيمة creditsUsed من كل نتيجة بدلًا من ضرب العدد الإجمالي لجهات الاتصال المُرسلة.

يستهلك الإيميل الذي يتم العثور عليه بحالة unverified مقدار 25 credits أيضًا، حتى لو وضعه كود الحملة المحلي في needs_verification بدلًا من campaign_contacts.

استهلك الإيميلان اللذان عُثر عليهما في هذا المثال 50 credits. ولأغراض التخطيط:

  • يستهلك العثور على 100 إيميل عمل مقدار 2,500 credits.
  • يستهلك العثور على 1,000 إيميل عمل مقدار 25,000 credits.
  • قد تستهلك قائمة إدخال أكبر رصيدًا أقل من عدد جهات الاتصال × 25، لأن عمليات البحث التي لا تعيد نتيجة لا تُحتسب تكلفتها.

يتطلب الوصول إلى REST API اشتراك Growth. وفق سعر الشحن الحالي بالدولار الأمريكي، تشتري 20 دولارًا رصيدًا قدره 20,000 credits، أي 0.001 دولار لكل credit. وبهذا السعر، تبلغ قيمة 25,000 credits نحو 25 دولارًا. ولا يدخل الاشتراك ضمن حساب الرصيد هذا. راجع صفحة أسعار Datablist للاطلاع على أحدث تفاصيل الاشتراكات والباقات.

Loading pricing…

حل الأخطاء الشائعة واستعادة المهمة

مفتاح API مفقود. يُظهر السكربت الخطأ Set DATABLIST_API_KEY before running this script. قبل تنفيذ أي طلب. صدّر المتغير في نافذة Terminal الحالية، ثم شغّل الملف مجددًا.

تعيد API خطأ في المصادقة. تعرض raise_for_status() إخفاق HTTP. انسخ Personal API Key صالحًا، وتأكد من أن ترويسة X-API-Key تستخدمه، ثم أعد التشغيل بعد تصحيح بيانات الاعتماد.

لا يتوفر في الحساب رصيد credits كافٍ. تحقق من رصيد الحساب. أضف credits أو انتظر تخصيص الرصيد التالي ضمن اشتراكك قبل إرسال المزيد من المهام.

حقول الإدخال لا تطابق الوضع المحدد. استخدم حقول أسماء منفصلة أو fullName مع name_company، أو استخدم linkedInProfile مع linkedin. صحح جهة الاتصال وأرسلها باستخدام وضع واحد متسق.

حالة المهمة هي failed أو stopped. يُظهر السكربت RuntimeError يتضمن معرّف المهمة وحالتها النهائية وأي خطأ متاح فيها. افحص المهمة وأي نتائج متاحة قبل تحديد جهات الاتصال التي لا تزال بحاجة إلى البحث.

فُقدت استجابة POST. انسخ قيمة uniqueId التي طُبعت قبل الإرسال. اعرض مهامك باستخدام المفتاح نفسه:

shell
curl "https://api.datablist.com/v1/enrichments/tasks" \
-H "X-API-Key: $DATABLIST_API_KEY"

ابحث عن المهمة التي تحمل قيمة uniqueId المطبوعة، وانسخ قيمة @id الخاصة بها، ثم تابع باستخدام رابط حالة المهمة أو النتائج. يتيح لك ذلك استعادة المهمة الحالية بدلًا من إرسال جهات الاتصال مجددًا بصورة تلقائية. تشرح وثائق Task API دورة حياة المهمة وEndpoints الخاصة بالنتائج.

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

أصبح لديك الآن دالة Python Requests متزامنة تنتظر اكتمال التنفيذ، وتدعم مسارات الإدخال الثلاثة، إلى جانب فلتر محلي يجهّز الإيميلات التي تم العثور عليها والتحقق منها لمراجعة الحملة.

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