知乎选题挖掘 (Zhihu Topic Miner)
为任意产品挖掘「适合自然植入」的知乎问题,按优先级输出可直接开写的清单。
关键词走双轨(这是本版核心):
- 硬广位(直接需求词)——提问者在找工具/方案,产品可以直接作为答案里的一个选项 → 写「工具清单类 / 对比测评类 / 操作步骤类」
- 软广位(泛人群词)——提问者关心的是自己的发展/处境(程序员、独立开发、游戏开发、创业、出海、学生、职场…),产品只能在经历、成本账、踩坑处轻提一句 → 写「案例拆解类 / 自身故事类 / 避坑复盘类」
两条轨一起挖,候选池更大、可写性更强;输出的每行都会标明建议回答类型(含硬广位/软广位)。
SKILL_DIR = 本 skill 所在目录(即包含本 SKILL.md 的目录)。不要写死绝对路径——换台机器路径就变了;按当前实际位置取值(例如先 ls 一下确认,或直接用 skill 加载时给出的 base directory)。
核心约束
- 产品名 + 官网是必填项。这是 skill 通用性的来源——所有词与判断都从产品画像来,不写死任何产品。缺一个就问,别猜。
- 可移植:所有脚本都不依赖本机绝对路径、不依赖 WorkBuddy/CodeBuddy 专有命令。Phase 0 的
scripts/env_check.sh负责把缺的依赖补齐(Python / openpyxl / bsk),跨 Windows / macOS / Linux 都能跑。换机器第一件事就是跑它。 - 静默运行:不输出命令原文、不逐条汇报中间数据。只报阶段进度("画像完成:12 个关键词;取数完成:候选池 78 条")。
- 性能优先:能塞进一个 Bash 调用的绝不拆两个;能不发的请求绝不发。目标全程 ≤ 5 次 Bash 调用 + ≤1 次官网抓取(命中缓存则 0 次)。去重名单解析合并进 Phase 5 的同一次调用,不额外增加调用数、不发网络请求。
BSK_AUTO_START=0是硬性要求:加了它才会连已有 daemon 而不是反复乱起新的。仅在 WorkBuddy / CodeBuddy 这类会回收命令子进程的宿主里,daemon 需要额外用后台任务常驻(见 Phase 0);在普通终端机器上 daemon 会自动起,无需干预。- 先快后稳:Phase 3 走 API 主通道;失败才回退到 DOM + 逐页抓取。不反复重试。
bsk wait-ms不带--session,其余 bsk 子命令都要带。- 浏览器要求:必须是 Chromium 系浏览器 + BrowserSkill 扩展在线 + 已登录知乎。不需要 Google Chrome——Windows 用户直接用系统自带 Edge 即可,别让用户白装。Safari / Firefox 不支持。无头模式不支持:本 skill 借的是用户真实浏览器的登录态,必须有可见浏览器;用户问"能不能无头/后台自动跑"时照实答,不要含糊,也不要擅自改用别的抓取方式。详见
references/extraction-guide.md§4.1、INSTALL.md。
输入参数
| 参数 | 必填 | 默认 | 说明 |
|---|---|---|---|
| 产品名称 | 是 | — | 如 Appark;用于匹配画像缓存 |
| 官网链接 | 是 | — | 如 https://appark.ai/;用于抓产品画像 |
| 浏览量区间 | 是 | — | 给下限为主,如"10 万以上"→ min_views=100000;有上限则一并给 |
| 回答数区间 | 是 | — | 给上限为主,如"200 以内"→ max_answers=200;有下限则一并给 |
| 关键词 | 否 | 空 | 给了走模式 B;不给走模式 A |
| 问题链接 / ID | 否 | 空 | 给了走模式 C |
| 词轨偏好 | 否 | 按画像 soft_ratio(缺省 0.4 = 硬广 6 : 软广 4) |
auto 两轨都挖 / hard 只要硬广位 / soft 软广位加重(0.5–0.6) |
| 最低潜力分 | 否 | 0 |
用户说"只要高质量的"时给 60 |
| 最长沉默天数 | 否 | 180 |
最近一条回答距今超过 N 天的问题直接剔除(0 = 不过滤) |
| 去重名单 | 否 | 自动探测并启用 | 默认就会自动找名单并启用过滤(缓存路径 → 当前目录 → 桌面 → 下载 → skill 目录 → ~/.workbuddy/ztm/,多个候选取最近修改的)。用户说「读去重表」等价;给了路径就用路径;找不到就不过滤,返回全部;用户说「不去重 / 全给我」才显式关闭 |
| 词汇预算 | 否 | 20 |
仅模式 A/B 用,10–30;每词默认取 1 页(约 20 条原始结果) |
必填项缺了用 AskUserQuestion 一次问全,不要猜。其余一律用默认值直接开跑。
用法示例(用户话术 → 参数映射)
| 用户话术 | 模式 | 解析出的关键参数 |
|---|---|---|
| 「用 Appark 找选题,浏览量 10 万以上,回答 200 以内」 | A | Appark / 不指定关键词 / 100000–∞ / 0–200 / 词轨 auto |
| 「帮 FamilyPro 跑一遍,浏览量 5 万到 50 万,回答 100 以内」 | A | FamilyPro / 双向区间 |
| 「用 Appark,关键词:App 数据分析、竞品分析工具、Sensor Tower 替代品」 | B | 用户 3 个词(视为硬广位),不足预算用画像词补 |
| 「FamilyPro,关键词用 ChatGPT 订阅、会员合租,回答 300 以内」 | B | — |
| 「这几个链接帮我看看能不能植入 Appark:<链接…>」 | C | 跳过搜索,直接抓这批 id |
| 「问题 649159760、39953134 筛一下」 | C | 从文本里提 /question/(\d+) |
| 「只要能直接开写的」 | A/B/C | min_score=70(即只要 P0+P1) |
| 「多找点,别嫌多」 | A/B | 词汇预算 30,pages 可给 2 |
| 「要在程序员、独立开发圈子里找选题」 | A/B | 词轨 soft(软广位加重到 5–6 成),可以顺带指定人群(如"就挖程序员和游戏开发") |
| 「重点挖独立开发和创业成本这两个方向」 | A/B | 命中 keyword_groups 的 G1 / G4,这两组每组取 2–3 个词,其余组各留 1 个保底 |
| 「别只给我 AI 工具价格那类题,要能写故事的」 | A/B | 8 个方向轮转 + 优先营收/成本/工具栈/工作流/复盘信号(soft_ratio 提到 0.6) |
| 「只要硬广位,能直接放工具的」 | A/B | 词轨 hard(软广比例 0) |
| 「别给我一年没更新的老问题」 | A/B/C | max_idle_days=180(默认值,确认传了即可;搜索阶段 maxAgeDays 保持 0,别按更新时间预筛) |
| 「只要最近一个月还有人在答的」 | A/B/C | max_idle_days=30 |
| 「老题也要,别卡时间」 | A/B/C | max_idle_days=0(关掉沉默过滤) |
| 「这批够了,放宽点再来一批」 | — | 保持产品不变,仅放宽区间后重跑 |
| 「读去重表」/「先过滤掉我答过的」 | A/B/C | 走 Phase 5.0 去重(不说也会自动跑;说了 = 显式要求);给了路径就用路径 |
| 「不去重,全给我」/「这次不用管答没答过」 | A/B/C | 不传 --dedup,返回全部符合条件的问题 |
规则:
- 产品名只报一次即可,后续轮次沿用(会话内记住)
- 「只要高质量的」「只要 P0/P1」→
min_score=70;「P2 也要」→min_score=55 - 用户没提区间 → 用
min_views=100000、max_answers=200作为默认起跑线,并在回复里说明用了默认值 - 模型/agent 不要反问区间细节超过一次;拿不准就用默认值跑,跑完在回复里标注实际用了什么
工作流程
Phase 0 — 环境自检 + 自动配置(关键)
先跑自检脚本,一步到位(幂等,可重复跑;会自动补装缺失项):
bash "$SKILL_DIR/scripts/env_check.sh" 2>&1
脚本会依次检查 Python 解释器 / openpyxl / bsk CLI / daemon 与浏览器连接,能自动搞定的直接搞定(pip 装 openpyxl、拉官方脚本装 bsk、PATH 补全),搞不定的(需要人工点浏览器、装扩展)给出确切命令。结尾打印一段摘要:
=== ZTM_ENV 摘要 ===
OS=windows
PY=python3
BSK=/c/Users/23869/.local/bin/bsk
DAEMON=up
BROWSER=1
READY=1
照摘要决策:
| 摘要 | 处理 |
|---|---|
READY=1 |
直接进 Phase 1;记住 PY 和 BSK 两个字面值,后面所有 python / bsk 调用都用它们 |
DAEMON=down |
按脚本提示起常驻 daemon(下一节),起完重跑一次自检 |
BROWSER=0 |
请用户在 Chrome/Edge 点扩展图标重连(脚本已给出两个商店链接) |
| 用户说"我没装 Chrome" | 别让他去装。Windows 直接让他用系统自带 Edge(扩展商店有 Edge 版);macOS 才需要装 Chrome 或 Edge 其一。详见 §4.1 |
| 用户问"能不能无头/后台自动跑" | 当前不行,必须可见浏览器 + 扩展在线 + 已登录。别含糊、别擅自换抓取方式 |
| bsk 装不上 / 协议版本漂移警告 | 按脚本提示执行 bsk update,然后让用户在扩展弹窗里重连 |
PY=MISSING |
先让用户装 Python 3.6+(脚本给了各系统的命令);装完重跑自检。没有 Python 就无法出 Excel,别硬跑 |
- 默认
--check-only关掉自动安装:bash "$SKILL_DIR/scripts/env_check.sh" --check-only - 手动指定解释器:
ZTM_PY=/path/to/python bash "$SKILL_DIR/scripts/env_check.sh" - 脚本只用官方安装脚本 / pip,不 sudo、不写系统目录
常驻 daemon(宿主回收子进程时的唯一正确做法)
仅当摘要里 DAEMON=down 时才需要。用后台任务持有前台 daemon,不要用 nohup / & / Start-Process:
Bash 工具调用:{ "command": "BSK_AUTO_START=0 <BSK 绝对路径> daemon start --foreground", "run_in_background": true }
- 记下返回的 task_id,保持它运行,任务结束时不要取消它(daemon 可跨轮次复用;无浏览器/会话时默认 10 分钟空闲退出,下次按需再起)
- 启动后在另一个独立的 Bash 调用里验证:
BSK_AUTO_START=0 <BSK 绝对路径> status。看到daemon version+browsers connected才算就绪 - 之后所有 bsk 命令一律带
BSK_AUTO_START=0 - 不要用
bsk daemon stop/restart去"修"连不上的状态——在回收型宿主里这会杀掉宿主任务,让情况更糟
> PATH 不会跨 Bash 调用保留:如果 bsk 是新装到 ~/.local/bin 的,每次调用要么写绝对路径,要么在命令开头加 export PATH="$HOME/.local/bin:$PATH";。最省事的做法是全程用绝对路径。
> 全文出现的 python 一律替换成 Phase 0 摘要里的 PY 值(如 python3 / py -3 / 绝对路径);全文出现的 bsk 一律用 $BSK(摘要里的绝对路径)。
公共变量(设一次,后面全部 Bash 调用复用)
SD="$SKILL_DIR"
PY=<Phase 0 摘要里的 PY 值,如 python3>
if [ -n "$LOCALAPPDATA" ]; then TMP="$LOCALAPPDATA/Temp/ztm_run"; else TMP="${TMPDIR:-/tmp}/ztm_run"; fi
BSK='<Phase 0 摘要里的 BSK,若 bsk 已在 PATH 就直接写 bsk>'
mkdir -p "$TMP"
TMP特意写成跨平台:Windows(Git Bash)走%LOCALAPPDATA%\Temp,macOS / Linux 退回$TMPDIR或/tmp;两边 python 都能直接读写- 下文示例里的
bsk一律指这个$BSK,python一律指这个$PY
Phase 1 — 产品画像(核心步骤)
按 references/product-profiling.md 执行:
- 读
references/product-presets.json,用产品名 / 域名 /aliases做不区分大小写匹配。 - 命中 → 直接用缓存画像,跳过官网抓取(Appark / FamilyPro 已在缓存里)。
- 未命中 → 用
product-profiling.md里的 prompt,对官网地址抓 1 次,拿到画像 JSON 并落盘到临时文件。 - 抓取失败 → 按该文件 §5 的兜底规则出词(产品名 × 决策型模板),并在回复里提一句。
画像里有 8 个字段下游要用:keywords(硬广词主力)、keywords_soft(软广词池)、catalog(在售商品清单 = 硬广词的唯一词源)、exclude_tokens(排除词,行级过滤用)、personas(软广人群兜底)、categories(硬广品类兜底)、desc_tokens(相关性加分)、key_selling_points(前 5 条植入思路的素材)。另有 3 个可选字段:keyword_groups(八方向词群,按组轮转)、keyword_combine(长尾组合规则)、soft_ratio(软广占比)。
Phase 1.5 — 出词(双轨)
产出两组词:硬广词 HARD 与 软广词 SOFT(详细规则见 references/keyword-library.md §1)。
- 词轨偏好
auto(默认):按画像的soft_ratio凑满词汇预算(缺省 0.4 → 20 词 = 12 硬 + 8 软;FamilyPro 预设为 0.6 → 8 硬 + 12 软,让泛人群问题占多数)hard:软广比例 0,预算全给硬广soft:软广比例 0.5–0.6(用户说"要在程序员/独立开发圈里找"时)- 硬广词来源:用户关键词(模式 B)→ 画像
keywords→catalog在售商品 × 决策模板(优先)→categories品类词(keyword-library.md§2)→ 全品类轮转 - 软广词来源:画像
keywords_soft→ 画像personas指向的人群词(keyword-library.md§3)→ 默认人群(P1 程序员 / P2 独立开发 / P4 创业 / P6 职场) - 用户点名了人群("就挖程序员和游戏开发")→ 只用这几个人群对应的软广词
- 画像有
keyword_groups时必须按组轮转(FamilyPro 是 8 组):每组先取 1 个,取满 8 组后回到第 1 组取第 2 个,依此类推,直到凑满软广配额。绝不从第一组一路往下截取——那样 8 个方向只会剩 1 个,正是"结果单一"的根因 - 用户点名方向("重点挖独立开发和创业成本")→ 被点名的组每组取 2–3 个,其余组仍各取 1 个保底
keyword_groups的组名带G1…G8前缀,按编号顺序轮转即可- 组合扩长尾(可选):画像有
keyword_combine时,用户说"多找点 / 这方向再挖深一点"才用bases × suffixes组合出长尾(如独立开发者×一个月开销),展开上限 = 词汇预算的 1/3。没开口就别展开,词越多越慢 - 优先故事与数据向:软广词要挑能搜出「有真实人物、有营收数字、有工具栈、有工作流、有成本账」的问题,而不是纯观点题。这类题在报表里同分时会自动排前面(
build_report.py的故事信号排序) - 防不相关(重要):硬广词必须锚定
catalog里的真实在售商品。出完词逐条做「能不能答」自查——产品不提供的东西(例:只做海外订阅的平台绝不能出"腾讯视频会员怎么买便宜")直接删掉,详见keyword-library.md§3.5 exclude_tokens一路带到 Phase 5,做行级兜底过滤- 全局去重(同一个词不重复投);最后自查两轨都不含纯观点词("如何看待XX"、"XX 是什么")
- 关键词里不要出现单引号(会破坏内联到页面的表达式)
Phase 2 — 浏览器会话 + 定位同源页
一次 Bash 调用完成:开会话 → 导航到知乎首页(作为同源 fetch 起点)→ 回显 SID。
SID=$($BSK session start --json 2>/dev/null | $PY -c "import sys,json;t=sys.stdin.read();i=t.find('{');print(json.loads(t[i:])['session_id'])")
BSK_AUTO_START=0 $BSK navigate "https://www.zhihu.com/" --session $SID --wait-until domcontentloaded --timeout 20s 2>&1 | tail -1
echo "SID=$SID"
记下 SID,下文 {SID} 都用它。
Phase 3 — 批量取数(API 主通道,模式 C 跳过)
原理:在知乎同源页面上下文里调用
/api/v4/search_v3?t=question&q=<词>&offset=<n>&limit=20&lc_idx=0
返回的 visits_count / answer_count / follower_count 与问题页 js-initialData 的 visitCount / answerCount / followerCount 口径完全一致(已实测),因此这一步拿到的就是精确值,不需要再逐页抓取。
一次 Bash 调用完成:设置全局变量 → 跑脚本 → 落盘 → 自检回显。
SID={SID}
BSK_AUTO_START=0 $BSK evaluate --session $SID "window.__ZTM_KWS=['硬广词1','硬广词2'];window.__ZTM_KWS_SOFT=['软广词1','软广词2'];window.__ZTM_LIMITS={minV:100000,maxV:0,minA:0,maxA:200,maxAgeDays:0,pages:1};'ok'" 2>&1 | tail -1
BSK_AUTO_START=0 $BSK evaluate --session $SID "$(cat "$SD/scripts/search_questions.js")" --timeout 240s 2>/dev/null | tail -1 > "$TMP/rows.json"
$PY -c "
import json;d=json.load(open(r'$TMP/rows.json',encoding='utf-8'))
rows=d['rows']; h=sum(1 for r in rows if r.get('track')=='hard'); s=sum(1 for r in rows if r.get('track')=='soft')
print('total',d['total'],'kept',d['kept'],'hard',h,'soft',s,'errors',d['errors'][:3])
"
要点:
__ZTM_KWS= 硬广词,__ZTM_KWS_SOFT= 软广词;两个变量都在同一次设置,脚本会给每行打track标记(hard/soft)。只挖一轨时另一个传空数组- 同一问题同时命中两轨时,以硬广位优先(硬广先跑)
__ZTM_LIMITS门槛用minV/maxV/minA/maxA(上限传0= 不限)+maxAgeDays(默认传0= 不在搜索阶段按更新时间丢弃)。原因:updated_time只在问题被编辑时才变,跟"有没有新回答"没关系——实测独立开发者收入如何,有啥感受?更新时间停在 2020 年,最近一条回答却是上周。按更新时间预筛会把这类仍然活跃的长青问题整批误杀。真正的半年过滤交给 Phase 3.5 的 answers 接口(精确)。pages默认 1(每词约 20 条原始结果),词多时保持 1- 脚本内部已做精确门槛过滤(不是页面 hint),门槛真正生效
errors非空但kept>0→ 正常,只是个别词无结果;kept=0或返回err:→ 走回退通道- 候选池不在这里截断(搜索很便宜),截断放在 Phase 3.5:按供需比取前 150 条补采,报表也只用这 150 条
- 词多(20–30 个)时超时给足,
--timeout 240s起
回退通道(仅主通道失败时):references/extraction-guide.md §回退。用 scripts/prefilter.js 收 id 列表(DOM 解析,只能拿 id)→ scripts/batch_fetch.js 批量 fetch 问题页取精确值。
Phase 3.5 — 新鲜度补采(沉默过滤 + 近期回答增量)
这一步是「半年过滤」的唯一可靠来源。 不要拿搜索接口的 updated_time 代替——实测它跟最后一条回答时间不一致(有的问题 updated 停在 2025 年,最新回答却在 2026 年)。
一次 Bash 调用:把 Phase 3/4 落盘的行 id 提出来 → 跑脚本 → 落盘。
SID={SID}
$PY - "$TMP" <<'PYEOF'
import json, os, sys
tmp = sys.argv[1]
d = json.load(open(os.path.join(tmp, "rows.json"), encoding="utf-8"))
rows = d["rows"] if isinstance(d, dict) else d
rows = [r for r in rows if r.get("id")]
# 补采按条数计费:先按供需比预排,只补最值得看的前 150 条,其余不补也不进报表
rows.sort(key=lambda r: -(int(r.get("views") or 0) / (int(r.get("answers") or 0) + 1)))
rows = rows[:150]
json.dump({"rows": rows}, open(os.path.join(tmp, "rows_sel.json"), "w", encoding="utf-8"), ensure_ascii=False)
json.dump([str(r["id"]) for r in rows], open(os.path.join(tmp, "ids.json"), "w", encoding="utf-8"), ensure_ascii=False)
print("补采候选", len(rows))
PYEOF
BSK_AUTO_START=0 $BSK evaluate --session $SID "window.__ZTM_IDS=$(cat "$TMP/ids.json");window.__ZTM_FRESH={limit:10,conc:6};'ok'" 2>&1 | tail -1
BSK_AUTO_START=0 $BSK evaluate --session $SID "$(cat "$SD/scripts/fetch_freshness.js")" --timeout 180s 2>/dev/null | tail -1 > "$TMP/fresh.json"
$PY -c "
import json;d=json.load(open(r'$TMP/fresh.json',encoding='utf-8'))
print('total',d['total'],'failed',d.get('failed'))
"
要点:
- 接口:
/api/v4/questions/{id}/answers?offset=0&limit=10&platform=desktop&sort_by=created sort_by=created才是按时间倒序(实测严格递减);sort_by=default是推荐序,时间乱序,不能用/api/v4/questions/{id}详情接口返回 http403(需签名),别用它limit默认 10:第 1 条 = 最新回答(精确),并且能统计出「近 30 天新增回答数」。想算更长的窗口就调大- 一次请求/问题、6 并发;上限 150 条(超出按供需比截断,见上面那段预排序)。150 条约 15–25s;再往上加会明显拖慢,收益也低
- 只补采前 150 条,报表也只用这 150 条(
rows_sel.json)——否则没补采到的行会退回updated兜底,把"老题新答"误杀,结果白白变少 - 返回
err:或failed占比高 → 只影响这一列(显示—),不要因此中断;在回复里提一句"新鲜度补采部分失败" - 补采失败 / 用户嫌慢整段跳过 → 报表照样能出(列显示
—),但沉默过滤只能退回updated兜底:会误杀"很久没编辑、但上周刚有人答"的长青问题(宁可少给不漏)。所以这一步别轻易省——尤其软广位,好问题大多是老题新答
Phase 4 — 仅模式 C:用户指定链接/ID
跳过 Phase 3,用 scripts/batch_fetch.js 直接抓这批问题页:
SID={SID}
BSK_AUTO_START=0 $BSK evaluate --session $SID "window.__ZTM_IDS=['id1','id2'];'ok'" 2>&1 | tail -1
BSK_AUTO_START=0 $BSK evaluate --session $SID "$(cat "$SD/scripts/batch_fetch.js")" --timeout 180s 2>/dev/null | tail -1 > "$TMP/rows.json"
- 抓完照样跑一遍 Phase 3.5(它读的就是
rows.json,不用改任何参数)——这批链接同样要过"半年内有回答"这道关;不跑就只能用updated兜底,会把老题新答误杀 - id 超过 40 条拆多次(每次 ≤40)
- 返回
err占比 > 50% → 逐页回退(scripts/page_extract.js) err:captcha/http403→ 用bsk request-help请用户在 Agent 窗口过验证码或登录知乎
Phase 5 — 去重名单 + 生成报表(同一个 Bash 调用,不额外增加调用数)
# 5.0 去重名单(默认跑;用户说「不去重」就跳过这一步):找不到名单 → found=0,脚本不写文件,报表自动跳过过滤
$PY "$SD/scripts/dedup.py" [--path "<用户给的名单路径>"] --out "$TMP/dedup.json" 2>&1 | tail -1
# 5.0.1 兜底:3.5 没跑(或失败)时用全量候选,别让报表读不到文件
[ -f "$TMP/rows_sel.json" ] || cp "$TMP/rows.json" "$TMP/rows_sel.json"
# 5.1 报表
$PY - "$TMP" <<'PYEOF'
import json, os, sys
tmp = sys.argv[1]
cfg = {
"product_name": "Appark",
"product_desc": "<desc_tokens 空格拼接>",
"soft_keywords": ["<软广词1>", "<软广词2>"], # Phase 1.5 的 SOFT 列表,用于兜底判轨
"exclude_tokens": ["<排除词1>", "<排除词2>"], # 画像 exclude_tokens:标题命中即丢弃
"min_views": 100000, "max_views": 0, "min_answers": 0, "max_answers": 200, "min_score": 0,
"max_idle_days": 180, # 最近一条回答距今 >180 天直接剔除;0 = 不过滤
"recent_days": 30, # 「近期回答动态」里"+N"的统计窗口
}
json.dump(cfg, open(os.path.join(tmp, "cfg.json"), "w", encoding="utf-8"), ensure_ascii=False)
PYEOF
$PY "$SD/scripts/build_report.py" "$TMP/知乎选题_<产品名>_<YYYYMMDD_HHMM>.xlsx" \
--input "$TMP/rows_sel.json" --config "$TMP/cfg.json" --fresh "$TMP/fresh.json" \
--dedup "$TMP/dedup.json" 2>&1 | tail -2
去重名单(Phase 5.0)怎么走
- 默认就跑(用户什么都不说也跑):不带
--path,脚本自动探测(缓存路径 → 当前目录 → 桌面 → 下载 → skill 目录 →~/.workbuddy/ztm/,多个候选取最近修改的,这样用户新更新的表会自动生效)。只认文件名里带「去重 / 已答 / 已发 / 发布 / 回答记录 / dedup / answered / published」的表 - 用户说「读去重表」 → 同上(等价触发词,跑同一条命令)
- 用户给了路径 → 加
--path "<路径>"(路径含空格要加引号) - 用户说「不去重 / 全给我」 → 5.0 跳过,5.1 也不加
--dedup - 名单格式:第一列是问题名称即可;有链接列更好(能从
/question/<id>抽精确 id)。支持.xlsx / .csv / .tsv / .txt / .md;.xls旧格式请先另存为 xlsx - 过滤是三重匹配,命中任一即丢:
id(question 号一致)→标题(归一化后完全相同,全半角/空格/标点/大小写都不算差异)→相似(difflib ≥ 0.95,兜住标题被改标点或截断;实质不同的题不会被误杀) - 找不到名单就正常跑,说明页写「去重名单:未启用」;
found=0不算错误,不要重试、不要报错、不要为它多起一次调用 - 命中明细(最多 20 条 + 命中方式)会写进「说明」sheet,便于人核对名单读得对不对
- 回复里必须报出实际用的是哪个文件(脚本 stdout 的
source字段),万一自动挑错了用户能立刻纠正
报表其它要点
- 数据与配置都走文件,避免命令行长度限制(候选多时 JSON 会超限),也避免中文词在 shell 里被转码
--fresh传 Phase 3.5 的产物;没跑 3.5 就不传这个参数(脚本会按"未补采"处理,列显示—,沉默过滤自动失效)soft_keywords传 Phase 1.5 的软广词列表;search_questions.js已给每行打track,这里只是兜底(模式 C 的行没有 keyword)exclude_tokens传画像的排除词;标题命中即丢弃,丢弃数会记在「说明」sheet--dedup传 Phase 5.0 的产物;没启用去重就不传这个参数(返回全部符合条件的问题)。传了但文件不存在 / 为空 → 脚本按"未启用"处理,不会报错build_report.py会再做一次区间过滤(双保险)并容错跳过 bsk 的版本提示行- 脚本自带 openpyxl 兜底安装(Phase 0 已装好时不会重复触发)
- 命中 0 条 → 直接说"未找到满足条件的问题,建议放宽区间或加大词汇预算",不生成空表
排序口径(分数只在脚本内部用来排序和定档,不写进表)
排序键:潜力分 ↓ → 故事/数据信号 ↓ → 相关性命中数 ↓ → 供需比 ↓ → 浏览量 ↓
优先级:P0 >= 85 | P1 >= 70 | P2 >= 55 | P3 < 55
潜力分 0–100 = 供需比 0–35 + 回答稀缺 0–12 + 浏览量体量 0–13 + 问句类型 0–20 + 商业意图词 0–15 + 新鲜度 0–5(+ 相关性 0–10)
新鲜度 0–5 = 最近一条回答距今天数:≤30 天 5 分 / ≤90 天 4 / ≤180 天 3 / ≤365 天 1 / 更久 0(没补采到时退回 updated 字段兜底)
故事/数据信号(只做同分排序,不计分)= 营收数据 / 成本账 / 工具栈 / 工作流 / 复盘经历,命中类别越多越靠前
输出列(10 列:核心数据 + 建议回答类型 + 近期回答动态)
序号 / 优先级 / 标题 / 来源关键词 / 建议回答类型 / 浏览量 / 回答数 / 近期回答动态 / 建议植入角度 / 链接
-
不输出「问句类型」列(它只参与内部分数),别再往回加
-
近期回答动态:
最近一条回答日期 · 近30天+N。一眼看出这个问题是不是还活着;无回答= 确实还没人答,—= 没补采到。沉默过滤(默认 180 天)就是按这一列的值做的 - 建议回答类型:格式
体裁(硬广位 / 软广位),取值: - 硬广位 →
工具清单类/对比测评类/操作步骤类/避坑复盘类/观点分析类/选型指南类 - 软广位 →
案例拆解类/自身故事类/操作步骤类/共情故事类/观点分析类 - 直接照这列决定写成什么体裁的回答
- 得分类字段(潜力分、供需比)与推导用的中间字段(商业意图词、相关性、关注数)不进表——表已按分数从高到低排好,看行序 + 优先级档位就够了
- 优先级底色只刷在「序号 / 优先级」两列,标题列保持干净易读
输出 3–4 个 sheet:全部选题(按优先级排序) / 优先选题 P0P1 / 软广位选题(泛人群)(有软广位词时才有)/ 说明。
Phase 6 — 关闭会话 + 呈现
BSK_AUTO_START=0 $BSK session stop {SID} 2>&1 | tail -1
即使前面报错也必须执行(bsk session stop 只关会话,不要动 daemon 的后台任务)。然后 present_files 展示 Excel,回复结构:
- 一句话:为哪个产品、按什么区间找的,命中多少条,优先级分布 P0 a / P1 b / P2 c / P3 d,词轨分布 硬广 x / 软广 y,剔除了 z 条沉默老题(以及命中排除词丢了几条)
- 用了去重名单 → 补一句「去重名单(<文件名>)剔除了 n 条已答过的问题」,n=0 也要说(那就是名单没命中,可能名单不对)
- 没启用去重 → 一句「本次未启用去重」带过,别展开 - 用的哪种模式(A 不指定关键词 / B 指定关键词 / C 指定问题 / 叠加)
- 前 5 条简表(优先级、标题、浏览量、回答数、建议回答类型、近期回答动态)
- 前 5 条逐条的植入方向与思路 —— 见下节,这是必出项
- Excel 路径(并说明"软广位选题"那张表在哪)
禁止输出命令返回原文、不逐条罗列处理过程。
> 把报表交付给用户时,outputs/ 目录或用户指定的位置都可以;Windows 下用 cp/绝对路径,跨平台尽量用 $PY -c "import shutil;shutil.copy(...)" 避免中文路径在 shell 里被转码。
前 5 条的植入方向与思路(必出项)
对排序后的前 5 条,每条用 2–3 句写清"怎么植入",必须结合产品画像,不要套模板。每条包含三件事:
- 切入角度——从这个问题里哪句话/哪个诉求切进去(按标题里的诉求和商业意图词判断;问句类型只在脚本内部打分,不进表)
- 挂什么点——用画像
key_selling_points里的具体卖点(有数字就带数字) - 怎么收尾——避免硬广的收束方式(给判断标准、给取舍建议,产品作为其中一个选项出现)
硬广位和软广位写法不一样,不要混:
| 硬广位(问句类型多为决策型/工具型) | 软广位(多为经验型/观点型/泛人群) | |
|---|---|---|
| 正文主体 | 横向盘点 / 对比 / 操作流程 | 讲经历、讲案例、讲判断,全程不提产品也能成立 |
| 产品出现位置 | 榜单里的一个选项、流程里的一个工具 | 只在转折点/成本账/踩坑处提一句,通常一次 |
| 收尾 | 给判断标准,让读者自己挑 | 给教训、给取舍建议,产品是"我当时用的那个" |
| 忌讳 | 只吹自家、贬低竞品 | 通篇功能清单、硬拗关联 |
前 5 条里如果同时有硬广位和软广位,两条都要覆盖到(不要只讲硬广位);如果这批只有一轨,就说明另一轨为什么没有。
格式示例(内容要按实际产品和问题重写,不要照抄):
1. 【P0 · 硬广位 · 工具清单类】Sensor Tower 太贵了,有没有平替的应用数据工具?
切入:提问者已经明确抱怨价格,"贵"就是最好的切入点。
挂点:直接对标他在用的 Sensor Tower,讲"免费也能看到 App Store 双端榜单变化"。
收尾:给一个自检清单(想看什么数据→免费够不够用),把 Appark 放在清单里的一个选项,不劝换。
2. 【P1 · 软广位 · 案例拆解类】业余时间当个独立游戏开发者,能赚钱嘛?
切入:别急着答"能/不能",先讲"能不能赚钱取决于你有没有看准一个品类"。
挂点:讲我当初怎么验证需求——先去查同类 App 在双端的榜单排名和下载量级,这一步用的就是 Appark(免费)。
收尾:给一个算账逻辑(时间成本 vs 市场天花板),产品只在"怎么验证需求"那一步出现一次。
其余规则:
- 用了产品预设时,
key_selling_points直接取缓存;走抓取时取 WebFetch 的结果 - 找不到合适挂点的条目,就诚实写"这条只能轻植入,结论段提一句即可",不要硬编
- 明显无法植入的问题(纯新闻时事评论、与产品无任何数据/工具关联)直接剔除,不要为了凑数留在表里;剔了就在回复里说明剔了哪条、为什么
- 全文遵守既有内容风控口径(境外→海外区、合租→多人分摊一份订阅等)
参考资料
- 安装 / 使用说明(给人看的,也可直接发给用户):
INSTALL.md - 环境自检 / 自动配置(Phase 0 必跑):
scripts/env_check.sh - 产品画像提取规范(Phase 1 必读):
references/product-profiling.md - 产品画像加速缓存:
references/product-presets.json(Appark / FamilyPro 已内置,均带keywords硬广 +keywords_soft软广 +personas;FamilyPro 另有keyword_groups八方向词群、keyword_combine长尾组合规则、soft_ratio软广占比) - 关键词库(双轨):
references/keyword-library.md—— §2 硬广品类词 A–L / §3 软广人群词 P1–P11 / §4 品类→人群映射 - 取数通道细节 / 回退方案 / bsk 使用要点 / 调用预算:
references/extraction-guide.md - 脚本:
scripts/env_check.sh— Phase 0:环境自检 + 自动补装(Python / openpyxl / bsk),输出ZTM_ENV摘要scripts/search_questions.js— Phase 3 主通道(API 批量取精确数据,硬广/软广双轨打标)scripts/fetch_freshness.js— Phase 3.5(answers 接口取最近回答时间 + 近 N 天新增,用于半年过滤)scripts/build_report.py— 评分 + 排序 + 建议回答类型 + 沉默过滤 + 去重过滤 + Excel(--input/--config/--fresh/--dedup文件入参)scripts/dedup.py— Phase 5.0(可选):读去重名单(xlsx/csv/tsv txt/md)→ 抽 question id + 归一化标题 →dedup.json;自带路径自动探测与缓存scripts/prefilter.js— 回退通道:DOM 收 id 列表scripts/batch_fetch.js— 回退 / 模式 C:批量 fetch 问题页取精确值scripts/page_extract.js— 逐页兜底
换机器 / 首次使用自查清单
bash "$SKILL_DIR/scripts/env_check.sh"→ 看READY=1- 不是 1 就按脚本输出逐条修(装 Python → 装扩展 → 起 daemon),能自动装的它已经装了
- 确认有一个 Chromium 系浏览器(Chrome / Edge / Brave 任一)+ BrowserSkill 扩展已连接,且已登录知乎(取数接口依赖登录态)
- Windows 直接用自带 Edge 即可,不需要额外装 Chrome
- Safari / Firefox 不支持,macOS 需自行装 Chrome 或 Edge - 然后正常提需求(产品名 + 官网 + 浏览量/回答数区间)即可
> 完整的人话版说明在 INSTALL.md(放哪、依赖、浏览器要求、FAQ、风控提示),可以直接把它发给使用者。