出海 KOL 筛选:用 Bright Data 批量采集 Instagram + TikTok 网红数据

这里写目录标题

    • [一、出海 KOL 筛选的痛点:现有工具不够用](#一、出海 KOL 筛选的痛点:现有工具不够用)
      • [1.1 典型翻车案例](#1.1 典型翻车案例)
      • [1.2 国内外工具对比与局限性](#1.2 国内外工具对比与局限性)
    • 二、技术架构与开发准备
      • [2.1 管道整体架构](#2.1 管道整体架构)
      • [2.2 开发环境与账号准备](#2.2 开发环境与账号准备)
    • [三、Instagram 博主数据采集实战](#三、Instagram 博主数据采集实战)
      • [3.1 Instagram Scraper API 接入说明](#3.1 Instagram Scraper API 接入说明)
      • [3.2 Python 批量采集脚本实现](#3.2 Python 批量采集脚本实现)
      • [3.3 返回字段与数据结构](#3.3 返回字段与数据结构)
    • [四、TikTok 创作者数据采集实战](#四、TikTok 创作者数据采集实战)
      • [4.1 TikTok Scraper API 接入说明](#4.1 TikTok Scraper API 接入说明)
      • [4.2 Python 批量采集脚本实现](#4.2 Python 批量采集脚本实现)
      • [4.3 扩展端点:抓 Posts 数据补全互动指标](#4.3 扩展端点:抓 Posts 数据补全互动指标)
    • [五、KOL 评分模型与排名输出](#五、KOL 评分模型与排名输出)
      • [5.1 数据合并与字段对齐](#5.1 数据合并与字段对齐)
      • [5.2 五维度评分体系设计](#5.2 五维度评分体系设计)
      • [5.3 评分模型 Python 实现](#5.3 评分模型 Python 实现)
      • [5.4 排名输出与样例解读](#5.4 排名输出与样例解读)
    • 六、成本分析与开源交付
      • [6.1 单条数据成本对比](#6.1 单条数据成本对比)
      • [6.2 GitHub Repo 与使用指南](#6.2 GitHub Repo 与使用指南)
    • 七、总结

一、出海 KOL 筛选的痛点:现有工具不够用

1.1 典型翻车案例

美妆出海品牌筛 KOL 翻车是常事。一个典型案例:某品牌预算五万美金砸在 8 个 TikTok 博主身上,视频一发每条几百万播放,团队群里发红包庆祝。一周后看转化------加购 0.3%,退货 40%,ROAS 算下来还不如直接投 Facebook 信息流。
复盘时用Bright Data拉这几个账号的真实数据,问题立刻浮出来:一个号称 50 万粉的"美妆头部",最近 90 天只发了 4 条内容,活跃度比普通个人号还低;另外三个账号的粉丝 70% 集中在印尼和巴西------品牌要打北美市场,一个都没对上。
最坑的不是博主骗了团队,是花几千块订阅的 KOL 工具根本没显示这些字段。同样的 8 个账号,Bright Data 5 分钟就跑出所有问题------互动率分布、粉丝地区、发文频率,任何一条都能在签约前叫停。
这种翻车本来可以避免。这篇文章交付一套可复用的 KOL 筛选管道:用Bright Data的 Instagram Scraper API 和 TikTok Scraper API 拉真实数据,跑评分模型,5 分钟出排名。代码全开 GitHub,照着跑就行。

1.2 国内外工具对比与局限性

国内 KOL 工具做国内很强,出海基本抓瞎。踩完坑整理一张对照表:

工具 月费 覆盖平台 数据时效 核心局限
飞瓜数据 ¥999--¥3,999 抖音/快手/B站/小红书 较新 不覆盖 Instagram/TikTok 海外版
新榜 ¥1,500--¥5,000 微信/微博/抖音 较新 海外博主数据几乎没有
卡思数据 ¥2,000+ 抖音/快手/B站 较新 仅国内平台
蝉妈妈 ¥299--¥1,999 抖音/TikTok 部分 较新 TikTok 海外字段有限无法自定义
HypeAuditor 99 99 99399 IG/TikTok/YouTube 月度更新 价格高、筛选死板、无中文
自建(Bright Data) 按用量付费 海外全平台,字段自定义 实时 需要初始配置

飞瓜、卡思、新榜做的是抖音快手生态,找 Instagram、TikTok 海外版、YouTube 博主时要么没数据要么字段少得可怜。HypeAuditor 覆盖海外但订阅贵、更新慢、筛选死板------你想找"粉丝在东南亚、互动率 >5%、近 30 天发过美妆内容"这种组合,没工具能直接给答案。这也是为什么 Bright Data 更适合作为需要自定义字段、实时数据和规模化采集的海外数据基础设施。

不用 Dify / Coze 零代码工作流去搭,因为 KOL 评分本质是数据科学问题,不是流程编排问题。你要稳定字段、可调权重、可复现排名,每改阈值都要看分布------纯 Python + Bright Data social media scraper API 最直接最可控。

二、技术架构与开发准备

2.1 管道整体架构

一句话:账号清单丢给 Bright Data → 结构化 JSON 扛回来 → 清洗 + 评分 → 排名 CSV

plain 复制代码
账号 URL 清单
    │
    ▼
Bright Data Scraper API ──► Instagram Profiles (gd_l1vikfch901nx3by4)
                        └─► TikTok Profiles    (gd_l1villgoiiidt09ci)
    │
    ▼
数据清洗(去重、字段对齐、单位归一)
    │
    ▼
KOL 评分模型(5 维度,权重可调)
    │
    ▼
排名输出 ──► kol_ranked.csv / Google Sheets

中间这段是关键------Bright Data 的数据采集基础设施帮助处理复杂网页采集中的访问限制、动态内容和数据获取流程,让团队无需自行维护代理池和采集基础设施。你不用养代理池、不用逆向 X-Gorgon、不用每次平台改版修代码。我们只关心"拿到数据后怎么用"。

整条管道拆成四段,每段一个 Python 脚本,互不依赖、单独跑也行:

脚本 干啥 输入 输出
instagram_profile_scraper.py 调 IG Scraper API 抓博主资料 inputs/urls_instagram.txt outputs/ig_profiles.csv
tiktok_creator_scraper.py 调 TikTok Scraper API 抓创作者资料 inputs/urls_tiktok.txt outputs/tt_profiles.csv
merge_profiles.py 两平台字段对齐 + 补默认值 上面两个 CSV outputs/merged_profiles.csv
export_to_sheets.py 套评分模型 + 排名导出 merged_profiles.csv outputs/kol_ranked.csv

数据流是单向的------前一段的 CSV 是下一段的输入,但脚本本身不强制顺序:只想抓 TikTok 就只跑第 2 个,前面没产出 ig_profiles.csv 也不影响 merge_profiles.py 跳过 IG 直接合并 TikTok 那一份。后面第三、四、五章会逐段展开实现,先把架构和分工记清楚。

2.2 开发环境与账号准备

开始前准备:

如果你还没有 Bright Data 账号,可以先创建账号获取 API Token。免费额度足够完成小规模 KOL 数据测试。

Bright Data 官网:【一键直达】

  1. Bright Data 账号官网注册,新账号会免费信用额度,可以体验跑一下 KOL 数据。
  1. API Token :登录后台 → User Settings → API Token → 复制。下文统一用 YOUR_BRIGHTDATA_API_KEY 占位。(每个控制面板用户都可以拥有一个分配给他的 API 密钥。第一个 API 密钥会自动为创建账户的用户生成。具有管理员权限的用户无法以明文形式查看自己的 API 密钥)
  1. Python 3.9+ :能跑 python 命令就行。
  1. 目标账号清单:至少 10--20 个 Instagram/TikTok 账号 URL(也可以先根据关键字抓取内容,在抓取目标账号主页)。

环境变量**(选择自己的API Token)**:

plain 复制代码
# macOS / Linux
export BRIGHTDATA_TOKEN=YOUR_BRIGHTDATA_API_KEY
plain 复制代码
# Windows PowerShell
$env:BRIGHTDATA_TOKEN="YOUR_BRIGHTDATA_API_KEY"

三、Instagram 博主数据采集实战

3.1 Instagram Scraper API 接入说明

Instagram Scraper API 给 IG 准备了好几个 dataset,出海筛 KOL 最常用 Profiles 端点,dataset_id 是 gd_l1vikfch901nx3by4。接口要点:

  • 同步模式:单次最多 20 个 URL,请求即返回,适合小批量快速验证
  • 异步模式/trigger 端点):单次最多 5000 条,返回 snapshot_id 后轮询,适合大批量生产
  • 请求体 :JSON 包成 {"input": [{"url": "...", "country": ""}]} 格式,country 留空走默认区域
  • 计费:按成功返回的 record 数计费,失败不计费
  • 同步转异步 :同步接口硬上限 60 秒,超时后不报错但响应体变成 {snapshot_id, message} 占位符,要拿 snapshot_id 轮询 /progress/{id} 直到 ready 再下数据。Repo 脚本已经内置自动轮询逻辑,命令行只会看到状态打印,不用手动调接口

实测提示 :不同 dataset 的 payload schema 不一样------country 字段在 TikTok dataset 上能传,在 IG dataset 上直接被拒(返回 400 + This input should not contain a country field)。Repo 里两个 scraper 各自适配了对应 schema。报错时记得打印响应体,Bright Data 的错误信息写得很清楚,但 raise_for_status() 默认会把它吞掉,所以脚本里加了 if not resp.ok: print(resp.text) 兜底。

3.2 Python 批量采集脚本实现

plain 复制代码
# instagram_profile_scraper.py
"""
Instagram Profiles Scraper
==========================
调用 Bright Data Instagram Scraper API 抓取账号资料。
同步接口单次最多 20 个 URL,超过走异步 /trigger。
"""
import os
import sys
import json
import time
from pathlib import Path
import requests
import pandas as pd
from dotenv import load_dotenv

# 自动找项目根目录的 .env 文件加载 BRIGHTDATA_TOKEN
# .env 文件需手动创建(参考 .env.example),不要把 token 写进代码
load_dotenv(Path(__file__).resolve().parent.parent / ".env")

# 项目根目录,用于解析 inputs/ 和 outputs/ 路径
PROJECT_ROOT = Path(__file__).resolve().parent.parent

API_TOKEN = os.environ.get("BRIGHTDATA_TOKEN")
if not API_TOKEN:
    sys.exit("ERROR: 请先在 kol-pipeline/.env 文件里配置 BRIGHTDATA_TOKEN(参考 .env.example)")

IG_DATASET_ID = "gd_l1vikfch901nx3by4"
API_URL = "https://api.brightdata.com/datasets/v3/scrape"


def _parse_response(resp):
    """解析响应,兼容 JSON 数组和 NDJSON 两种格式。"""
    try:
        data = resp.json()
        if not isinstance(data, list):
            data = [data]
    except ValueError:
        data = [json.loads(line) for line in resp.text.splitlines() if line.strip()]
    return data


def _wait_for_snapshot(snapshot_id, headers, timeout=300, interval=5):
    """同步请求超过 60 秒会被自动转异步,返回 snapshot_id 占位符。
    拿 snapshot_id 轮询进度,ready 后下载结果。
    """
    progress_url = f"https://api.brightdata.com/datasets/v3/progress/{snapshot_id}"
    download_url = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
    deadline = time.time() + timeout
    print(f"  请求被自动转异步,snapshot_id={snapshot_id},开始轮询进度...")
    while time.time() < deadline:
        prog = requests.get(progress_url, headers=headers, timeout=30).json()
        status = prog.get("status", "unknown")
        print(f"  → 状态: {status}")
        if status == "ready":
            dl = requests.get(download_url, headers=headers,
                              params={"format": "json"}, timeout=120)
            dl.raise_for_status()
            return _parse_response(dl)
        if status == "failed":
            raise RuntimeError(f"Snapshot 抓取失败: {prog}")
        time.sleep(interval)
    raise TimeoutError(f"等待 snapshot 超时({timeout}s)")


def scrape_instagram_profiles(urls):
    """同步批量抓取 Instagram 账号资料,超过 60 秒自动轮询 snapshot。"""
    if len(urls) > 20:
        sys.exit(f"ERROR: 同步接口单次最多 20 个 URL,当前 {len(urls)} 个,请改用异步 /trigger")
    headers = {
        "Authorization": f"Bearer {API_TOKEN}",
        "Content-Type": "application/json",
    }
    # IG dataset 的 payload schema 比 TikTok 严格:不接受 country 字段
    payload = {"input": [{"url": u} for u in urls]}
    resp = requests.post(
        API_URL,
        params={
            "dataset_id": IG_DATASET_ID,
            "notify": "false",
            "include_errors": "true",
        },
        headers=headers,
        json=payload,
        timeout=120,
    )
    if not resp.ok:
        # 默认 raise_for_status() 会吞掉响应体,主动打印方便定位 400/422 错误
        print(f"\n  Bright Data API 返回 {resp.status_code}:")
        print(f"  {resp.text[:2000]}\n")
        resp.raise_for_status()
    data = _parse_response(resp)
    # 检测异步占位响应:返回里只有 snapshot_id 和 message,没有业务字段
    if (data and isinstance(data[0], dict)
            and "snapshot_id" in data[0] and "message" in data[0]):
        data = _wait_for_snapshot(data[0]["snapshot_id"], headers)
    df = pd.DataFrame(data)
    keep = ["user_name", "full_name", "biography", "followers",
            "following", "posts_count", "is_verified", "url"]
    return df[[c for c in keep if c in df.columns]]


def load_urls(path):
    with open(path, encoding="utf-8") as f:
        return [line.strip() for line in f if line.strip() and not line.startswith("#")]


if __name__ == "__main__":
    default_urls = PROJECT_ROOT / "inputs" / "urls_instagram.txt"
    urls_file = sys.argv[1] if len(sys.argv) > 1 else default_urls
    urls = load_urls(urls_file)
    print(f"准备抓 {len(urls)} 个 Instagram 账号...")
    df = scrape_instagram_profiles(urls)
    output_path = PROJECT_ROOT / "outputs" / "ig_profiles.csv"
    os.makedirs(output_path.parent, exist_ok=True)
    df.to_csv(output_path, index=False, encoding="utf-8-sig")
    print("\n抓取成功,前 3 条预览:")
    print(df.head(3).to_string())
    print(f"\n完整数据已写入 {output_path} ({len(df)} 条)")

3.3 返回字段与数据结构

跑完拿到这种结构(字段已 Bright Data 清洗过):

plain 复制代码
{
  "user_name": "instagram",
  "full_name": "Instagram",
  "biography": "Discover what's next. ✨",
  "followers": 676000000,
  "following": 500,
  "posts_count": 7800,
  "is_verified": true,
  "url": "https://www.instagram.com/instagram"
}

字段对应到 KOL 评分维度:followers 是基础规模、posts_count 算发文频率、is_verified 加分粉丝质量、biography 用来文本分析内容方向。

四、TikTok 创作者数据采集实战

4.1 TikTok Scraper API 接入说明

TikTok Scraper API 的 dataset_id 是 gd_l1villgoiiidt09ci,调用方式跟 Instagram 完全一致------同样的 endpoint、同样的 payload 格式、同样的 Bearer 鉴权。区别在返回字段:除了基础资料,TikTok 还带 likes(累计点赞)、videos_countis_verified,方便直接算"累计互动 ÷ 视频数 = 平均互动"。

接口要点:

  • 单次同步最多 20 个 URL,超过走异步 /trigger
  • 字段比 IG 多 likesvideos_count,可估算账号累计互动密度
  • 要拿带货痕迹、平均播放量、近期发文频率,需要叠加 Posts 端点请求(见 4.3)

实测提示 :5 个 TikTok URL 抓取平均会超 60 秒,几乎每次都会触发"同步转异步"------响应不是数据,是 {snapshot_id, message} 占位符。Repo 脚本里的 _wait_for_snapshot 函数自动处理这套流程,你跑命令只会看到 5 秒一次的状态打印:

plain 复制代码
请求被自动转异步,snapshot_id=sd_xxx,开始轮询进度...
→ 状态: running
→ 状态: ready

不用管它,等 ready 就自动下数据写 CSV。snapshot 结果保留 16 天,建议拿到立刻消费,超期会自动清理。

4.2 Python 批量采集脚本实现

plain 复制代码
# tiktok_creator_scraper.py
"""
TikTok Profiles Scraper
=======================
调用 Bright Data TikTok Scraper API 抓取创作者资料。
同步接口单次最多 20 个 URL。
"""
import os
import sys
import json
import time
from pathlib import Path
import requests
import pandas as pd
from dotenv import load_dotenv

# 自动找项目根目录的 .env 文件加载 BRIGHTDATA_TOKEN
load_dotenv(Path(__file__).resolve().parent.parent / ".env")

# 项目根目录(scripts/ 的父目录),用于解析 inputs/ 和 outputs/ 路径
PROJECT_ROOT = Path(__file__).resolve().parent.parent

API_TOKEN = os.environ.get("BRIGHTDATA_TOKEN")
if not API_TOKEN:
    sys.exit("ERROR: 请先在 kol-pipeline/.env 文件里配置 BRIGHTDATA_TOKEN(参考 .env.example)")

TT_DATASET_ID = "gd_l1villgoiiidt09ci"
API_URL = "https://api.brightdata.com/datasets/v3/scrape"


def _parse_response(resp):
    """解析响应,兼容 JSON 数组和 NDJSON 两种格式。"""
    try:
        data = resp.json()
        if not isinstance(data, list):
            data = [data]
    except ValueError:
        data = [json.loads(line) for line in resp.text.splitlines() if line.strip()]
    return data


def _wait_for_snapshot(snapshot_id, headers, timeout=300, interval=5):
    """同步请求超过 60 秒会被自动转异步,返回 snapshot_id 占位符。
    拿 snapshot_id 轮询进度,ready 后下载结果。
    """
    progress_url = f"https://api.brightdata.com/datasets/v3/progress/{snapshot_id}"
    download_url = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
    deadline = time.time() + timeout
    print(f"  请求被自动转异步,snapshot_id={snapshot_id},开始轮询进度...")
    while time.time() < deadline:
        prog = requests.get(progress_url, headers=headers, timeout=30).json()
        status = prog.get("status", "unknown")
        print(f"  → 状态: {status}")
        if status == "ready":
            dl = requests.get(download_url, headers=headers,
                              params={"format": "json"}, timeout=120)
            dl.raise_for_status()
            return _parse_response(dl)
        if status == "failed":
            raise RuntimeError(f"Snapshot 抓取失败: {prog}")
        time.sleep(interval)
    raise TimeoutError(f"等待 snapshot 超时({timeout}s)")


def scrape_tiktok_profiles(urls):
    """同步批量抓取 TikTok 创作者资料,超过 60 秒自动轮询 snapshot。"""
    if len(urls) > 20:
        sys.exit(f"ERROR: 同步接口单次最多 20 个 URL,当前 {len(urls)} 个")
    headers = {
        "Authorization": f"Bearer {API_TOKEN}",
        "Content-Type": "application/json",
    }
    # TikTok dataset 接受 country 字段(IG dataset 不接受,见 3.2 节)
    payload = {"input": [{"url": u, "country": ""} for u in urls]}
    resp = requests.post(
        API_URL,
        params={
            "dataset_id": TT_DATASET_ID,
            "notify": "false",
            "include_errors": "true",
        },
        headers=headers,
        json=payload,
        timeout=120,
    )
    if not resp.ok:
        print(f"\n  Bright Data API 返回 {resp.status_code}:")
        print(f"  {resp.text[:2000]}\n")
        resp.raise_for_status()
    data = _parse_response(resp)
    # 检测异步占位响应:返回里只有 snapshot_id 和 message,没有业务字段
    if (data and isinstance(data[0], dict)
            and "snapshot_id" in data[0] and "message" in data[0]):
        data = _wait_for_snapshot(data[0]["snapshot_id"], headers)
    return pd.DataFrame(data)


def load_urls(path):
    with open(path, encoding="utf-8") as f:
        return [line.strip() for line in f if line.strip() and not line.startswith("#")]


if __name__ == "__main__":
    default_urls = PROJECT_ROOT / "inputs" / "urls_tiktok.txt"
    urls_file = sys.argv[1] if len(sys.argv) > 1 else default_urls
    urls = load_urls(urls_file)
    print(f"准备抓 {len(urls)} 个 TikTok 账号...")
    df = scrape_tiktok_profiles(urls)
    output_path = PROJECT_ROOT / "outputs" / "tt_profiles.csv"
    os.makedirs(output_path.parent, exist_ok=True)
    df.to_csv(output_path, index=False, encoding="utf-8-sig")
    keep_cols = [c for c in ["nickname", "account_id", "followers",
                             "likes", "videos_count", "is_verified"]
                 if c in df.columns]
    print("\n抓取成功,前 3 条预览:")
    print(df[keep_cols].head(3).to_string())
    print(f"\n完整数据已写入 {output_path} ({len(df)} 条)")

下一步:

已经拿到账号数据?

使用 Bright Data** 数据接口**继续扩展 Posts 数据,分析互动率、内容趋势和商业化潜力。

4.3 扩展端点:抓 Posts 数据补全互动指标

Profiles 端点只返回账号级聚合数据。要拿平均播放量、近期发文频率、带货频率这些精细化指标,需要再调一次 TikTok Posts 端点(按账号主页 URL 抓最近 N 条视频)。Posts 返回字段包括 play_count(单视频播放)、digg_count(点赞)、comment_countshare_countcommerce_info(带货痕迹)、tagged_user(合作品牌)。聚合后能算出平均播放量、平均互动率、带货频率(commerce_info 非空比例)、近 30 天发文数等指标。

另外还有 Discover by keyword 模式(dataset_id gd_lu702nij2f790tmv9h,多带 type=discover_new&discover_by=keyword 两个参数,payload 用 search_keyword 字段),输入 #makeup``#skincare 能反查潜在 KOL------这是"找人阶段"的核心能力,可以直接把关键词搜出来的创作者 URL 列表喂给 Profiles 端点做评估。完整 Posts 采集 + 关键词发现代码在 GitHub Repo,文章篇幅原因不展开。

已经拿到账号数据?

使用 Bright Data 数据接口继续扩展 Posts 数据,分析互动率、内容趋势和商业化潜力。

五、KOL 评分模型与排名输出

5.1 数据合并与字段对齐

抓完 IG 和 TikTok 数据后,两份 CSV 还不能直接喂给评分模型------两个平台的字段命名差异很大,必须先做归一化:

含义 IG 字段 TikTok 字段 评分模型期望字段
账号标识 (从 URL 提取) account_id username
显示名 full_name nickname display_name
累计点赞 --- likes avg_likes(按视频数摊算)
视频数 --- videos_count ---
帖子数 posts_count --- ---
是否认证 is_verified is_verified is_verified

merge_profiles.py 的工作就是把这两套字段对齐到统一格式,输出 merged_profiles.csv 作为评分模型的输入。脚本本身不强依赖两个 CSV 都存在------只有 IG 就只合并 IG,只有 TikTok 就只合并 TikTok,方便分阶段验证。

plain 复制代码
# merge_profiles.py
"""
Merge Profiles
==============
把 Instagram 和 TikTok 的原始数据合并、字段对齐,
补齐 KOL 评分模型需要的输入字段。
"""
import os
import sys
from pathlib import Path
import pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parent.parent


def normalize(df, platform):
    """把不同平台的字段名归一化。"""
    df = df.copy()
    df["platform"] = platform

    # username 归一化:优先用接口直接给的字段,没有就从 URL 末段提取
    # (IG 接口不返回 user_name,URL 末段是唯一来源)
    if "user_name" in df.columns and df["user_name"].notna().any():
        df["username"] = df["user_name"]
    elif "account_id" in df.columns:
        df["username"] = df["account_id"]
    elif "url" in df.columns:
        # https://www.instagram.com/nike → nike;https://www.tiktok.com/@tiktok → @tiktok
        df["username"] = (df["url"].astype(str)
                          .str.rstrip("/")
                          .str.rsplit("/", n=1).str[-1])

    df["display_name"] = df.get("full_name", df.get("nickname", ""))

    # 平均互动数据:TikTok 的 likes 是累计点赞,按视频数摊到单条
    # 真实业务场景下,建议再调 Posts 端点拿最近 30 条视频的真实均值
    if "avg_likes" not in df.columns:
        # 分母优先用 videos_count(TikTok),其次 posts_count(IG),都没有就当 1
        if "videos_count" in df.columns:
            denom = df["videos_count"].fillna(1).clip(lower=1).astype(int)
        elif "posts_count" in df.columns:
            denom = df["posts_count"].fillna(1).clip(lower=1).astype(int)
        else:
            denom = 1
        likes = df["likes"] if "likes" in df.columns else 0
        df["avg_likes"] = likes / denom
    df["avg_comments"] = df.get("avg_comments", 0)
    df["avg_shares"] = df.get("avg_shares", 0)

    df["posts_per_week"] = df.get("posts_per_week", 7)
    df["recent_posts_30d"] = df.get("recent_posts_30d", 30)
    df["geo_tier"] = df.get("geo_tier", "english")
    df["has_shop_link"] = df.get("has_shop_link", False)

    keep = ["platform", "username", "display_name", "followers", "following",
            "posts_count", "videos_count", "is_verified", "url",
            "avg_likes", "avg_comments", "avg_shares",
            "posts_per_week", "recent_posts_30d", "geo_tier", "has_shop_link"]
    return df[[c for c in keep if c in df.columns]]


def merge(ig_path=None,
          tt_path=None,
          out_path=None):
    ig_path = ig_path or PROJECT_ROOT / "outputs" / "ig_profiles.csv"
    tt_path = tt_path or PROJECT_ROOT / "outputs" / "tt_profiles.csv"
    out_path = out_path or PROJECT_ROOT / "outputs" / "merged_profiles.csv"
    frames = []
    if os.path.exists(ig_path):
        frames.append(normalize(pd.read_csv(ig_path), "instagram"))
        print(f"  ✓ 读到 Instagram 数据:{ig_path}")
    else:
        print(f"  ⚠ 跳过 Instagram(文件不存在):{ig_path}")
    if os.path.exists(tt_path):
        frames.append(normalize(pd.read_csv(tt_path), "tiktok"))
        print(f"  ✓ 读到 TikTok 数据:{tt_path}")
    else:
        print(f"  ⚠ 跳过 TikTok(文件不存在):{tt_path}")

    if not frames:
        raise SystemExit("ERROR: outputs/ 下既没有 ig_profiles.csv 也没有 tt_profiles.csv,先跑至少一个采集脚本")

    merged = pd.concat(frames, ignore_index=True)
    os.makedirs(os.path.dirname(out_path), exist_ok=True)
    merged.to_csv(out_path, index=False, encoding="utf-8-sig")
    print(f"\n合并完成:{len(merged)} 条记录 → {out_path}")
    print(merged[["platform", "username", "followers"]].to_string())
    return merged


if __name__ == "__main__":
    merge()

踩坑提示 :Bright Data IG 接口不直接返回 user_name 字段(只给 full_name),所以脚本里写了三层 fallback------优先用 user_name → 其次用 account_id → 最后从 URL 末段提取(https://www.instagram.com/nikenike)。这种"字段名漂移"在跨平台爬虫里非常常见,解析代码必须有 fallback 链,否则一上线就掉数据。

跑完 merge 会输出 merged_profiles.csv,IG + TikTok 数据合在一张表里,platform 列区分来源:

5.2 五维度评分体系设计

数据拿回来只是起点。真正决定能不能"挑对人"的是评分模型------这就是出海 kol 数据分析跟"随便看看粉丝数"最大的区别。下面这 5 个维度组合,对海外网红筛选最实用:

维度 默认权重 计算逻辑
互动率 30% (平均点赞+评论+分享) / 粉丝数
粉丝质量 20% 认证 + 合理发帖频率
内容活跃度 20% 近 30 天发文数 / 12
地区匹配 15% 目标市场 / 英语圈 / 其他
商业化潜力 15% 是否有 TikTok Shop 挂车

权重设计逻辑:互动率权重最高,因为它是判断"粉丝真假"最直接的指标------百万粉账号互动率 <1% 基本是僵尸粉或买量号。粉丝质量排第二,认证 + 合理发帖频率往往意味着 MCN 在背后运营。活跃度排第三,过滤沉睡账号------老账号粉丝几百万但半年不发内容,合作了等于白花钱。地区匹配放第四,因为可以通过"组合查询"提前过滤。商业化垫底但不是不重要------同分时优先选有挂车历史的博主。

templates/kol_scoring_weights.csv 把权重做成可调参数,建议跑过 50--100 个账号后看分布再调。

5.3 评分模型 Python 实现

plain 复制代码
# kol_scoring_model.py
"""
KOL Scoring Model
=================
基于互动率、粉丝质量、活跃度、地区匹配、商业化潜力的 5 维度评分。
权重可在 templates/kol_scoring_weights.csv 里调整。
"""
import pandas as pd

DEFAULT_WEIGHTS = {
    "engagement": 0.30,
    "quality": 0.20,
    "activity": 0.20,
    "geo": 0.15,
    "commerce": 0.15,
}


def load_weights(path="templates/kol_scoring_weights.csv"):
    """从 CSV 读取权重,文件不存在则用默认值。"""
    try:
        w_df = pd.read_csv(path)
        return {row["dimension"]: row["weight"] for _, row in w_df.iterrows()}
    except FileNotFoundError:
        return DEFAULT_WEIGHTS


def compute_engagement_rate(row):
    """互动率 = (点赞+评论+分享) / 粉丝数 × 100%"""
    if row["followers"] == 0:
        return 0.0
    inter = (row.get("avg_likes", 0)
             + row.get("avg_comments", 0)
             + row.get("avg_shares", 0))
    return round(inter / row["followers"] * 100, 2)


def score_one(row, weights=None):
    w = weights or DEFAULT_WEIGHTS

    er_score = min(row["engagement_rate"] / 5.0 * 100, 100)

    quality = 50
    if row.get("is_verified"):
        quality += 30
    if 5 <= row.get("posts_per_week", 0) <= 20:
        quality += 20

    activity = min(row.get("recent_posts_30d", 0) / 12 * 100, 100)

    geo_map = {"target": 100, "english": 60, "other": 30}
    geo = geo_map.get(row.get("geo_tier", "other"), 30)

    commerce = 100 if row.get("has_shop_link") else 50

    total = (
        er_score * w["engagement"]
        + quality * w["quality"]
        + activity * w["activity"]
        + geo * w["geo"]
        + commerce * w["commerce"]
    )
    return round(total, 1)


def rank_kols(df, weights=None):
    df = df.copy()
    df["engagement_rate"] = df.apply(compute_engagement_rate, axis=1)
    df["kol_score"] = df.apply(lambda r: score_one(r, weights), axis=1)
    return df.sort_values("kol_score", ascending=False).reset_index(drop=True)


if __name__ == "__main__":
    df = pd.read_csv("outputs/merged_profiles.csv")
    ranked = rank_kols(df)
    print(ranked[["platform", "username", "followers",
                  "engagement_rate", "kol_score"]].to_string())
plain 复制代码
# export_to_sheets.py
"""
Export Ranked KOLs
==================
读取合并后的数据,跑评分模型,导出排名 CSV。
需要先跑过 instagram_profile_scraper.py + tiktok_creator_scraper.py + merge_profiles.py。
"""
import os
import sys
from pathlib import Path
import pandas as pd
from kol_scoring_model import rank_kols, load_weights

PROJECT_ROOT = Path(__file__).resolve().parent.parent


def export_ranked(input_csv=None,
                  output_csv=None,
                  weights_path=None):
    input_csv = input_csv or PROJECT_ROOT / "outputs" / "merged_profiles.csv"
    output_csv = output_csv or PROJECT_ROOT / "outputs" / "kol_ranked.csv"
    weights_path = weights_path or PROJECT_ROOT / "templates" / "kol_scoring_weights.csv"

    if not os.path.exists(input_csv):
        sys.exit(f"ERROR: 找不到 {input_csv},请先跑 merge_profiles.py")

    df = pd.read_csv(input_csv)
    weights = load_weights(weights_path)
    print(f"使用权重:{weights}")

    ranked = rank_kols(df, weights)
    os.makedirs(os.path.dirname(output_csv), exist_ok=True)
    ranked.to_csv(output_csv, index=False, encoding="utf-8-sig")

    print(f"\n已导出 {len(ranked)} 位 KOL 排名到 {output_csv}")
    print("\nTop 10:")
    show_cols = [c for c in ["platform", "username", "followers",
                             "engagement_rate", "kol_score"]
                 if c in ranked.columns]
    print(ranked[show_cols].head(10).to_string())


if __name__ == "__main__":
    input_csv = sys.argv[1] if len(sys.argv) > 1 else None
    export_ranked(input_csv=input_csv)

想自动化你的 KOL 筛选流程?

Bright Data 数据接入你的评分模型,每次调整筛选条件都可以重新计算排名。

5.4 排名输出与样例解读

合并 IG + TikTok 数据跑一次评分,输出长这样:

plain 复制代码
platform   username        followers    engagement_rate  kol_score
0  tiktok     khaby.lame      162000000    4.82             87.3
1  instagram  natgeo          282000000    1.94             72.6
2  tiktok     <美妆博主A>      856000       6.31             88.7   ← 满分潜力股
3  tiktok     <挂车博主B>      1240000      3.15             74.2
4  instagram  <僵尸粉账号C>    2980000      0.42             38.1   ← 立刻剔除

最后两行的对比最关键:博主 A 粉丝不到 100 万但互动率 6.31%、商业潜力满分,反而是榜单第一;账号 C 名义 298 万粉,互动率 0.42% 直接拉响警报,评分只有 38,立刻从备选池剔除。这就是为什么出海团队必须自建评分------HypeAuditor 这种订阅工具给不到这种粒度。

想快速搭建自己的海外 KOL 数据采集 pipeline?

使用 Bright Data Scraper API 获取结构化 Instagram 和 TikTok 数据,无需维护复杂采集基础设施。开始免费测试你的第一个 KOL 数据集:👉 注册 Bright Data,获取免费额度并运行你的第一批社媒数据采集任务。

六、成本分析与开源交付

6.1 单条数据成本对比

按"评估 1000 个海外 KOL"算成本:

方案 单条成本 1000 条总价 自定义字段
HypeAuditor 含在月费 99 99 99399 长期订阅,字段固定 几乎不能自定义
飞瓜/蝉妈妈海外版 --- 不支持 ---
自建 + Bright Data 0.15 0.15 0.150.4 / record 150 150 150400 完全自定义

Bright Data 按成功付费------请求失败不计费。实测成功率 99% 左右,比住宅代理 DIY(成功率 35--40%)高一截。加上调试时间、平台改版后修代码这些隐性成本,自建反而最便宜。

6.2 GitHub Repo 与使用指南

GitHub仓库链接:https://github.com/xtdexw/kol-pipeline

GitHub Repo 结构:

plain 复制代码
kol-pipeline/
├─ scripts/
│  ├─ instagram_profile_scraper.py   # 抓 Instagram
│  ├─ tiktok_creator_scraper.py      # 抓 TikTok
│  ├─ merge_profiles.py              # 合并 + 字段对齐
│  ├─ kol_scoring_model.py           # 评分模型
│  └─ export_to_sheets.py            # 跑评分 + 导出
├─ inputs/
│  ├─ urls_instagram.txt             # IG 账号清单
│  └─ urls_tiktok.txt                # TikTok 账号清单
├─ templates/
│  └─ kol_scoring_weights.csv        # 评分权重模板
├─ .env.example
└─ README.md

使用三步:

  1. 克隆 Repo,.env.example 复制为 .env,填 BRIGHTDATA_TOKEN
  2. inputs/urls_instagram.txtinputs/urls_tiktok.txt 贴账号 URL(一行一个,单文件最多 20 个)。
  3. 依次跑四个脚本(每个独立,可以单独跑),最终结果在 outputs/kol_ranked.csv
plain 复制代码
python scripts/instagram_profile_scraper.py   # 抓 IG → ig_profiles.csv
python scripts/tiktok_creator_scraper.py      # 抓 TT → tt_profiles.csv
python scripts/merge_profiles.py              # 合并  → merged_profiles.csv
python scripts/export_to_sheets.py            # 评分  → kol_ranked.csv

没接触过 Bright Data 的同学,建议先去Bright Data 官网查看一下相关文档教程。

七、总结

这套管道跑通之后最大的价值不是"能抓数据了",是"不再被工具绑死"------以前调一个互动率阈值要给工具方提需求排队两周,现在改 weights 字典里一个数字立刻见效。出海窗口期就那么几个月,KOL 数据不能等工具排期。

适合这几类人用:

  • 出海品牌的营销 / BD:要批量评估海外 KOL,订阅 HypeAuditor 一年小几万太贵
  • MCN 和海外代运营:手上几十上百个达人要定期出排名
  • 独立站卖家自己做 affiliate:想快速剔掉僵尸粉和水军账号
  • 数据分析师 / 爬虫学习者:要用真实 API 跑一个生产级 pipeline 当练习项目

不太适合一次只筛 5--10 个达人的团队------平台内置搜索 + 手动 Excel 反而更快,这套代码的核心价值是规模化批量筛选。

跑出自己的 KOL 排行榜后,欢迎评论区把权重配置发出来,不同垂类的最佳权重组合大家可以对一对。

🚀 开始构建你的海外 KOL 数据 pipeline

使用 Bright Data 获取结构化 Instagram 和 TikTok 数据,快速验证你的达人筛选模型。

免费注册 Bright Data,立即开始测试。