يمكنك العثور على أرقام الهواتف من LinkedIn باستخدام Python عبر إرسال روابط الملفات الشخصية العامة بصيغة linkedin.com/in/... إلى Waterfall Phone Finder API من Datablist. تتحقق الـ API بالتتابع من مزوّدي بيانات الهواتف المؤهلين، ثم تُرجع الأرقام التي عُثر عليها بالصيغة الدولية مع رموز البلدان.

يستخدم هذا الدليل مكتبة Requests لتنفيذ تكامل كامل واحد باستخدام Python. وبعد ذلك، يعيد استخدام النتائج نفسها لإعداد قائمة محلية لمكالمات المبيعات تتضمن اسم كل عميل محتمل، ورابط LinkedIn، والمسؤول عن الحساب.

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

استخدم API واحدة للعثور على أرقام الهواتف انطلاقًا من قائمة روابط ملفات LinkedIn الشخصية العامة، ثم جهّز قائمة محلية لمكالمات المبيعات تتضمن اسم كل عميل محتمل والمسؤول عن حسابه. يجرّب Waterfall Phone Finder المزوّدين المؤهلين بالتتابع، ويحتفظ بالأرقام التي عُثر عليها لتكون جاهزة لمراجعة فريقك.

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

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

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

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

في مثال Python هذا، أستخدم حزمة requests. أنشئ مجلدًا للمشروع، وفعّل بيئة افتراضية، وثبّت Requests، ثم أنشئ ملف السكربت:

shell
mkdir waterfall-phone-lookup
cd waterfall-phone-lookup
python -m venv .venv
source .venv/bin/activate
python -m pip install requests
touch find_phone_numbers.py
export DATABLIST_API_KEY="your-api-key"

أنشئ Personal API Key في حسابك على Datablist واستخدمه مع DATABLIST_API_KEY. يتوفر الوصول إلى REST API ضمن خطة Growth. وسأشرح لاحقًا في الدليل الرصيد الذي تستهلكه عمليات البحث الناجحة.

العثور على أرقام الهواتف بدالة Python واحدة

يستخدم المثال ثلاثة سجلات قابلة للتعديل لعملاء محتملين. يحتوي كل سجل على prospect_id ثابت، واسم، ورابط LinkedIn عام، والمسؤول عن الحساب المكلّف بمراجعة النتيجة:

  • Florian Poullin على https://www.linkedin.com/in/florianpoullin/
  • Satya Nadella على https://www.linkedin.com/in/satyanadella/
  • Reid Hoffman على https://www.linkedin.com/in/reidhoffman/

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

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

python
import json
import os
import time
import uuid
import requests
def find_phone_numbers(prospects, api_key, settings):
"""Submit one phone lookup task, wait, and return every result."""
tasks_url = "https://api.datablist.com/v1/enrichments/tasks"
headers = {"X-API-Key": api_key}
inputs = [
{
"inputId": prospect["prospect_id"],
"data": {"profileUrl": prospect["linkedin_url"]},
}
for prospect in prospects
]
unique_id = str(uuid.uuid4())
print(f"Task uniqueId: {unique_id}", flush=True)
payload = {
"enrichmentId": "waterfall-phone-finder",
"uniqueId": unique_id,
"settings": settings,
"inputs": inputs,
}
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 SystemExit("Set DATABLIST_API_KEY before running this script.")
prospects = [
{
"prospect_id": "prospect-florian-poullin",
"name": "Florian Poullin",
"linkedin_url": "https://www.linkedin.com/in/florianpoullin/",
"account_owner": "Habib",
},
{
"prospect_id": "prospect-satya-nadella",
"name": "Satya Nadella",
"linkedin_url": "https://www.linkedin.com/in/satyanadella/",
"account_owner": "Florian",
},
{
"prospect_id": "prospect-reid-hoffman",
"name": "Reid Hoffman",
"linkedin_url": "https://www.linkedin.com/in/reidhoffman/",
"account_owner": "Habib",
},
]
settings = {"cacheAge": "30"}
phone_results = find_phone_numbers(prospects, api_key, settings)
print(json.dumps(phone_results, indent=2, ensure_ascii=False))

شغّل الملف باستخدام:

shell
python find_phone_numbers.py

يمكنك أيضًا تنزيل مثال Requests المتزامن الذي تتولى Datablist صيانته. يحافظ السكربت أعلاه على الترتيب البسيط نفسه: إرسال مهمة واحدة، وانتظار اكتمالها، ثم استرجاع جميع صفحات النتائج.

إرسال طلب البحث عن رقم الهاتف

تحوّل صيغة list comprehension كل عميل محتمل في المصدر إلى بنية المدخلات التي تتطلبها الـ API. يصبح prospect_id هو inputId، بينما يتحول linkedin_url إلى data.profileUrl. استخدم رابطًا عامًا لملف شخصي يتبع المسار linkedin.com/in/.... لا يدعم هذا الإثراء روابط Sales Navigator.

تنشئ الدالة قيمة uniqueId وتطبعها قبل طلب POST. يرسل الطلب هذه القيمة مع معرّف الإثراء waterfall-phone-finder، وإعداد الـ cache، والمدخلات. يعيد الإرسال الناجح استجابة HTTP بالحالة 202 Accepted ومعرّف المهمة @id، بينما تستمر المعالجة في الخلفية.

يسرد مرجع Waterfall Phone Finder API الإعدادات الحالية، وحقول الإدخال، والمخرجات، والرصيد، والحدود.

بالنسبة إلى عميل محتمل واحد، يتخذ جسم الطلب البنية التالية:

json
{
"enrichmentId": "waterfall-phone-finder",
"uniqueId": "generated-uuid",
"settings": {
"cacheAge": "30"
},
"inputs": [
{
"inputId": "prospect-florian-poullin",
"data": {
"profileUrl": "https://www.linkedin.com/in/florianpoullin/"
}
}
]
}

رابط الملف الشخصي العام هو مدخل البحث. ويمثل inputId مفتاح الربط الذي يصل النتيجة بالاسم والمسؤول عن الحساب في سجل المصدر. اختر معرّفًا ثابتًا داخل نظامك وفريدًا ضمن المهمة.

انتظار المهمة التي تعمل في الخلفية

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

استرجاع جميع صفحات النتائج

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

كيف يعثر Waterfall المُدار على رقم الهاتف

يضبط المثال قيمة cacheAge على "30". تتحقق Datablist أولًا من وجود نتيجة مؤهلة لا يتجاوز عمرها 30 يومًا. وإذا وُجدت، يمكن للـ API إعادة استخدامها. يسمح هذا الإعداد باستخدام نتيجة من الـ cache، لكنه لا يضمن وجودها.

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

استمرار البحث المتسلسل عن الهاتف بعد عدم العثور على نتيجة وتوقفه عند العثور على رقم
استمرار البحث المتسلسل عن الهاتف بعد عدم العثور على نتيجة وتوقفه عند العثور على رقم

لماذا يفيد Waterfall عبر البلدان والأسواق؟

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

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

اضبط cacheAge على "0" عندما يتطلب سير عملك إجراء بحث جديد تحديدًا. واحتفظ بالقيمة "30" في هذا المثال للسماح بإعادة استخدام نتيجة مؤهلة من الـ cache.

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

في ما يلي إحدى نتائج Florian Poullin. جرى إخفاء أرقام الهاتف أدناه.

json
{
"inputId": "prospect-florian-poullin",
"status": "success",
"outputs": {
"finderProvider": "wizleads",
"phoneNumber": "+33 • •• •• •• ••",
"phoneNumberCountry": "FR"
},
"creditsUsed": "500",
"error": null
}

يحتوي phoneNumber على الرقم بالصيغة الدولية عند توفره. وتشير البادئة +33 والقيمة phoneNumberCountry: "FR" إلى أن الرقم فرنسي.

يمثل finderProvider أداة البحث التي أبلغت عن هذه النتيجة. استهلك الصف 500 credit، وتعني error: null أن الـ API لم تُرجع خطأ على مستوى الصف.

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

بعد توفر phone_results، يمكنك إعداد قائمة محلية ليراجعها فريق المبيعات. أضف الكتلة التالية بعد طباعة النتائج في الملف نفسه. تعيد هذه الكتلة استخدام النتائج الحالية ولا تُجري استدعاءً ثانيًا للـ API:

python
results_by_id = {
result["inputId"]: result
for result in phone_results
}
call_list = []
for prospect in prospects:
result = results_by_id.get(prospect["prospect_id"])
if not result or result["status"] != "success":
continue
phone_number = result.get("outputs", {}).get("phoneNumber")
if not phone_number:
continue
call_list_record = prospect.copy()
call_list_record["result"] = result
call_list.append(call_list_record)
print(json.dumps(call_list, indent=2, ensure_ascii=False))

يستخدم فهرس النتائج inputId بوصفه مفتاح الربط، بحيث تظل كل نتيجة مرتبطة بسجل مصدرها دون الاعتماد على موضعها في القائمة. تحتوي حمولة الـ API على معرّف كل عميل محتمل ورابط ملفه الشخصي، بينما تحتفظ قائمة prospects الأصلية بالاسم والمسؤول عن الحساب اللازمين لقائمة المكالمات.

في صف Florian، تربط القيمة prospect-florian-poullin النتيجة المُعادة بالقيم الأصلية name وlinkedin_url وaccount_owner. ويقرأ الفريق رقم الهاتف ورمز البلد FR من result.outputs. كما يظل كائن الـ API الكامل، بما يشمله من حالة ورصيد وخطأ، مرفقًا تحت result.

تتبع حلقة قائمة المكالمات ترتيب prospects. وهي تنسخ كل عميل محتمل مُدرج قبل إرفاق result، ما يُبقي قائمة المصدر الأصلية متاحة لإجراء التصحيحات أو لاستخدام قناة متابعة مختلفة.

يظل أي صف بلا نتيجة، أو يحتوي على خطأ، أو يفتقر إلى رقم هاتف، ضمن قائمة prospects من دون تغيير، لكنه لا يدخل إلى call_list. وهكذا يُعد الكود قائمة محلية لمكالمات المبيعات من أجل مراجعتها.

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

يستهلك كل رقم هاتف جديد يُعثر عليه خارج الـ cache عبر Waterfall المُدار الافتراضي 500 credit. أما النتيجة المقبولة من الـ cache فتستهلك 0 credit، وكذلك البحث الذي لا يعثر على نتيجة. اقرأ قيمة creditsUsed في كل صف مُعاد لمعرفة الإنفاق الفعلي، بدلًا من ضرب عدد جميع الملفات الشخصية المُرسلة في 500.

للتخطيط، استخدم المعادلة التالية:

عدد أرقام الهواتف الجديدة التي عُثر عليها خارج الـ cache × 500 credit

تستهلك 40 نتيجة جديدة 20,000 credit، وتستهلك 100 نتيجة جديدة 50,000 credit. وفق سعر الشحن الأساسي الحالي بالدولار الأمريكي، تشتري 20 دولارًا رصيدًا قدره 20,000 credit، أي إن قيمة 500 credit تساوي 0.50 دولار. ويُحتسب اشتراك Growth الذي يوفر الوصول إلى REST API بصورة منفصلة. راجع أسعار Datablist للاطلاع على الخطط وباقات الرصيد الحالية.

وكمثال للتخطيط، افترض أن 100 ملف شخصي أسفرت عن 20 رقمًا جديدًا، و5 أرقام مقبولة من الـ cache، و75 عملية بحث بلا نتيجة. الكمية القابلة للفوترة هي الأرقام الجديدة العشرين، لذا تستهلك العملية 10,000 credit. ويمكن لقائمة المكالمات أن تتضمن الأرقام الخمسة والعشرين التي عُثر عليها. أدخل 20 في الحاسبة، وليس جميع المدخلات المئة أو الأرقام الخمسة والعشرين التي عُثر عليها. يوضح هذا المثال طريقة حساب التكلفة، ولا يمثل معدل نجاح متوقعًا.

Loading pricing…

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

  • قيمة DATABLIST_API_KEY مفقودة: يتوقف السكربت قبل إرسال أي طلب. صدّر المتغير ثم شغّل الملف مجددًا.
  • الـ API ترفض المفتاح: تعرض raise_for_status() خطأ HTTP. انسخ Personal API Key مجددًا، وتأكد من أن الطلب يرسله داخل header باسم X-API-Key.
  • رابط الملف الشخصي غير مدعوم: استبدل الرابط الفارغ، أو غير الصحيح، أو الخاص بشركة، أو رابط Sales Navigator، برابط عام لملف شخصي يتبع المسار linkedin.com/in/....
  • الرصيد غير كافٍ: تحقق من رصيد الحساب والـ credits المتاحة قبل إرسال مزيد من المهام.
  • حالة المهمة failed أو stopped: افحص الخطأ والنتائج المتاحة والرصيد المستخدم قبل تحديد ما لا يزال يتعين تشغيله.
  • صف بلا نتيجة أو يحتوي على خطأ: احتفظ بالعميل المحتمل في المصدر لتصحيح بياناته أو التواصل معه عبر قناة متابعة أخرى. لا يكفي الصف وحده لتحديد المزوّد أو المنطقة الجغرافية أو شرط الظهور الذي أدى إلى النتيجة.

إذا فُقدت استجابة POST، فاستخدم قيمة uniqueId التي طُبعت قبل الإرسال. اعرض قائمة مهامك قبل إعادة الإرسال. استبدل your-api-key في الطلب التالي، ثم ابحث عن المهمة التي تتطابق قيمة uniqueId فيها مع القيمة المطبوعة:

shell
curl --get "https://api.datablist.com/v1/enrichments/tasks" \
--header "X-API-Key: your-api-key" \
--data-urlencode "enrichmentId=waterfall-phone-finder" \
--data-urlencode "limit=100"

إذا لم تكن المهمة في الصفحة الأولى، فاتبع قيمة nextCursor في استجابة قائمة المهام. انسخ قيمة @id للمهمة المطابقة، واستبدل بها TASK_ID أدناه، ثم تحقق من حالتها أو استرجع نتائجها الحالية:

shell
curl "https://api.datablist.com/v1/enrichments/tasks/TASK_ID" \
--header "X-API-Key: your-api-key"
curl --get "https://api.datablist.com/v1/enrichments/tasks/TASK_ID/results" \
--header "X-API-Key: your-api-key" \
--data-urlencode "limit=100"

تشرح وثائق Task API حالات المهام، وتقسيم النتائج إلى صفحات، وسلوك المقاطعة.

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

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

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