在 Python 中查找工作邮箱,只需使用 Requests 库,将联系人姓名和公司信息,或公开的 LinkedIn 个人主页 URL,发送至 Datablist 的 Waterfall Email Finder API。API 会返回查找到的工作邮箱、邮箱地址的可送达状态以及邮件服务商。
下面的完整示例会查询 microsoft.com 的 Satya Nadella 和 greylock.com 的 Reid Hoffman。脚本会等待后台任务完成,并获取每一页结果。随后,我们将复用这些结果,在本地整理一份已验证联系人名单,供 Campaign 审核使用。
📌 快速摘要
使用一个 Python 函数将已知联系人提交至 Waterfall Email Finder API,等待任务完成并收集找到的工作邮箱。然后筛选出已验证联系人,供 Campaign 审核,同时将尚未匹配到邮箱的联系人保留在源列表中。
- 输入: 名字、姓氏及公司域名,完整姓名及公司名称,或公开的 LinkedIn 个人主页 URL。
- 输出: 工作邮箱及其
emailStatus和mxProvider,并在本地生成一份仅包含已有邮箱联系人的审核名单。
💡 更想使用 no-code 方案? 无需编写代码,直接使用 Datablist 的 Waterfall Email Finder,即可从 collection 中批量查找工作邮箱。
配置 Python、Requests 和 API Key
本 Python 示例使用 requests 包。创建一个名为 find_work_emails.py 的文件,然后安装 Requests:
python -m pip install requests在 Datablist API 账户页面创建 Personal API Key。使用 REST API 需要订阅 Growth 套餐。请在运行脚本的终端中导出该 Key:
export DATABLIST_API_KEY="your-api-key"Waterfall Email Finder 成功返回结果时也会消耗 credits。下文的费用部分会分别说明 credits 成本,以及提供 API 访问权限的订阅费用。
用一个 Python 函数查找工作邮箱
将以下完整脚本粘贴到 find_work_emails.py 中:
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))在同一个终端中运行:
python find_work_emails.py你也可以下载持续维护的 Requests 示例。理解同步阻塞版本后,还可以在该代码库中查看异步 HTTPX 版本。
该函数会依次发起三类请求。
第一步,提交任务。 每个 inputId 都是联系人稳定且唯一的 ID。嵌套的 data 对象包含查询时使用的字段。POST 请求返回 202 Accepted 和 @id,后者是后续请求所需的任务 ID。代码会在提交前打印 uniqueId,即使响应丢失,你仍可使用该值恢复任务。
第二步,轮询任务状态。 Datablist 会在后台处理任务。这个 Requests 客户端每三秒检查一次,直到任务状态变为 completed、failed 或 stopped。Requests 在等待期间会阻塞,但 API 任务本身仍以异步方式运行。
第三步,获取结果。 一个任务可能包含多页结果。循环会持续获取下一页,直到 nextCursor 为 null。请使用 inputId 将结果与源联系人匹配,不要依赖响应顺序。
根据联系人数据选择输入方式
每个任务只能使用一种输入模式。保留同一个 find_emails() 函数,只需根据源数据替换 settings 和 people 的值。
尽量选择在任务内唯一、且已属于源联系人的 inputId。CRM 记录 ID 或其他长期稳定的 Lead ID 都很合适。Datablist 会在结果中返回该值,因此无需比对姓名,也不必假设响应顺序与输入顺序一致,就能准确匹配记录。
如果你有拆分后的姓名字段和公司域名,请使用 sourceKind: "name_company"。已知域名是最明确的公司标识:
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:
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)如果你有公开的个人主页 URL,请使用 LinkedIn 输入方式:
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/... 个人主页 URL,并将其作为查找邮箱时的联系人背景信息。不支持 Sales Navigator 个人资料 URL。
如果需要自行选择 provider key 及调用顺序,请阅读自定义 waterfall 指南。本文使用的 managed waterfall 无需配置特定 provider。
Managed waterfall 如何查找邮箱
对于姓名加公司的请求,API 会直接使用你提供的域名。如果提供的是公司名称,系统会先尝试将其解析为可用的公司域名。对于 LinkedIn 请求,系统会清理公开个人主页 URL,并将可用的联系人与公司信息转换为符合 email provider 要求的查询上下文。
Managed waterfall 会将尚未匹配到邮箱的联系人发送给符合条件的 provider。如果没有返回可用邮箱,另一家符合条件的 provider 可以继续尝试查找同一联系人。一旦接受某个工作邮箱,该联系人的查询流程就会停止。默认响应不会显示 finder 的名称。
Waterfall 为何有助于国际化 Outbound
不同工作邮箱 provider 使用的数据集并不相同,因此它们在各个国家和市场的覆盖率也有差异。某个 provider 可能无法找到特定地区的联系人,而另一家 provider 却掌握了查找该邮箱所需的数据。
对于销售或 Growth 团队,waterfall 能让每个尚未匹配到邮箱的联系人获得更多查询机会,然后才会将其排除在 Outbound 名单之外。这样既能找回单一 provider 工作流可能遗漏的联系人,又能让团队始终使用一致的技术流程。
Datablist 通过一个 API 集成管理符合条件的 provider 调用顺序。你只需提交一次联系人,即可获得统一格式的结果,无需为每家 provider 分别开发和维护集成,也不必自行合并各方响应。
读取完整 API 结果
示例任务成功返回了两条结果。下面已对邮箱地址进行脱敏处理。
{
"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。
下面的 Campaign 代码会将 verified 结果放入 campaign_contacts,并将找到但尚未验证的邮箱放入 needs_verification。每条结果中的 creditsUsed 是最终计费依据。
默认的 managed 结果不包含 finderProvider。该字段仅在使用有效自定义 Key 的姓名加公司工作流中按条件返回,因此脚本不应根据 mxProvider 推断它的值。
为 Campaign 审核整理已找到的邮箱
确认直接查询可以正常运行后,将以下代码块追加到脚本末尾,放在返回 lookup_results 之后。它会基于刚刚完成的查询,在本地整理出供 Campaign 审核的联系人。
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,循环会从原始 people 列表读取 contact-satya-nadella,找到具有相同 inputId 的结果,复制源记录,并将完整 API 对象附加到 result 下。由于邮箱存在且状态为 verified,该联系人会进入 campaign_contacts。即使 Reid 的 API 结果先返回,他也会遵循相同流程。
三个条件让判断逻辑清晰易懂。第一个条件会跳过没有匹配结果的联系人;第二个条件会跳过行级错误和其他非成功状态;第三个条件要求结果中必须存在实际的 emailFound 值。附加完整的 result 对象,可以保留状态、输出、credits 消耗和错误字段,便于后续审核。
如果找到了邮箱,但其可送达状态不是 verified,它会进入 needs_verification。没有结果、出现行级错误或缺少 emailFound 的记录,都不会进入这两个整理后的列表。原始 people 列表仍会保留,可用于修正数据或后续调研。
估算 Waterfall Email Finder API 成本
默认的 managed waterfall 每找到一个工作邮箱消耗 25 credits。默认查询未找到结果时消耗 0 credits。请读取每条结果中的 creditsUsed,不要直接用提交的联系人总数进行乘法计算。
即使找到的邮箱状态为 unverified,仍会消耗 25 credits,尽管本地 Campaign 代码会将它放入 needs_verification,而不是 campaign_contacts。
本示例找到两个邮箱,共消耗 50 credits。规划用量时可参考:
- 找到 100 个工作邮箱需要 2,500 credits。
- 找到 1,000 个工作邮箱需要 25,000 credits。
- 较大的输入列表实际消耗可能低于
联系人数量 × 25,因为未找到结果的查询不会收费。
使用 REST API 需要订阅 Growth 套餐。按目前以美元计价的充值价格,20 美元可购买 20,000 credits,即每个 credit 为 0.001 美元。按此价格计算,25,000 credits 的价值为 25 美元。订阅费与 credits 费用分开计算。请查看 Datablist 价格页面,了解当前套餐和 credits 包详情。
修复常见错误并恢复任务
缺少 API Key。 脚本会在发起请求前抛出 Set DATABLIST_API_KEY before running this script.。请在当前终端导出该变量,然后重新运行文件。
API 返回身份验证错误。 raise_for_status() 会报告 HTTP 请求失败。复制当前有效的 Personal API Key,确认 X-API-Key header 使用了该 Key,修正凭据后再重新运行。
账户 credits 不足。 请检查账户余额。充值 credits,或等待套餐下一次发放 credits 后,再提交更多任务。
输入字段与所选模式不匹配。 使用 name_company 时,请提供拆分的姓名字段或 fullName;使用 linkedin 时,请提供 linkedInProfile。修正联系人信息,并使用一种统一模式重新提交。
任务状态为 failed 或 stopped。 脚本会抛出一个 RuntimeError,其中包含任务 ID、最终状态和可用的任务错误信息。先检查该任务及所有可用结果,再判断哪些联系人仍需重新查询。
POST 响应丢失。 复制提交前打印的 uniqueId。使用相同的 Key 列出任务:
curl "https://api.datablist.com/v1/enrichments/tasks" \
-H "X-API-Key: $DATABLIST_API_KEY"找到具有该 uniqueId 的任务,复制其 @id,然后通过任务状态或结果 URL 继续操作。这样可以恢复现有任务,无需自动重复提交联系人。任务 API 文档详细介绍了任务生命周期和结果 endpoint。
后续步骤
现在,你已经拥有一个支持三种输入方式的同步阻塞式 Python Requests 函数,以及一个可在本地筛选已找到且通过验证的邮箱、为 Campaign 审核做准备的过滤逻辑。
用一小批已调研的联系人替换示例中的两位联系人。选择与现有数据匹配的输入方式,运行查询,并在使用邮箱前检查 emailStatus。如需处理更大规模的名单,请阅读如何批量查找邮箱。持续维护的 GitHub 代码库提供可下载的同步阻塞式和异步 Python 示例。


