Python에서 공개 linkedin.com/in/... 프로필 URL을 Datablist의 Waterfall Phone Finder API로 전송하면 LinkedIn 전화번호를 찾을 수 있습니다. API는 사용 가능한 전화번호 데이터 provider를 순서대로 조회한 뒤, 검색된 번호를 국가 코드가 포함된 국제 형식으로 반환합니다.
이 가이드에서는 Requests 라이브러리로 완전한 Python 연동을 한 번 구현합니다. 그런 다음 같은 결과를 재사용해 각 잠재 고객의 이름, LinkedIn URL, account owner가 포함된 로컬 영업 전화 목록을 만듭니다.
📌 시간이 없다면 이것만 확인하세요
하나의 API로 공개 LinkedIn 프로필 URL 목록에서 전화번호를 찾고, 각 잠재 고객의 이름과 account owner가 포함된 로컬 영업 전화 목록을 만들 수 있습니다. Waterfall Phone Finder는 사용 가능한 provider를 순서대로 조회하며, 검색된 번호는 팀이 검토할 수 있도록 정리됩니다.
- Input: 공개 LinkedIn 프로필 URL이 포함된 잠재 고객 레코드
- Output: 검색된 국제 전화번호, 번호의 국가 코드, 이름, account owner가 포함된 로컬 전화 목록
💡 No-code 방식이 더 편하신가요? Datablist의 Waterfall Phone Finder를 사용하면 코드를 작성하지 않고도 LinkedIn 프로필 URL에서 전화번호를 찾을 수 있습니다.
프로필 URL이 아닌 이름만 가지고 있다면 먼저 no-code 이름 기반 전화번호 검색 워크플로를 따르거나 Python에서 이름으로 LinkedIn 프로필 찾기를 진행하세요.
Python, Requests, Datablist API 키 설정
이 Python 예제에서는 requests 패키지를 사용합니다. 프로젝트 폴더를 만들고 가상 환경을 활성화한 다음, Requests를 설치하고 스크립트 파일을 생성하세요.
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"Datablist 계정에서 Personal API Key를 생성하고 DATABLIST_API_KEY에 지정하세요. REST API는 Growth 플랜에서 사용할 수 있습니다. 조회 성공 시 차감되는 크레딧은 이 가이드의 뒷부분에서 설명합니다.
하나의 Python 함수로 전화번호 찾기
예제에서는 수정 가능한 잠재 고객 레코드 3개를 사용합니다. 각 레코드에는 고정 prospect_id, 이름, 공개 LinkedIn URL, 결과 검토를 담당하는 account owner가 있습니다.
- Florian Poullin:
https://www.linkedin.com/in/florianpoullin/ - Satya Nadella:
https://www.linkedin.com/in/satyanadella/ - Reid Hoffman:
https://www.linkedin.com/in/reidhoffman/
API에는 고정 ID와 LinkedIn URL만 전송하면 됩니다. 이름과 account owner는 로컬 원본 레코드에 유지합니다. 이렇게 분리하면 조회 요청을 간결하게 유지하면서도 결과 반환 후 팀에 필요한 비즈니스 정보를 보존할 수 있습니다.
아래 전체 스크립트를 find_phone_numbers.py에 붙여 넣으세요.
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))다음 명령으로 파일을 실행하세요.
python find_phone_numbers.pyDatablist가 관리하는 동기식 Requests 예제를 다운로드할 수도 있습니다. 위 스크립트도 하나의 task를 제출하고 완료될 때까지 기다린 뒤 모든 결과 페이지를 가져오는 동일한 흐름을 따릅니다.
전화번호 조회 요청 제출
List comprehension은 각 원본 잠재 고객을 API input 형식으로 변환합니다. prospect_id는 inputId가 되고, linkedin_url은 data.profileUrl이 됩니다. linkedin.com/in/... 경로를 사용하는 공개 개인 프로필 URL을 입력하세요. 이 enrichment는 Sales Navigator URL을 지원하지 않습니다.
함수는 uniqueId를 생성하고 POST 요청 전에 출력합니다. 요청에는 이 값과 waterfall-phone-finder enrichment ID, 캐시 설정, inputs가 함께 전송됩니다. 요청이 정상적으로 접수되면 HTTP 202 Accepted와 task @id가 반환되며, 처리는 백그라운드에서 계속됩니다.
Waterfall Phone Finder API 레퍼런스에서 현재 설정, input 필드, output, 크레딧, 제한 사항을 확인할 수 있습니다.
잠재 고객 한 명을 조회하는 request body는 다음과 같습니다.
{
"enrichmentId": "waterfall-phone-finder",
"uniqueId": "generated-uuid",
"settings": {
"cacheAge": "30"
},
"inputs": [
{
"inputId": "prospect-florian-poullin",
"data": {
"profileUrl": "https://www.linkedin.com/in/florianpoullin/"
}
}
]
}공개 프로필 URL은 조회 input입니다. inputId는 결과를 원본 레코드의 이름 및 account owner와 연결하는 join key입니다. 자체 시스템에서 변하지 않고 해당 task 안에서 중복되지 않는 ID를 선택하세요.
백그라운드 task 완료 대기
Requests client는 3초마다 task를 확인하는 동안 로컬에서 대기합니다. Datablist task 자체는 백그라운드에서 계속 실행됩니다. 상태가 completed로 바뀌면 함수가 결과를 가져옵니다. 상태가 failed 또는 stopped이면 점검할 수 있도록 task ID와 함께 오류가 발생합니다.
모든 결과 페이지 가져오기
결과 요청은 한 번에 최대 100개 행을 가져옵니다. nextCursor가 null이 될 때까지 다음 페이지를 계속 요청합니다. 모든 결과에는 원본 inputId가 포함되므로 응답 순서에 의존하지 않고 올바른 잠재 고객과 매핑할 수 있습니다.
Managed waterfall의 전화번호 검색 방식
예제에서는 cacheAge를 "30"으로 설정합니다. Datablist는 먼저 생성된 지 30일 이내인 사용 가능한 결과가 있는지 확인합니다. 해당 결과가 있으면 API가 이를 재사용할 수 있습니다. 이 설정은 캐시 결과의 사용을 허용하지만, 캐시 hit를 보장하지는 않습니다.
프로필이 여전히 확인되지 않으면 managed waterfall이 사용 가능한 전화번호 데이터 provider를 순서대로 조회합니다. 유효한 번호를 반환하지 못한 provider가 있으면 해당 레코드는 다음 단계로 넘어갑니다. 유효한 번호를 찾으면 그 레코드의 조회가 종료됩니다.
국가와 시장별 Waterfall의 장점
Datablist는 하나의 전화번호 데이터 provider에만 의존하지 않습니다. Provider마다 사용하는 데이터셋이 다르고, 국가와 시장에 따라 데이터 커버리지도 달라집니다. 한 provider에 전화번호가 없어도 다른 provider가 찾을 수 있습니다. Datablist는 동일한 API 연동을 통해 이러한 provider를 관리합니다.
여러 시장을 대상으로 전화 목록을 만드는 영업팀이라면, 첫 번째 provider가 결과를 반환하지 못했다는 이유만으로 잠재 고객을 제외하는 일을 줄일 수 있습니다. Python 코드는 하나의 task를 제출하고 통일된 결과 형식을 읽으므로 provider별 연동을 따로 구축하고 유지할 필요가 없습니다.
워크플로에서 반드시 최신 조회가 필요하다면 cacheAge를 "0"으로 설정하세요. 이 예제에서는 사용 가능한 캐시 결과를 재사용할 수 있도록 "30"을 유지합니다.
전체 결과 확인하기
다음은 Florian Poullin에 대한 결과 하나입니다. 아래 전화번호는 마스킹했습니다.
{
"inputId": "prospect-florian-poullin",
"status": "success",
"outputs": {
"finderProvider": "wizleads",
"phoneNumber": "+33 • •• •• •• ••",
"phoneNumberCountry": "FR"
},
"creditsUsed": "500",
"error": null
}전화번호를 찾은 경우 phoneNumber에 국제 형식의 번호가 들어갑니다. +33 접두사와 phoneNumberCountry: "FR"은 프랑스 전화번호임을 나타냅니다.
finderProvider는 이 결과를 반환한 finder입니다. 이 행에는 500크레딧이 사용됐으며, error: null은 API가 행 단위 오류를 반환하지 않았다는 뜻입니다.
검토용 영업 전화 목록 만들기
phone_results를 확보하면 영업팀이 검토할 로컬 목록을 만들 수 있습니다. 같은 파일에서 결과를 출력하는 코드 뒤에 아래 블록을 추가하세요. 기존 결과를 재사용하므로 두 번째 API 호출은 발생하지 않습니다.
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))결과 index는 inputId를 join key로 사용하므로 목록의 위치에 의존하지 않고 각 결과를 원본 레코드와 연결할 수 있습니다. API payload에는 각 잠재 고객의 ID와 프로필 URL이 포함되며, 원본 prospects 목록에는 전화 목록에 필요한 이름과 account owner가 유지됩니다.
Florian의 행에서는 prospect-florian-poullin이 반환 결과를 원본 name, linkedin_url, account_owner와 연결합니다. 팀은 result.outputs에서 전화번호와 FR 국가 코드를 확인합니다. 상태, 크레딧, 오류를 포함한 전체 API 객체도 result 아래에 그대로 유지됩니다.
전화 목록 loop는 prospects의 순서를 따릅니다. 각 잠재 고객을 result에 연결하기 전에 복사하므로 원본 목록은 정보 수정이나 다른 후속 채널에 계속 활용할 수 있습니다.
결과가 없거나 오류가 발생했거나 전화번호가 비어 있는 행은 변경되지 않은 prospects 목록에는 남지만 call_list에는 들어가지 않습니다. 이 코드는 검토용 로컬 영업 전화 목록을 만듭니다.
Waterfall Phone Finder API 비용 계산
기본 managed waterfall로 캐시에 없는 새 전화번호를 찾을 때마다 500크레딧이 사용됩니다. 사용 가능한 캐시 결과가 적용되면 0크레딧, 검색 결과가 없어도 0크레딧입니다. 제출한 모든 프로필 수에 500을 곱하지 말고, 반환된 각 행의 creditsUsed를 확인해 실제 사용량을 계산하세요.
예상 비용을 계산할 때는 다음 공식을 사용합니다.
캐시에 없는 새 전화번호 검색 건수 × 500크레딧
새 번호 40개에는 20,000크레딧, 100개에는 50,000크레딧이 사용됩니다. 현재 기본 USD 충전 요율로는 $20에 20,000크레딧을 구매할 수 있으므로 500크레딧은 $0.50에 해당합니다. REST API를 제공하는 Growth 구독료는 별도입니다. 현재 플랜과 크레딧 패키지는 Datablist 요금제에서 확인하세요.
예를 들어 프로필 100개를 조회해 새 번호 20개, 사용 가능한 캐시 번호 5개, 결과 없음 75개가 나왔다고 가정해 보겠습니다. 과금 대상은 새 번호 20개이므로 조회에 10,000크레딧이 사용됩니다. 전화 목록에는 검색된 번호 25개를 모두 담을 수 있습니다. 계산기에는 전체 input 100개나 검색된 번호 25개가 아니라 20을 입력하세요. 이 예시는 과금 계산 방식을 보여주기 위한 것이며 예상 성공률을 의미하지 않습니다.
자주 발생하는 오류 해결과 기존 task 복구
DATABLIST_API_KEY가 없음: 요청을 보내기 전에 스크립트가 중단됩니다. 환경 변수를 export한 뒤 파일을 다시 실행하세요.- API가 키를 거부함:
raise_for_status()가 HTTP 오류를 표시합니다. Personal API Key를 다시 복사하고 요청의X-API-Keyheader에 포함됐는지 확인하세요. - 지원되지 않는 프로필 URL: 비어 있거나 형식이 잘못된 URL, 회사 URL, Sales Navigator URL을
linkedin.com/in/...경로의 공개 개인 프로필 URL로 교체하세요. - 크레딧 부족: 추가 작업을 제출하기 전에 계정 잔액과 사용 가능한 크레딧을 확인하세요.
- Task 상태가
failed또는stopped: 다시 실행할 작업을 결정하기 전에 오류, 사용 가능한 결과, 크레딧 사용량을 확인하세요. - 행에 결과가 없거나 오류가 있음: 원본 잠재 고객은 정보 수정이나 다른 후속 채널을 위해 보관하세요. 해당 행만으로는 어떤 provider, 지역 또는 공개 범위 조건이 결과에 영향을 미쳤는지 알 수 없습니다.
POST 응답을 받지 못했다면 제출 전에 출력된 uniqueId를 사용하세요. 다시 제출하기 전에 task 목록부터 확인하세요. 아래 요청에서 your-api-key를 교체한 다음, 출력된 값과 uniqueId가 일치하는 task를 찾으세요.
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"첫 페이지에 task가 없다면 task 목록 응답의 nextCursor를 따라가세요. 일치하는 task의 @id를 복사해 아래의 TASK_ID를 교체한 뒤 상태를 확인하거나 기존 결과를 가져오세요.
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 문서에서 task 상태, 결과 pagination, 중단 시 동작을 확인할 수 있습니다.
다음 단계
이제 공개 LinkedIn 프로필 URL에 재사용할 수 있는 Requests 함수와, 검색된 전화번호를 영업 검토용으로 정리하는 고정 ID 기반 merge 로직을 갖추게 됐습니다.
수정 가능한 예제 데이터를 소수의 선별된 잠재 고객으로 교체하세요. 고정 ID와 account owner를 유지하고, 준비된 목록을 팀의 전화 영업 프로세스로 넘기기 전에 검색된 번호를 하나씩 검토하세요. 먼저 소규모 batch로 시작해 ID 매핑과 반환 필드를 확인하는 것이 좋습니다. Non-blocking 동시 처리가 필요하다면 Datablist가 관리하는 비동기 HTTPX 예제를 사용하세요.


