“一个独立开发者的工程化实践记录 —— 从筛选逻辑、界面架构,到日志体系与数据落盘。”
一、需求是怎么来的
小红书蒲公英是小红书官方的创作者商业合作平台,品牌方和 MCN 在上面筛选达人、看报价、看数据表现。它通常需要具备相应权限的企业资质账号才能开通。
问题在于:它的数据都在页面上,但不给你导出。
做一次投放选型,可能要对比几十个达人。页面上你能看到报价、粉丝数、互动中位数、粉丝画像,但它们是散落在详情页里的,一页只能看一个。想做横向对比表,只能手动复制粘贴。
我需要的不是"抓到什么惊天数据",我需要的是一张能排序、能拉透视表、能留档的 CSV。
于是我写了这个工具。
二、先划清边界(这段放最前面)
工具本身决定不了用途,用的人决定。所以我把边界写在最前面,而不是丢在文末:
这个工具做什么
- 面向已具备蒲公英访问权限的账号,辅助整理自己在平台内可见到的达人合作参考数据
- 采集的是达人账号层面的汇总统计字段:报价、粉丝数、互动中位数、粉丝画像占比等,这些是平台面向投放方提供的决策参考数据
- 输出 CSV,供自己在内部做投放选型、报价对比、数据留档
这个工具不做什么,也请不要拿它做
- 不绕过平台权限、不破解风控、不提供任何规避访问限制的手段
- 不采集普通用户的个人身份信息 —— 工具字段里没有、也不会有手机号、私信、评论用户列表这类内容
- 不做高频、恶意或破坏性请求
- 不把采集结果用于出售、传播他人信息或任何侵犯他人合法权益的行为
后面的技术内容,我也不会涉及任何接口地址、签名算法和风控细节。写这篇文章的目的是交流工程实现,不是提供可复制的采集方案。
三、整体设计
3.1 分层架构
┌──────────────────────────────────────────────┐
│ GUI 层 (tkinter) │
│ 登录窗口 / 主窗口 / 日志滚动区 / 线程安全弹窗 │
├──────────────────────────────────────────────┤
│ 调度层 (threading) │
│ 主线程只负责界面,采集跑在 daemon 子线程 │
├──────────────────────────────────────────────┤
│ 采集层 (requests) │
│ 按条件请求 → JSON 解析 → 字段映射 │
├──────────────────────────────────────────────┤
│ 输出层 (csv / logging) │
│ 逐条 append CSV + 按天切分日志 │
└──────────────────────────────────────────────┘
软件只有两层用户可见入口:登录窗口和主窗口。登录成功后销毁登录窗口再创建主窗口,这样两套界面状态互不干扰。
3.2 两种采集方式
主界面上有一个下拉框控制采集方式,走两个完全不同的输入路径:
| 采集方式 | 输入 | 适用场景 |
|---|---|---|
| 按关键词采集 | 笔记关键词 + 筛选条件 + 页码范围 | 从零开始找达人,按内容领域筛选 |
| 按小红书号采集 | 读取 博主列表.xlsx 中的「小红书号或昵称」列 | 手上已有达人名单,批量补充详细数据 |
按小红书号采集 这个模式,本质是"给我一份名单,我把每个人的底细补齐"。实际用起来,这个是高频场景 —— 收到一份合作意向名单,第一件事就是先跑一遍看看报价和真实数据表现。
四、界面上那几个筛选条件怎么用
软件界面上的筛选项:
筛选项说明:
| 输入项 | 说明 |
|---|---|
| 搜索关键词 | 笔记层面的关键词,留空则不按关键词过滤 |
| 笔记类型 | 不限 / 图文笔记为主 / 视频笔记为主 |
| 粉丝数量 | 上下限区间,留两个 0 表示不限制 |
| 图文报价 | 合作报价区间,留两个 0 表示不限制 |
| 搜索页范围 | 从第几页到第几页,每页 20 个博主 |
| 采集间隔 | 在 config_pub.json 里改 wait_sec,默认 2 秒 |
「粉丝数量 + 图文报价」这个组合是最有用的:筛掉粉丝量和报价不匹配的号。比如你想要 1w~10w 粉、报价 500 以内的腰部达人群,两个区间一叠加,范围立刻收窄。
五、输出的 34 个核心字段,拆开看
字段多不等于有用。我按业务含义分了四组,顺便说说哪几个是真能拿来决策的。
5.1 KOL 基础信息
| 字段 | 说明 |
|---|---|
| 小红书昵称 / 小红书号 | 昵称可能重复,小红书号才是唯一键,适合去重 |
| 地址 | 达人所在地区 |
| 机构 | 所属 MCN;无机构时标记「无机构」 |
| 账号类型 | 一级内容标签,如「兴趣爱好」「美妆护肤」 |
| 获赞与收藏 | 账号累计互动量级 |
| 账号评估 | 平台给出的账号质量分级(异常 / 普通 / 优秀) |
| 小红书链接 / 蒲公英链接 | 回查用的直达入口 |
5.2 粉丝画像
| 字段 | 说明 |
|---|---|
| 粉丝数 | |
| 女性粉丝占比 | 画像聚合值 |
| 年龄占比最多的 | 例如「25-34居多, 占比37.6%」 |
⚠️ 这里要特别说明:这些是平台对达人粉丝群体给出的聚合统计值(百分比、分布),不是任何具体用户的个人信息。工具不采集、也无法采集到个体用户数据。这个区别很重要,别混为一谈。
5.3 报价与效率指标
| 字段 | 说明 |
|---|---|
| 图文报价 / 视频报价 | 合作参考价 |
| 预估阅读单价_图文 | 报价 ÷ 预估阅读量,性价比的核心指标 |
| 预估 CPM(图文 / 视频) | 千次曝光成本预估 |
| 合作笔记数 | 该达人已产生的商业合作笔记量级 |
报价不能直接横向比。 一个报价 3000 的号和一个报价 800 的号,谁更划算?看预估阅读单价。这一列是我做初筛时第一个排序的字段。
5.4 内容表现(两个维度就要分开看)
日常维度:
- 图文3秒阅读
- 日常_阅读中位数
- 日常_互动中位数
- 日常_阅读来源发现页占比
- 日常_阅读来源搜索页占比
合作维度:
- 合作_阅读中位数
- 合作_互动中位数
- 合作_阅读来源发现页占比
- 合作_阅读来源搜索页占比
这里有两个非常实用的判断方法:
① 日常阅读中位数 vs 合作阅读中位数
如果两者接近,说明这个达人接了商单之后表现稳定,不会「一恰饭就掉量」。 如果合作数据明显低于日常,就要谨慎 —— 要么是商单内容质量下滑,要么是粉丝对广告接受度低。
这个差值,光看主页是看不出来的,必须两个数据放一起才成立。
② 搜索页占比 vs 发现页占比
- 搜索页占比高 → 流量长尾属性强。笔记发出去之后还能持续被搜到,适合做种草沉淀、做品牌关键词占位。
- 发现页占比高 → 推荐流爆量属性强。适合做短期曝光爆发、做活动造势。
同一个预算,投给两种达人的效果是不一样的。这个维度很多人筛达人的时候完全没看。
5.5 合作笔记阅读数(8 列)
合作笔记1阅读数 ~ 合作笔记8阅读数,按发布时间倒序取最近 8 篇。
中位数只能告诉你"平均水平",这 8 个数能告诉你稳定性。8 篇里如果有一两篇断崖式下跌,说明这个号流量波动大,单次投放的不确定性就高。
数据是拿来这样用的 —— 不是看一个数,是看一组数的分布。
六、几个实现上的坑和解法
6.1 tkinter 不是线程安全的
采集必须跑在子线程(否则界面直接卡死),但 tkinter 的控件和 messagebox 只能在主线程操作。子线程里直接调,Windows 上大概率报 RuntimeError: main thread is not in main loop 或者干脆假死。
解法是封装一层线程安全弹窗:
def _safe_showinfo(self, title, message):
"""线程安全弹窗:子线程通过 after 切回主线程执行。"""
try:
if threading.current_thread() is threading.main_thread():
self.txt_msglist.bell()
messagebox.showinfo(title, message)
else:
self.txt_msglist.after(
0,
lambda: (self.txt_msglist.bell(), messagebox.showinfo(title, message))
)
except Exception as e:
self.logger.error(f'[_safe_showinfo] {e}')
关键在 widget.after(0, fn) —— 它把调用排进主线程的事件队列,是 tkinter 跨线程通信的标准姿势。同理还封装了 _safe_showerror 和 _safe_showwarning。
6.2 采集任务用 daemon 线程
class MyThread(threading.Thread):
def __init__(self, func, *args):
super().__init__()
self.func = func
self.args = args
self.setDaemon(True)
self.start() # 构造即启动
def run(self):
self.func(*self.args)
setDaemon(True) 保证用户直接关掉主窗口时,采集线程不会挂着不放、导致进程退不干净。
6.3 日志:按天切分 + 保留 7 天
class Log_week():
def get_logger(self):
self.logger = logging.getLogger(__name__)
formatter = '[%(asctime)s-%(filename)s][%(funcName)s-%(lineno)d]--%(message)s'
self.logger.setLevel(logging.DEBUG)
sh = logging.StreamHandler()
log_formatter = logging.Formatter(formatter, datefmt='%Y-%m-%d %H:%M:%S')
info_file_name = time.strftime("%Y-%m-%d") + '.log'
case_dir = r'./logs/'
info_handler = TimedRotatingFileHandler(
filename=case_dir + info_file_name,
when='MIDNIGHT',
interval=1,
backupCount=7,
encoding='utf-8',
)
self.logger.addHandler(sh)
sh.setFormatter(log_formatter)
self.logger.addHandler(info_handler)
info_handler.setFormatter(log_formatter)
return self.logger
三个细节值得抄:
- 日志格式里带上
funcName和lineno。出问题直接定位到函数和行号,比到处print强太多。 when='MIDNIGHT'+backupCount=7,每天零点切分,只留最近 7 天,不会把用户磁盘撑爆。- 桌面软件一定要同时挂
StreamHandler(控制台)和文件 Handler。开发时看控制台,用户侧看文件。
6.4 数据落盘:逐条追加 + utf_8_sig
两个必须做对的细节:
① 不要等全部跑完才写文件。 每采到一条就 append 一次。中途断网、手动关掉、程序崩了,已经采到的数据都还在文件里。
② 编码一定用 utf_8_sig。
def init_csv(self, csv_header):
"""初始化csv文件"""
with open(self.result_file, 'a+', encoding='utf_8_sig', newline='') as f:
writer = csv.writer(f)
writer.writerow(csv_header)
# 每采完一条就落盘
with open(self.result_file, 'a+', encoding='utf_8_sig', newline='') as f:
writer = csv.writer(f)
writer.writerow(data_row)
utf_8_sig 是带 BOM 的 UTF-8。用普通 utf-8 写出来的 CSV,用户双击用 Excel 打开,Windows 版 Excel 会默认按 GBK 解读,满屏乱码 —— 用户会以为你的软件坏了。加个 BOM,Excel 就认了。这个坑我用一次乱码的代价学会了,之后所有 CSV 输出都固定 utf_8_sig。
6.5 请求节奏做成外部可配置
节奏不写死在代码里,放到 config_pub.json:
{
"wait_sec": 2
}
每次请求前:
time.sleep(random.uniform(self.wait_sec, self.wait_sec + 1))
用随机区间而不是固定值,一是节奏更自然,二是能减少对目标服务的瞬时压力。默认 2 秒,用户按自己的任务规模往上调。
我对这件事的态度很明确:采集工具的第一原则是不给对方服务添麻烦。 频率克制一点,自己这边稳定性反而更好,被中途掐断的概率也更低。
读取配置时也做了校验,wait_sec < 1 直接拒绝并提示重新配置,避免用户手滑填 0 把间隔关掉。
6.6 配置文件的轻度隐藏
打包成 exe 之后,配置文件如果放在根目录,用户一眼就看到、也容易误删。一个小技巧是把它塞进一个看起来像第三方依赖的深层路径:
cfg_path = './node_modules/jsdom/lib/jsdom/living/xhr/'
配合打包时把 node_modules 一起复制过去,普通用户既不会误删、也不容易发现。这不能当安全手段用,只是降低误操作概率。真要防篡改,还得靠服务端校验。
七、用起来长什么样
-
打开软件,用账号密码登录
-
用内置的《cookie 小工具》完成 cookie 配置(自动写入
cookie.txt,不用手动复制粘贴) -
选择采集方式:按关键词 / 按小红书号
-
填写筛选条件
-
点「开始执行」,界面上的日志区会实时滚动进度
-
采集完成后,在软件目录拿 CSV,在
logs目录看日志
软件主界面(目前已升级到v2.5版):
八、已知不足与后续计划
写技术文章如果不聊缺点,那就成软文了。列几个我自己清楚的问题:
| 问题 | 说明 | 计划 |
|---|---|---|
| 不做去重 | 同一达人在多个关键词下会重复写入,需要自己在 Excel 里去重 | 加一个「小红书号」维度的去重开关 |
| 单账号串行 | 所有请求在同一个 cookie 下串行,速度受限于间隔设置 | 多账号轮询不做 —— 会显著拉高账号风险,收益不划算 |
| MacOS首次运行 | 受 Gatekeeper 限制需要手动放行 | 写更清楚的图文引导 |
九、更新日志
| 版本 | 日期 | 更新内容 |
|---|---|---|
| v2.0 | — | 公开版本发布 |
| v2.1 | — | 新增按昵称搜索 |
| v2.2 | — | 新增字段:获赞与收藏 |
| v2.3 | 2026-03-24 | 新增 macOS 客户端 |
| v2.4 | — | 新增字段:预估 CPM、蒲公英详情页链接 |
| v2.5 | 2026-05-20 | 新增《cookie 小工具》自动配置、成功/告警弹窗提醒、自定义采集间隔;优化设备绑定机制 |
十、参考资料
- 项目仓库:github.com/mashukui/pgy_spider
- ·
- 演示视频:bilibili.com/video/BV1jJ4m1u7aj/
- ·
- 原创作者:马哥python说 —— 10年程序猿,专注Python开发
十一、合规声明
本文为个人项目的技术实践记录,仅用于技术交流与学习研究。文中不涉及任何接口地址、签名算法与风控细节,完整采集逻辑未公开。
工具仅供合法合规的数据分析、学习研究和自有业务场景使用。使用者应自行遵守目标平台的服务协议、隐私政策及所在地法律法规,不得用于高频恶意请求、未经授权采集或传播他人敏感信息、绕过平台权限与访问限制、以及任何侵犯他人合法权益的行为。
因使用者不当使用造成的风险与责任,由使用者自行承担。
@马哥python说 All rights reserved.