mcpField
P
prospeo-full-export

prospeo-full-export

github.com/growthenginenowoslawski营销与增长79065 次安装3 周前更新
代码仓库

关于 prospeo-full-export

借助 Claude 将 Prospeo 人员搜索结果导出为 CSV;大型美国搜索可按州拆分,以绕过单次搜索结果上限。适用于构建潜在客户名单,并要求大规模导出前进行资格筛选。

主要功能

  1. 01分页导出 Prospeo 搜索结果至 CSV
  2. 02将搜索条件映射为 API 筛选项
  3. 03按州拆分大型美国搜索
  4. 04去重跨州搜索结果
  5. 05导出前估算积分用量

适用场景

  1. 01导出筛选后的潜在客户搜索结果
  2. 02获取大型全美搜索结果集
  3. 03准备外联联系人名单
  4. 04预估搜索导出的 API 积分

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/growthenginenowoslawski/coldoutboundskills --skill prospeo-full-export

安装后在 Claude Code 中输入/prospeo-full-export确认已加载。

详细介绍

Prospeo 全量搜索导出

将 Prospeo 中的人员搜索结果全部提取到 CSV 文件。先在 Prospeo 界面中创建搜索,然后让 Claude 通过 API 获取每一条结果——即使搜索结果超过 25,000 条也没问题。

必做步骤:使用 /icp-prompt-builder 进行筛选(请勿跳过)

导出超过 500 个联系人前,先在 Prospeo 中运行 50 个联系人的样本搜索,然后调用 /icp-prompt-builder,通过 3-5 轮、每轮 10 个样本来调整筛选提示词(每轮都需要你批准)。将调整后的提示词应用于完整导出,以筛除不匹配的对象。

**为何必须执行:**后续的邮箱补全每人需要 $0.05-$0.15。导出 25K 条结果时,若其中 40% 不匹配,就会有 $500-$1,500 浪费在毫无结果的邮箱查找上。ICP 提示词构建器需要 10-15 分钟,但有 40-70% 的概率能帮你节省这笔费用。

**可安全跳过的情况:**仅当 Prospeo 筛选条件已经极为精准(例如,5 个完全匹配的职位名称 + 1 个行业 + 窄范围的员工人数),并且你以前成功使用过相同筛选条件时才可跳过。即便如此,也要在 10 个样本上运行 /icp-prompt-builder 进行合理性检查——确认一下几乎不费成本。

功能说明

  1. 你在 Prospeo 网页界面中创建搜索(按职位名称、地点、行业、公司规模等筛选)
  2. 告诉 Claude 你使用了哪些筛选条件
  3. Claude 将这些筛选条件转换为 Prospeo API 调用
  4. Claude 遍历所有结果页,并导出为 CSV
  5. 对于大型美国搜索(25K+),Claude 会自动按州拆分,以获取全部结果

设置(仅首次需要)

步骤 1:创建 Prospeo 账号
  1. 前往 prospeo.io 并注册
  2. 选择包含 Search Person API 的套餐(大多数付费套餐都包含)
  3. 每次返回结果的 API 请求消耗 1 个积分,并返回 25 个联系人
步骤 2:获取 API 密钥
  1. 登录 Prospeo
  2. 前往 Settings > API(或访问 prospeo.io/app/settings/api)
  3. 复制你的 API 密钥
步骤 3:设置 API 密钥

将其设置为环境变量,以便 Claude 使用:

# Add to your shell profile (~/.zshrc or ~/.bashrc)
export PROSPEO_API_KEY="your_api_key_here"

然后重启终端,或运行 source ~/.zshrc。

**安全提示:**切勿将 API 密钥直接粘贴到脚本文件中。请始终使用环境变量。


使用方法

步骤 1:在 Prospeo 界面中创建搜索

前往 prospeo.io/app/search,使用筛选条件创建搜索。界面支持以下筛选条件:

  • 职位名称(例如,“CEO”、“VP Sales”、“Head of Marketing”)
  • 地点(例如,“United States”、“California”、“New York”)
  • 公司行业(例如,“Information Technology”、“Healthcare”)
  • 公司员工人数(例如,11-500 名员工)
  • 公司技术(例如,“Salesforce”、“HubSpot”)
  • 收入范围
  • 联系详情(是否有已验证的邮箱、是否有电话号码)

记下界面中显示的结果总数——你需要用它来估算积分。

步骤 2:告诉 Claude 你的筛选条件

只需描述你的筛选条件即可。例如:

“我搜索了美国信息技术行业中员工人数为 11-500 人且邮箱已验证的公司里的 CEO 和 CTO。”

“我正在寻找加利福尼亚州员工人数为 50-200 人的 SaaS 公司中的 VP of Sales 和 Head of Sales。”

“我需要美国所有使用 HubSpot、员工人数为 20-1000 人的公司的 Marketing Director。”

步骤 3:Claude 执行导出

Claude 将:

  1. 确认筛选条件和预计积分成本
  2. 创建 TypeScript 脚本
  3. 运行脚本,遍历所有结果
  4. 将全部结果导出到当前目录中的 CSV 文件

筛选条件参考

以下是 Prospeo API 接受的确切筛选条件名称。当你描述搜索需求时,Claude 会将你的描述映射到这些筛选条件:

UI 筛选条件API 筛选键格式
职位名称person_job_title{ include: ["CEO", "CTO"], exclude: ["Intern"] }
地点person_location_search{ include: ["California, United States #US"] }
行业company_industry{ include: ["Information Technology"] }
员工人数company_headcount_custom{ min: 11, max: 500 }
技术company_technology{ include: ["Salesforce", "HubSpot"] }
收入company_revenue_custom{ min: 1000000, max: 50000000 }
成立年份company_founding_year{ min: 2010, max: 2025 }
公司名称company_name{ include: ["Acme"], exclude: ["Test"] }
公司域名company_domain{ include: ["acme.com"] }
有电子邮件person_contact_details{ email: ["VERIFIED"] }
有电话号码person_contact_details{ mobile: ["TRUE"] }
精确匹配职位名称person_job_title{ include: [...], match_only_exact_job_titles: true }
地点格式

地点必须遵循以下确切格式:

  • 国家/地区:"United States #US"、"United Kingdom #GB"、"Canada #CA"
  • 州:"California, United States #US"、"Texas, United States #US"
  • 城市:"San Francisco, California, United States #US"
员工人数范围(常用预设值)
标签minmax
1-10110
11-501150
51-20051200
201-500201500
501-10005011000
1001-500010015000
5001-10000500110000
10001+10001(省略 max)

API 详情

端点: POST https://api.prospeo.io/search-person

身份验证: 在 X-KEY 请求头中提供你的 API 密钥

速率限制: 每秒 2 个请求(脚本会自动处理)

分页: 每页 25 条结果,最多 1000 页 = 每次搜索 25,000 条结果

点数: 每个返回至少 1 条结果的请求消耗 1 个点数。一次包含 25,000 条结果的搜索约消耗 1,000 个点数。

请求格式
const response = await fetch('https://api.prospeo.io/search-person', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-KEY': process.env.PROSPEO_API_KEY!,
  },
  body: JSON.stringify({
    page: 1,  // 1-1000
    filters: {
      person_job_title: { include: ['CEO', 'CTO'] },
      person_location_search: { include: ['United States #US'] },
      company_headcount_custom: { min: 11, max: 500 },
      company_industry: { include: ['Information Technology'] },
      person_contact_details: { email: ['VERIFIED'] },
    },
  }),
});
响应格式
{
  error: false,
  results: [
    {
      person: {
        person_id: "abc123",
        first_name: "Jane",
        last_name: "Smith",
        full_name: "Jane Smith",
        current_job_title: "CEO",
        linkedin_url: "https://linkedin.com/in/janesmith",
        email: "[email protected]",
        email_status: "VERIFIED",
        phone: "+14155551234",
        location: { city: "San Francisco", state: "California", country: "United States" }
      },
      company: {
        name: "Acme Corp",
        domain: "acme.com",
        linkedin_url: "https://linkedin.com/company/acme",
        industry: "Information Technology",
        headcount: 150,
        headcount_range: "51-200",
        technologies: ["Salesforce", "HubSpot"],
        location: { city: "San Francisco", state: "California", country: "United States" }
      }
    }
  ],
  pagination: {
    current_page: 1,
    total_page: 400,
    total_count: 10000,
    per_page: 25
  }
}

25K 上限:按州拆分

Prospeo 将单次搜索的上限设为 25,000 条结果(1000 页 x 每页 25 条)。如果针对全美的搜索结果超过 25K,脚本会自动将搜索拆分为 50 次按州进行的独立搜索。

工作方式:

  1. 运行一次搜索以检查 total_count
  2. 如果 > 20,000,则切换为按州搜索模式
  3. 将 "United States #US" 替换为各个州(例如,"California, United States #US")
  4. 分页获取每个州的结果
  5. 按 LinkedIn URL 去重

这意味着你可以通过单个搜索定义提取数十万条结果。

美国各州(按人口排序以提高效率)
California, Texas, Florida, New York, Illinois, Pennsylvania,
Ohio, Georgia, North Carolina, Michigan, New Jersey, Virginia,
Washington, Arizona, Massachusetts, Tennessee, Indiana, Missouri,
Maryland, Wisconsin, Colorado, Minnesota, South Carolina, Alabama,
Louisiana, Kentucky, Oregon, Oklahoma, Connecticut, Utah, Iowa,
Nevada, Arkansas, Mississippi, Kansas, New Mexico, Nebraska,
Idaho, West Virginia, Hawaii, New Hampshire, Maine, Montana,
Rhode Island, Delaware, South Dakota, North Dakota, Alaska,
Vermont, Wyoming

脚本模板

Claude 生成导出脚本时,会遵循以下模式:

import { writeFileSync } from 'fs';

// --- Config ---
const API_KEY = process.env.PROSPEO_API_KEY;
if (!API_KEY) {
  console.error('Set PROSPEO_API_KEY environment variable first.');
  process.exit(1);
}

const RATE_LIMIT_MS = 500; // 2 requests/sec
const MAX_RETRIES = 5;

// --- Types ---
interface ProspeoFilters {
  person_job_title?: { include?: string[]; exclude?: string[]; match_only_exact_job_titles?: boolean };
  person_location_search?: { include?: string[]; exclude?: string[] };
  company_headcount_custom?: { min?: number; max?: number };
  company_industry?: { include?: string[]; exclude?: string[] };
  company_technology?: { include?: string[]; exclude?: string[] };
  company_revenue_custom?: { min?: number; max?: number };
  company_founding_year?: { min?: number; max?: number };
  company_name?: { include?: string[]; exclude?: string[] };
  company_domain?: { include?: string[]; exclude?: string[] };
  person_contact_details?: { email?: string[]; mobile?: string[]; operator?: string };
}

// --- Rate-limited fetch ---
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

async function searchPage(filters: ProspeoFilters, page: number, retries = 0): Promise<any> {
  await sleep(RATE_LIMIT_MS);

  const res = await fetch('https://api.prospeo.io/search-person', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-KEY': API_KEY! },
    body: JSON.stringify({ page, filters }),
  });

  if (res.status === 429 && retries < MAX_RETRIES) {
    const backoff = Math.min(2000 * Math.pow(2, retries), 60000);
    console.log(`  Rate limited, waiting ${backoff / 1000}s...`);
    await sleep(backoff);
    return searchPage(filters, page, retries + 1);
  }

  if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`);
  return res.json();
}

// --- Deduplication ---
const seenLinkedIn = new Set<string>();
const seenEmail = new Set<string>();

function isDuplicate(person: any): boolean {
  const li = person.linkedin_url;
  const em = person.email;
  if (li && seenLinkedIn.has(li)) return true;
  if (em && seenEmail.has(em)) return true;
  if (li) seenLinkedIn.add(li);
  if (em) seenEmail.add(em);
  return false;
}

// --- CSV helpers ---
function escapeCSV(val: any): string {
  if (val == null) return '';
  const s = String(val);
  return s.includes(',') || s.includes('"') || s.includes('\n')
    ? `"${s.replace(/"/g, '""')}"` : s;
}

function resultToRow(r: any): string[] {
  const p = r.person || {};
  const c = r.company || {};
  return [
    p.first_name, p.last_name, p.full_name, p.current_job_title,
    p.email, p.email_status, p.phone, p.linkedin_url,
    p.location?.city, p.location?.state, p.location?.country,
    c.name, c.domain, c.linkedin_url, c.industry,
    c.headcount, c.headcount_range,
    (c.technologies || []).join('; '),
    c.location?.city, c.location?.state, c.location?.country,
  ];
}

const CSV_HEADERS = [
  'first_name', 'last_name', 'full_name', 'job_title',
  'email', 'email_status', 'phone', 'linkedin_url',
  'person_city', 'person_state', 'person_country',
  'company_name', 'company_domain', 'company_linkedin', 'company_industry',
  'company_headcount', 'company_headcount_range',
  'company_technologies',
  'company_city', 'company_state', 'company_country',
];

// --- Main export ---
async function exportSearch(filters: ProspeoFilters, outputFile: string, maxResults?: number) {
  console.log('Running initial search to check result count...');
  const first = await searchPage(filters, 1);

  if (first.error) {
    console.error('API error:', first.message);
    process.exit(1);
  }

  const totalCount = first.pagination?.total_count || 0;
  const totalPages = first.pagination?.total_page || 0;
  console.log(`Found ${totalCount.toLocaleString()} total results (${totalPages} pages)`);

  // Check if we need state-by-state splitting
  const needsSplit = totalCount > 20000
    && filters.person_location_search?.include?.some(l => l === 'United States #US');

  if (needsSplit) {
    console.log('Search exceeds 20K — switching to state-by-state mode...');
    await exportByState(filters, outputFile, maxResults);
    return;
  }

  // Simple pagination
  const rows: string[][] = [];
  const pagesToFetch = maxResults ? Math.min(Math.ceil(maxResults / 25), totalPages) : totalPages;
  const creditsEstimate = pagesToFetch;
  console.log(`Will fetch ${pagesToFetch} pages (~${creditsEstimate} credits)`);

  // Process page 1 results we already have
  for (const r of first.results || []) {
    if (!isDuplicate(r.person)) rows.push(resultToRow(r));
  }
  console.log(`  Page 1/${pagesToFetch} — ${rows.length} contacts`);

  for (let page = 2; page <= pagesToFetch; page++) {
    if (maxResults && rows.length >= maxResults) break;
    const data = await searchPage(filters, page);
    for (const r of data.results || []) {
      if (!isDuplicate(r.person)) rows.push(resultToRow(r));
    }
    if (page % 50 === 0 || page === pagesToFetch) {
      console.log(`  Page ${page}/${pagesToFetch} — ${rows.length} contacts so far`);
    }
  }

  writeCSV(outputFile, rows);
}

// --- State-by-state export ---
const US_STATES = [
  'California', 'Texas', 'Florida', 'New York', 'Illinois', 'Pennsylvania',
  'Ohio', 'Georgia', 'North Carolina', 'Michigan', 'New Jersey', 'Virginia',
  'Washington', 'Arizona', 'Massachusetts', 'Tennessee', 'Indiana', 'Missouri',
  'Maryland', 'Wisconsin', 'Colorado', 'Minnesota', 'South Carolina', 'Alabama',
  'Louisiana', 'Kentucky', 'Oregon', 'Oklahoma', 'Connecticut', 'Utah', 'Iowa',
  'Nevada', 'Arkansas', 'Mississippi', 'Kansas', 'New Mexico', 'Nebraska',
  'Idaho', 'West Virginia', 'Hawaii', 'New Hampshire', 'Maine', 'Montana',
  'Rhode Island', 'Delaware', 'South Dakota', 'North Dakota', 'Alaska',
  'Vermont', 'Wyoming',
];

async function exportByState(filters: ProspeoFilters, outputFile: string, maxResults?: number) {
  const rows: string[][] = [];

  for (let i = 0; i < US_STATES.length; i++) {
    if (maxResults && rows.length >= maxResults) break;

    const state = US_STATES[i];
    const stateFilters = JSON.parse(JSON.stringify(filters));
    stateFilters.person_location_search.include =
      stateFilters.person_location_search.include.map((loc: string) =>
        loc === 'United States #US' ? `${state}, United States #US` : loc
      );

    const first = await searchPage(stateFilters, 1);
    const stateTotal = first.pagination?.total_count || 0;
    const statePages = first.pagination?.total_page || 0;

    if (stateTotal === 0) {
      console.log(`  [${i + 1}/50] ${state}: 0 results, skipping`);
      continue;
    }

    // Process page 1
    for (const r of first.results || []) {
      if (!isDuplicate(r.person)) rows.push(resultToRow(r));
    }

    // Paginate remaining
    for (let page = 2; page <= statePages; page++) {
      if (maxResults && rows.length >= maxResults) break;
      const data = await searchPage(stateFilters, page);
      for (const r of data.results || []) {
        if (!isDuplicate(r.person)) rows.push(resultToRow(r));
      }
    }

    console.log(`  [${i + 1}/50] ${state}: ${stateTotal.toLocaleString()} results — ${rows.length.toLocaleString()} total contacts`);
  }

  writeCSV(outputFile, rows);
}

// --- Write CSV ---
function writeCSV(outputFile: string, rows: string[][]) {
  const lines = [CSV_HEADERS.join(',')];
  for (const row of rows) {
    lines.push(row.map(escapeCSV).join(','));
  }
  writeFileSync(outputFile, lines.join('\n'), 'utf-8');
  console.log(`\nExport complete!`);
  console.log(`  File: ${outputFile}`);
  console.log(`  Contacts: ${rows.length.toLocaleString()}`);
  console.log(`  Credits used: ~${seenLinkedIn.size + seenEmail.size > 0 ? 'see above' : rows.length / 25}`);
}

// --- Entry point ---
// Claude will fill in the filters based on your search description
const filters: ProspeoFilters = {
  // FILTERS_GO_HERE
};

const outputFile = 'prospeo-export.csv';
exportSearch(filters, outputFile);

点数成本估算

运行前,Claude 会估算点数成本:

总结果数页数点数估算成本(因套餐而异)
1,0004040~$2
5,000200200~$10
25,0001,0001,000~$50
100,000(按州拆分)~4,000~4,000~$200

Claude 会始终告知你估算成本,并在运行完整导出前征求确认。


示例对话

简单搜索:

“导出加利福尼亚州 SaaS 行业中,员工人数为 11–50 人且邮箱已验证的所有公司 CEO。”

全美范围的大规模搜索:

“我需要美国所有员工人数为 50–500 人的公司的销售副总裁和销售负责人。Prospeo 界面显示有 87,000 条结果。”

排除条件:

“美国的市场总监,排除人力派遣和招聘行业,员工人数为 20–200 人,邮箱必须已验证。”

使用技术筛选条件:

“美国所有使用 Shopify 的公司的 CTO,不限公司规模。”


故障排除

“Set PROSPEO_API_KEY environment variable first”

尚未设置 API 密钥。请在终端中运行:export PROSPEO_API_KEY="your_key"。

“API error: 401”

API 密钥无效。请前往 prospeo.io/app/settings/api 检查。

“API error: 402”

积分已用完。请为 Prospeo 账户充值。

“API error: 429”

请求频率受限。脚本会自动通过指数退避处理此问题。如果问题持续存在,说明并发请求过多——每次只运行一个导出任务。

结果似乎偏少
  • 检查位置格式是否正确(必须包含 #US、#GB 等)
  • 放宽职位筛选条件——Prospeo 默认会进行模糊匹配
  • 移除 person_contact_details 筛选条件,查看所有结果(而不只是邮箱已验证的结果)
不同州之间存在重复项

脚本会自动按 LinkedIn URL 去重。如果联系人搬迁过,他们可能会出现在多个州的搜索结果中——去重逻辑会处理这种情况。


要做什么

对 50 个联系人的样本运行 /icp-prompt-builder(上述必需步骤)。将调整后的提示词应用到完整导出列表,然后运行 /list-quality-scorecard 进行评分。

接下来:/campaign-copywriting → /smartlead-campaign-upload-public。

**或者等待:**如果 Prospeo 根据你的筛选条件返回的联系人少于 500 人,你的 ICP 可能过于狭窄。扩大职位范围(添加同义职位)或行业范围,然后再扩大规模。

相关技能

  • /icp-prompt-builder — 扩大规模前必需的资格筛选步骤
  • /list-quality-scorecard — 为筛选后的列表评分
  • /campaign-copywriting — 撰写邮件
  • /smartlead-campaign-upload-public — 以 DRAFT 状态启动

评论

支持 Markdown