Install
openclaw skills install @timerate-966/zhihu-research-page-skill自动搜索并整合≥500次真实引用,生成知乎风格≥10万字多答主深度知识网页,支持任意比例裁剪版本输出。
openclaw skills install @timerate-966/zhihu-research-page-skill/ zhihu-research-page 执行5%版本
主题:什么是 Harness Engineering?
/ zhihu-research-page 执行100%的v2版本
主题:国际象棋怎么学?代数记谱法、西西里、西班牙主流开局这些是什么?
/ zhihu-research-page 执行200%的v2版本
主题:各种调式的流行歌曲都有什么特点,它们各有什么代表作?
当前版本:v21(2026-07-21)
运行时需检查此版本号:若高于上次执行记录,则从 skill 目录重新读取最新 SKILL.md 和脚本更新工作区。
更新日志(每行一句,格式 vN [yyyy-MM-dd HH:mm:ss] 内容):
输入一个研究主题 → 自动产出:
该工作流基于已验证的 LILYGO T-WATCH-2020 项目实战流程提炼而成。
为避免工作区根目录散落大量中间文件,所有产出严格按以下目录存放:
工作区/
├── index.html # 最终 HTML 骨架(唯一留在根目录的文件)
├── CHANGELOG.md # 更新日志(可选)
├── images/ # 所有图片资源(与 HTML 同目录,相对路径引用)
│ ├── ch-01.png # 答主头像缓存(.png/.jpg/.svg)
│ ├── ch-02.svg # DiceBear 头像缓存
│ ├── diagram-03.png # 章节插图/截图/示意图/logo
│ └── ...
├── other/ # 网页草稿、脚本、中间产物
│ ├── _draft_*.html # 各章节 HTML 草稿
│ ├── assemble.py # 从 skill 复制的拼接脚本
│ ├── scan_html.py # 从 skill 复制的预扫描脚本
│ ├── *.ps1 / *.py / *.json # 其他中间脚本和配置
│ └── index_skeleton.html # 骨架备份(方便二次运行还原)
└── research_result/ # 搜索结果及中间产物
├── search_result.md # 搜索结果累积文件
└── *.md / *.json # 来源池清单、搜索报告等
铁律:
mkdir -p ./images ./other ./research_result./other/ 和 ./research_result/ 前缀,不依赖 cdindex.html 仍在根目录遇到异常时,必须先读取 TROUBLESHOOTING.md 查找匹配方案。该文件覆盖 12 类高频异常的根因和修复步骤(配额耗尽、task-notification 不实、<code> 字体异常、Windows 编码崩溃、颜色归一误伤、字数失真、权限被拒等)。仅在文件中无匹配方案时,才自行分析处理。
[0] 收集输入 → [1] 诊断问题 → [2] 创建骨架 HTML → [3] 并行搜索(500+) → [4] 规划章节(基于搜索) → [4.5] 答主身份与头像配置 → [5] 并行写章 → [6] 组装校验 → [7] 交付
严禁跳过任何阶段。搜索和撰写都通过 Subagent 委托执行,主流程不直接生成章节内容。
使用 Agent 工具启动的 general-purpose 子代理在写入文件时经常遇到 Write/Bash 权限被拒。需在项目根目录的 .claude/settings.local.json 中显式授权:
{
"permissions": {
"allow": [
"Bash(python *:other/_draft_*.html *)",
"Bash(python *:other/scan_html.py *)",
"Bash(python *:other/assemble.py *)",
"Write(*:other/_draft_*.html)",
"Write(*:index.html)",
"Write(*:other/assemble.py)",
"Write(*:images/*.png)",
"Write(*:images/*.jpg)",
"Write(*:images/*.svg)",
"Bash(curl *:images/*.png *)",
"Bash(curl *:images/*.svg *)",
"Edit(*:other/_draft_*.html)",
"Edit(*:index.html)"
]
}
}
若无此文件则创建。授予写入权限的文件类型:_draft_*.html(章节草稿)、index.html(骨架)、assemble.py(拼接脚本副本)。
另外,如果 .claude/settings.local.json 中有 additionalDirectories 配置,确保当前工作区路径被包含在内,以便子代理能访问工作区文件。
为保证 7 个并行搜索代理之间不重复劳动、且下游章节规划/撰写能读到一致的最新证据,所有搜索结果统一沉淀到工作区文件 search_result.md,采用「读上下文 → 搜索 → 返回即追加」闭环。
./research_result/search_result.md(读写,非 skill 目录)# 搜索结果汇总 (search_result.md)
> 本文件由主流程在每个搜索子代理回传后**自动追加**生成。
> 记录每次回传的「已搜索次数」与「核心结果」,便于过程追溯与断点续跑。
> 生成时间:{DATE}
> 目标:累计 ≥{N} 次真实搜索
> 累计搜索次数:{N}
> 已覆盖子方向:{N}
---
## 学习路径问题诊断
### 答主背景
- 学历 / 已修课程 / 自学经验 / 欠缺 / 当前目标
### 学习路径核心问题
**核心问题**:{一句话概括}
### 关键子问题
1. ...
2. ...
(阶段 1 诊断产物,供下游搜索代理了解上下文)
---
## [{N}] {子方向名称} — 更新于 {ISO 时间戳,含毫秒}
**本次搜索次数**:{N}次(WebSearch)
**核心发现**:
- 发现要点 1
- 发现要点 2
**关键数据/事实**(每条附 URL):
1. 数据/事实标题
- 来源:https://...
2. 数据/事实标题
- 来源:https://...
**存疑/争议**(可选,有则写):
- 说法 A(来源 X)与说法 B(来源 Y)不一致
- 某数据在不同来源中有显著差异
---
注意:来源直接内联在 **关键数据/事实** 区块下,格式为 N. 标题\n - 来源:URL。不单独拆「来源池」区块——来源跟着数据走,拆多了搜索代理反而容易省略。**存疑/争议** 为可选项,有矛盾信息时才写,服务于阶段 4 的「争议焦点」提取。
search_result.md,提取「已搜索次数总计」与已覆盖子方向,作为派发下一个代理时的去重依据。os.getcwd() 与全局变量 global_search_count),以 a 模式把该代理的结构化结果追加为新区块,自动累加「累计搜索次数」、刷新「最后更新」时间戳。search_result.md,作为下一步(派发下一个搜索代理 / 进入阶段 4)的上下文输入。run_in_background: true 并行启动,但写入 search_result.md 的动作只允许主流程串行执行(代理本身不直接写该文件,只把结果回传给主流程)。search_result.md 了解已有覆盖」,以尽量减少方向重叠。用户可能通过指定目录路径的方式引用本 Skill(例如 {用户目录}/zhihu-research-page),该目录是只读的技能定义源。
| 目录 | 读/写 | 说明 |
|---|---|---|
<skill_dir>/ | 只读 | 技能源码目录,包含 SKILL.md、references/、scripts/ |
<skill_dir>/references/css-template.css | 只读 | 从中读取 CSS 模板 |
<skill_dir>/scripts/assemble.py | 只读 | 从中复制到工作区后再编辑 CHAPTERS |
{当前工作区}/ | 读写 | 所有产出物的落点:index.html、draft*.html、assemble.py |
铁律:
<skill_dir>/ 下的任何文件向用户确认以下信息(缺省用默认值):
| 参数 | 默认 | 说明 |
|---|---|---|
| 研究主题 | 从对话上下文自动提炼 | 若用户未明确指定主题,扫描当前对话中涉及的课题/项目/问题域,提炼为一句中文主题 |
| 参考材料 | 空 | 用户提供的文档/链接/笔记,将注入所有子代理 |
| 章节建议 | 按主题自动规划 | 用户可指定 >5 个章节标题 |
| 输出路径 | ./index.html | 成品 HTML 路径 |
| 语言 | 中文 | 章节撰写语言 |
| 部分执行比例 | 无(完整执行) | 如用户说"仅执行 3%",则按比例缩减所有参数(见下方规则) |
主题提炼规则:若用户只说"执行 skill"/N%"版本"而未给主题,不追问用户,直接从当前对话上下文中提取——优先扫描最近的课题讨论、项目描述、诊断报告、打开的文件夹名、工作区
CLAUDE.md中项目描述,选最突出的一个作为主题。阶段 1 开头告知用户确认即可。
每次执行前,主流程必须:
SKILL.md 顶部的 <!-- VERSION:v{N} --> 注释,与工作区记录的版本对比./other/_OLD/ 归档(带时间戳前缀,不覆盖历史),再从 skill 目录复制最新文件SKILL_DIR="<skill_dir>" # 如 ~/.claude/skills/zhihu-research-page
CURRENT=$(cat ./other/.skill_version 2>/dev/null || echo "v0")
LATEST=$(grep -oP 'VERSION:\Kv\d+' "$SKILL_DIR/SKILL.md" | head -1)
if [ "$CURRENT" != "$LATEST" ]; then
echo "Skill 已更新:$CURRENT → $LATEST,正在同步..."
TS=$(date +%Y%m%d_%H%M%S)
mkdir -p ./other/_OLD/"${TS}"
# 归档旧文件(不覆盖历史记录)
for f in ./other/assemble.py ./other/scan_html.py ./other/wordcount_check.py; do
[ -f "$f" ] && mv "$f" "./other/_OLD/${TS}/"
done
# 复制新文件
cp "$SKILL_DIR"/scripts/assemble.py "$SKILL_DIR"/scripts/scan_html.py "$SKILL_DIR"/scripts/wordcount_check.py ./other/ 2>/dev/null
echo "$LATEST" > ./other/.skill_version
echo "旧文件已归档到 ./other/_OLD/${TS}/,新文件已就位,继续执行。"
fi
当主题包含 教程、教学、学习路径、学习、怎么做、入门、指南、上手、怎么用 等关键词时,自动启用。在阶段 0 额外执行:
当用户指定"仅执行 X% / N% 版本"时,按以下公式等比缩缩,且主题与上一版本完全相同。N 可以是任意正数(1%、30%、200%、500% 等均支持)。
| 参数 | 完整版 | N% 版本(N 为任意正数) |
|---|---|---|
| 工作目录 | 当前工作区 | 新建 v{M}_{N}pct/(与上一版本目录同级,M = 上一版本号+1) |
| 搜索次数 | ≥500 | max(5, floor(500 × N/100)) |
| 每章字数 | ≥11,000 | max(100, floor(11000 × N/100)) |
| 全页字数 | ≥100,000 | floor(100000 × N/100)(无额外地板,1% 就是 1000) |
| 章节数 | 10 | max(3, min(30, floor(10 × N/100)))(N%≤100% 自动缩为 3~10 章;>100% 允许扩到最多 30 章) |
| 搜索代理数 | 3 轮串行 | N%≤100% 用 1 个;>100% 按 min(6, floor(3 × N/100)) 代理 |
HTML <title> | 主题 | {主题}({N}%版本) |
知乎问题 <h1> | 主题 | {主题}({N}%版本) |
| 答主头像 | DiceBear(默认) | DiceBear,跳过头像搜索 |
各 N% 示例:
| N% | 搜索 | 字数/章 | 总字数 | 章节数 | 适用场景 |
|---|---|---|---|---|---|
| 1% | 5 | 110 | 1,000 | 3 | 极速预览 |
| 3% | 15 | 330 | 3,000 | 3 | 快速验证 |
| 30% | 150 | 3,300 | 30,000 | 3 | 中速草稿 |
| 100% | 500 | 11,000 | 100,000 | 10 | 完整版 |
| 200% | 1,000 | 22,000 | 200,000 | 20 | 深度加量 |
| 500% | 2,500 | 55,000 | 500,000 | 30 | 超深度(注意 token/时间预算) |
实用限制:N%>300% 时建议分批次交付,避免单次 session token 耗尽。
铁律:
v{N}_{percentage}pct/ 创建在与上一版本目录同级(如已有 v2-学习路径/,则创建 v3_3pct/)images/、other/、research_result/ 子文件夹<title> 和问题 <h1> 必须包含"N%版本"字样,用户打开即知为缩略版在搜索之前,先对主题做结构化诊断,明确研究的范围和方向。
将用户的研究主题拆解为:
基于诊断结果,规划 7 个搜索子方向(对应阶段 3 的 7 个搜索代理),每个方向一句话描述。确保搜索方向之间互不重叠、加起来覆盖诊断的所有关键子问题。
给出一个知乎风的问题标题草稿,用于阶段 2 的骨架 HTML。标题应:
主题:排查最近几天 CPU 和磁盘占用异常升高的原因
| 维度 | 内容 |
|---|---|
| 核心问题 | 最近几天系统 CPU 和磁盘 I/O 突然飙升的根本原因是什么? |
| 关键子问题 | ① 哪个进程/服务占用最高?② 是系统服务还是第三方程序?③ 是否有定时任务/计划任务触发?④ 磁盘 I/O 是随机读写还是顺序读写?⑤ 最近安装了哪些软件/更新? |
| 7 个搜索方向 | 系统性能监控方法、常见高 CPU 进程排查、磁盘 I/O 诊断工具、Windows 资源监视器分析、Sysinternals 工具链、杀软/索引服务资源占用、最近 Windows 更新已知性能问题 |
| 临时标题 | 最近几天电脑风扇狂转、磁盘灯长亮?可能是这几个原因在搞鬼 |
直接读取 references/css-template.css 的完整内容,嵌入 <style>...</style> 块。不要修改 CSS(它已包含完整的知乎设计 Token、顶栏、卡片、回答、侧栏及响应式规则)。
模板如下({...} 部分根据主题替换):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>知乎 · {主题的一句话问题}</title>
<style>
/* 粘贴 references/css-template.css 完整内容 */
</style>
</head>
<body>
<header class="zh-header">
<div class="zh-header__inner">
<a class="zh-logo" href="#">知乎</a>
<nav class="zh-nav">
<a href="#" class="active">首页</a><a href="#">会员</a><a href="#">发现</a><a href="#">等你来答</a>
</nav>
<div class="zh-search"><input class="zh-search__input" placeholder="{根据主题填占位}"></div>
<div class="zh-actions"><button class="zh-btn--follow" style="margin-left:0;padding:4px 16px;font-weight:600;">提问</button></div>
</div>
</header>
<div class="zh-page"><div class="zh-container">
<main class="zh-main">
<div class="zh-card">
<h1 class="zh-question__title">{主题的知乎风问题标题}</h1>
<div class="zh-question__meta"><span>被浏览 12,847 次</span><span style="margin-left:12px;">关注问题 · 分享</span></div>
<button class="zh-question__follow">关注问题</button>
<span style="font-size:14px;color:var(--text-secondary);margin-left:12px;">5 个回答</span>
</div>
<!-- 预留 1~2 条引导回答(可选) -->
<!-- ASSEMBLE -->
</main>
<aside class="zh-sidebar">
<div class="zh-card zh-sideblock">
<h3>📑 本页章节</h3>
<ol><li>(组装时自动填充)</li></ol>
</div>
</aside>
</div></div>
</body>
</html>
从 skill 目录(只读)的 references/css-template.css 读取,写入工作区的 index.html。示例:
import os
skill_dir = "<skill_dir>" # 用户指定的技能目录路径
workspace = os.getcwd() # 当前工作区
css_path = os.path.join(skill_dir, "references", "css-template.css")
css = open(css_path, encoding="utf-8").read()
html = html.replace("/* 粘贴 references/css-template.css 完整内容 */", css)
# index.html 写入工作区
open(os.path.join(workspace, "index.html"), "w", encoding="utf-8").write(html)
必须完整嵌入 CSS,不缩略。CSS 文件只读,不修改。
核心教训:per-session 搜索配额是主子代理共享硬限——7 代理并行各要求 ≥72 次,总需 ≥504 次,多数代理在 20-30 次即触顶,大量 token 消耗在"尝试→失败→请提升配额"空转。改为 3 轮串行:
remaining = 200 # 全局剩余配额(主子代理共享)
agents_per_round = min(3, remaining // 72) # 每轮最多 3 个,确保每个 ≥72 次
if agents_per_round == 0:
# 剩余不足 72 次时,剩余配额集中给 1 个代理
agents_per_round = 1
执行方式:
WebFetch 抓取——不计入搜索配额搜索代理通用 Prompt 模板:完整模板见 templates/search_agent_prompt.md。
使用时读入并替换 {direction} 占位符:
prompt = open('<skill_dir>/templates/search_agent_prompt.md', encoding='utf-8').read()
prompt = prompt.replace('{direction}', agent_direction)
子代理完成验收标准(重要):
ls 检查指定文件已生成 + 文件大小 > 0./research_result/report_XX.md)作为完成凭证追加策略:优先用 Python
open(path, "a")追加字符串——比 Edit 工具更稳定(Edit 在 old_string 含反引号/公式/特殊字符时匹配脆弱,易失败需重试)。仅当需要更新文件头计数器时才用 Edit 做精准替换。
核心要求:每收到一个搜索子代理的回传,主流程必须立即将该代理的结构化报告追加写入工作区的 ./research_result/search_result.md,而不是等 7 个代理全部回传后再统一处理。
首次写入(第 1 个代理回传前):若 ./research_result/search_result.md 不存在,先以写入模式创建并写入文件头。文件头应包含「目标搜索次数」「累计搜索次数」「已覆盖子方向」三个实时计数器,并在文件尾预留 --- 分隔:
import os, datetime
ws = os.getcwd()
path = os.path.join(ws, "research_result", "search_result.md")
target = 504 # 目标总搜索次数(7 代理 × 72)
header = (
"# 搜索结果汇总 (search_result.md)\n\n"
"> 本文件由主流程在每个搜索子代理回传后**自动追加**生成。\n"
"> 记录每次回传的「已搜索次数」与「核心结果」,便于过程追溯与断点续跑。\n\n"
f"> 生成时间:{datetime.date.today().isoformat()}\n"
f"> 目标:累计 ≥{target} 次真实搜索\n"
"> 累计搜索次数:0\n"
"> 已覆盖子方向:0\n\n"
"---\n"
)
open(path, "w", encoding="utf-8").write(header)
注意:文件头中的 累计搜索次数 和 已覆盖子方向 每次追加后需由主流程回写更新,以便断点续跑时读取当前进度。
后续每个代理回传:以追加模式写入一个区块,并维护全局累计搜索次数 global_search_count(主流程内部变量,初始 0)。区块标题统一用 ## [{N}] {子方向名称} — 更新于 {ISO 时间戳} 格式(精确到毫秒):
import os, datetime
ws = os.getcwd()
path = os.path.join(ws, "research_result", "search_result.md")
# agent_report = 该搜索代理回传的结构化报告字符串
# agent_search_count = 代理自报的本方向搜索次数(从报告第 3 项解析)
# global_search_count 已在主流程初始化
# agent_direction = 该代理负责的方向描述
global_search_count += agent_search_count # 累加全局计数
batch_no = global_search_count # 用累计次数做序号(断点续跑友好)
now_ts = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] # 精确到毫秒
block = (
f"## [{batch_no}] {agent_direction} — 更新于 {now_ts}\n\n"
f"**本次搜索次数**:{agent_search_count}次(WebSearch)\n\n"
f"{agent_report}\n\n" # 代理的原始结构化报告
"---\n\n"
)
with open(path, "a", encoding="utf-8") as f: # 始终用 "a" 追加
f.write(block)
# 追加完成后回写文件头中的累计搜索次数和已覆盖子方向(用于断点续跑读取)
content = open(path, encoding="utf-8").read()
content = re.sub(r'> 累计搜索次数:\d+', f'> 累计搜索次数:{global_search_count}', content)
content = re.sub(r'> 已覆盖子方向:\d+', f'> 已覆盖子方向:{batch_no}', content)
open(path, "w", encoding="utf-8").write(content)
文件内容约定:
## 学习路径问题诊断 开头(含答主背景、核心问题、关键子问题)## [{N}] {方向} — 更新于 {ISO 时间戳} 区块,包含 **本次搜索次数**、**核心发现**、**关键数据/事实**(每条附 URL)、可选的 **存疑/争议****关键数据/事实** 区块下,格式:N. 标题\n - 来源:URL--- 分隔用途:该文件是搜索过程的可追溯日志,既可用于断点续跑(下次运行先读文件头的累计搜索次数),也可在阶段 4 规划章节时作为证据参考。
各代理回传并写入 ./research_result/search_result.md 后,从所有区块的 **关键数据/事实** 下提取 N. ... \n - 来源:URL 格式的链接,合并为一个去重清单。提取正则:re.findall(r'- 来源:(https?://[^\s\n]+)', content)。此清单将在阶段 5 传给每个撰写代理。
严禁在搜索之前猜测章节标题。 章节规划必须基于阶段 3 的搜索发现。
分析阶段 3 各搜索代理的结构化报告,提取:
确保:
将章节列表写入工作区的 assemble.py 的 CHAPTERS 变量(此文件是从 skill 目录的 scripts/assemble.py 复制到工作区的副本)。参考格式:
CHAPTERS = [
("基于诊断命名的章节1", "ch-01", ["_draft_ch_01.html"]),
("基于诊断命名的章节2", "ch-02", ["_draft_ch_02.html"]),
# ... 其余 8 章
]
为每个章节写 3~5 条预期覆盖要点(传给阶段 5 的撰写代理),这些要点直接来自搜索报告中的发现。如果某章的搜索结果不足,标注"本章需在撰写阶段补充 ≥30 次专项搜索"。
默认策略:使用 DiceBear 风格化 SVG 头像(稳定、统一、零搜索成本)。仅当用户明确要求真实头像时才启动身份搜索——子 agent 搜索真实头像的性价比极低(消耗 ~150 次调用、~64K token、~30 分钟,常见收获仅 8 个可用头像)。
| 优先级 | 来源 | 判定标准 |
|---|---|---|
| 1 级 | 真实人物公开头像 | 公开可独立验证的直链(GitHub CDN avatars.githubusercontent.com/u/<id>、豆瓣影人页、雪球/掘金/丁香园等平台公开头像、个人官网) |
| 2 级 | DiceBear 风格化 SVG | `https://api.dicebear.com/7.x/{bottts-neutral |
| 3 级 | 单字符占位 | <div class="zh-avatar" aria-hidden="true">{首字}</div>(仅在前两级均不可用时使用) |
铁律:
<platform>.com/v2-... 占位路径)先确认本机可用的头像图链源及代理端口:
# 测 GitHub CDN(通常最可靠)
curl -sI --max-time 5 "https://avatars.githubusercontent.com/u/1" | head -3
# 测 DiceBear API
curl -sI --max-time 5 "https://api.dicebear.com/7.x/bottts-neutral/svg?seed=test" | head -3
# 如有企业代理,补充 -x <代理地址>
curl -x http://proxy:port -sI --max-time 5 "https://avatars.githubusercontent.com/u/1" | head -3
记录实际可达的源和端口,作为后续验证的基础。不同机器结果可能不同,不预设。
对每个章节的人设领域,并行派发 ≥2 个子 agent 搜索真实人物公开身份:
任务:搜索「{章节领域}」领域的真实公开人物,获取其公开头像 URL。
要求:
- 搜索该领域的知名专家/博主/贡献者(GitHub、技术博客、学术主页等)
- 仅返回可独立验证的人物:姓名明确 + 领域身份可交叉印证
- 头像 URL 必须是公开直链(如 GitHub avatar CDN),非需登录的平台内链
- 身份不明者(仅知 ID 但查不到真实身份)不返回
- 每人返回:姓名、身份简述、头像直链 URL、印证来源
即验:主流程不等待所有子 agent 回传——候选图链一到就用 curl -sI 亲测可达,取"身份交叉印证 + 图链可达"双重确认的锚定值。无双重印证的降级到 DiceBear。
./images/(关键——避免外链失效)所有确认为有效(图链可达)的头像必须下载缓存到工作区 ./images/ 子目录,HTML 中引用本地路径而非外链:
# 真实头像:curl 下载并根据 Content-Type 确定扩展名
curl -sL -o "./images/ch-01.tmp" "https://avatars.githubusercontent.com/u/20641750?v=4"
EXT=$(file --mime-type -b "./images/ch-01.tmp" | cut -d'/' -f2 | sed 's/jpeg/jpg/')
mv "./images/ch-01.tmp" "./images/ch-01.${EXT}"
# DiceBear SVG:直接保存为 .svg(远程 API 可能失效或限流)
curl -sL -o "./images/ch-02.svg" \
"https://api.dicebear.com/7.x/avataaars/svg?seed=arduino-fan&backgroundColor=f4b400&radius=50"
缓存规则:
.png / .jpg),命名 ch-<序号>.<ext>.svg 后缀缓存avatar 字段改为本地相对路径(如 ./images/ch-01.png、./images/ch-02.svg)avatar_remote 字段供溯源将每章作者块统一为以下结构,src 指向本地 ./images/ 缓存路径:
<div class="zh-answer__author">
<div class="zh-answer__author-avatar">
<img src="./images/ch-01.png" alt="{答主名}头像" width="44" height="44" style="border-radius:50%;display:block">
</div>
<div class="zh-author__meta">
<div class="zh-author__name">{答主名}</div>
<div class="zh-author__bio">{一句话简介}</div>
</div>
<button class="zh-btn--follow" type="button">关注</button>
</div>
./images/ch-02.svg./images/ch-01.png注入章节后,用精确命令验证,不依赖跨行正则(跨行正则会被换行符截断,误判为未注入):
# ✅ 正确的验证方式——grep -o 按行匹配
grep -o 'author-avatar[^<]*' ./other/_draft_*.html | head -20
# 或用 Python 上下文打印
python -c "
import re
html = open('./other/index_skeleton.html', encoding='utf-8').read()
# 找每个 author-avatar 块内容
for m in re.finditer(r'<div class=\"zh-answer__author-avatar\">(.*?)</div>', html, re.S):
print(m.group(1).strip()[:100])
"
不要用 [^>]* 跨行匹配(会因换行截断匹配不到,误报"未注入")。
阶段 4.5 完成后,应有一个明确的 JSON 映射表传给阶段 5 写作代理:
{
"ch-01": {"name": "嵌入式老潘", "bio": "10年嵌入式开发经验", "avatar": "./images/ch-01.svg", "tier": "dicebear"},
"ch-02": {"name": "创客阿杰", "bio": "Arduino中文社区活跃贡献者", "avatar": "./images/ch-02.svg", "tier": "dicebear"},
"ch-03": {"name": "开源极客小凯", "bio": "智能硬件独立开发者, GitHub 5K star 项目作者", "avatar": "./images/ch-03.png", "avatar_remote": "https://avatars.githubusercontent.com/u/20641750", "tier": "real", "avatar_source": "GitHub用户头像(仅作视觉素材)"}
}
每个章节一条记录,含:
name — 答主名(与章节领域匹配)bio — 简介,与章节领域匹配avatar — 本地缓存路径(./images/ch-XX.<ext>)tier — real / dicebear / fallback(仅指头像图源的来源等级)avatar_remote — 真实头像原始外链(仅 tier=real 时存在)avatar_source — 头像出处说明(如"GitHub用户头像(仅作视觉素材)")在一条消息中批量启动 10 个 Agent(subagent_type: "general-purpose", run_in_background: true)。每个代理:
_draft_ch_XX.html 文件完整模板见 templates/writing_agent_prompt.md。使用时读入并替换占位符:
prompt = open('<skill_dir>/templates/writing_agent_prompt.md', encoding='utf-8').read()
prompt = prompt.replace('{topic}', topic)
prompt = prompt.replace('{chapter_title}', ch_title)
prompt = prompt.replace('{chapter_points}', ch_points)
prompt = prompt.replace('{chapter_urls}', ch_urls)
prompt = prompt.replace('{chapter_id}', ch_id)
templates/author_block.htmlpython -X utf8 scripts/wordcount_check.py ./other/_draft_ch_XX.htmlfor ch in chapters:
Agent(name=f"writer-{ch.anchor}", subagent_type="general-purpose",
run_in_background=True, prompt=chapter_prompt)
建议至少额外补充 30~60 次搜索在每个撰写代理中,用于核实所引用的具体数据点。
从 skill 目录(只读)复制两个脚本到工作区:
cp <skill_dir>/scripts/scan_html.py ./other/scan_html.py
cp <skill_dir>/scripts/assemble.py ./other/assemble.py
编辑工作区副本的 assemble.py:将阶段 4 规划的章节列表填入 CHAPTERS 变量。scan_html.py 无需编辑——它自动扫描所有 _draft_*.html。
标签完整性检查(assembler 兜底,问题消灭在草稿阶段更高效)
在运行 assemble.py 之前,必须先运行预扫描:
bash # Windows 注意:必须设置编码,否则 emoji 输出崩溃
python -X utf8 ./other/scan_html.py
# 或 set PYTHONIOENCODING=utf-8 && python ./other/scan_html.py
Windows 编码警告:scan_html.py 使用 emoji(⚠️✅)输出,Windows 控制台默认 cp936 编码会导致 UnicodeEncodeError。必须加 -X utf8 标志或设 PYTHONIOENCODING=utf-8,否则脚本在 Windows 上首次运行即崩溃。
脚本自动检测三类高频 <code> 标签问题(子代理生成内容的最大翻车点):
问题类型 检测项 后果 A. 开闭不匹配 <code> 开启与 </code> 闭合数量不一致后续全文变等宽字体 B. 块级标签嵌套 <code>...</code> 内出现 <p>/<table>/<h3>/<blockquote> 等font-family: monospace 泄漏到正文C. 异常闭合 闭合标签含中文字符(如 </strong文>、</code。>) 标签不闭合,浏览器行为不可预期 D. 交叉嵌套 <code> 与 <strong> 开闭顺序交叉字体继承链断裂
若发现问题,按报告逐文件修复(常见修复:把 <code> 内的块级标签移到外面,补上缺失的 </code>,修正错位的 </strong>)。修复后重跑 scan_html.py 确认清零,再进入 6.3。
排查优先级(重要):当用户引用本 skill 要求"修复网页结构问题"或"字体异常/全是等宽字"时,优先排查 <code> 标签完整性,而非修改 CSS。99% 的字体泄漏和结构异常来自 <code> 标签封闭不当,css-template.css 本身已验证无误。
6.3 运行 assemble.py
bash python ./other/assemble.py
脚本会依次:
- 补 h2/h3 样式(若缺失)
- 剥离旧章节(幂等),注入锚点 id
- 一次性将全部章节替换骨架的
<!-- ASSEMBLE --> 占位标记
- 去重
<style> 块:内容相同的只保留首次出现
- 统一硬编码主题蓝:蓝系 hex →
var(--zhihu-blue),非蓝系保留
- 自动更新回答数
- 重建侧栏(纯章节导航)
- 自动自查(5 项,不通过立即终止):
- ①
:root 中 --zhihu-blue 定义数(须为 1)及 var() 用法数
- ② 检测残留硬编码蓝系 hex(非
:root 行)
- ③ 重复
<style> 块检测
- ④
.zh-answer__body code 样式一致性校验
- ⑤
.zh-body 类名出现次数(须为 0)
- 写入成品 HTML
- 打印每章及总计有效中文字数
CSS 类名隔离:页面级包裹层已改名为 zh-page(v14 前叫 zh-body)。同名类在不同层级复用会导致模板强调色泄漏——assemble.py 的去重(步骤 4)和颜色归一(步骤 5)已内置防护。章节草稿内覆盖样式建议用更具体的选���器链(如 #ch-01 .zh-body h2)。
6.4 幂等性注意事项
assemble.py 首次运行后会消费骨架中的 <!-- ASSEMBLE --> 占位标记。如果需要在同一次会话中重跑(如补完章节后再次拼接),需先手工恢复占位符:
bash # 用 sed/Python 把已注入的章节替换回占位标记
python -c "
import re
html = open('./other/index_skeleton.html', encoding='utf-8').read()
# 找 </main> 的位置,在它前面还原占位符
html = re.sub(r'(\s*)(</main>)', r'\n <!-- ASSEMBLE -->\n\2', html, count=1)
open('./other/index_skeleton.html', 'w', encoding='utf-8').write(html)
"
或更简单的方式:在最初写骨架时保存一份原始副本 index_skeleton.html,每次重跑前还原。
6.5 判断达标
若 总计 < 100,000:
- 找出最薄弱的 2~3 章(有效字最少的)
- 返回阶段 5,用独立 Agent 聚焦扩写补足
- 重跑 scan_html.py → assemble.py 直至达标
若 总计 ≥ 100,000:进入阶段 7。
N% 模式注意:运行 assemble.py 前先编辑 TARGET_WORDS = floor(100000 × N/100),避免 N% 版本误报"还差 97801 字"。Windows 运行统一使用 PYTHONIOENCODING=utf-8 python ./other/assemble.py,防止 emoji 输出在 GBK 控制台崩溃。
阶段 7:交付与收尾
7.1 结构校验
确认成品 HTML 中:
- 含
zh-header、zh-sidebar、zh-main、</main> 等关键结构
<article> 数与回答数匹配
- 章节锚点 id 与侧栏导航 href 一致
<code> 标签开闭数量匹配(可用 grep -c '<code' ./other/_draft_*.html 和 grep -c '</code>' ./other/_draft_*.html 快速核对)
- 无
<code>...</code> 内嵌套块级标签(若阶段 6.2 scan_html.py 已通过则无需重检)
- 每章作者块含
zh-answer__author-avatar 且有非空 src(用 grep -o 'author-avatar[^<]*' ./other/_draft_*.html 逐行确认,不依赖跨行正则)
7.2 链接抽检
用 WebFetch 对 2~3 条最关键的官方/核心链接做可达性验证,确保不是死链。
7.3 交付
运行 present_files 把成品 HTML 交付用户,并通报:
- 全页有效中文字数
- 各章字数分布
- 搜索总次数
- 外链总数
- 链接抽检结果
7.4 工作记忆
使用模型的 Memory 机制,将任务完成记录追加到当前工作区的 .workbuddy/memory/YYYY-MM-DD.md。不要硬编码绝对路径。
质量规则(贯穿全局)
规则 标准 有效中文字数口径 去 <script>/<style>/所有 HTML 标签后的汉字(\u4e00-\u9fff)+ 中文标点(\u3000-\u303f、\uff00-\uffef),英文代码和英文链接文本不计 链接真实性 禁止编造任何 URL,每个链接必须来自真实搜索或已核验来源池 搜索次数 阶段 3 总搜索 ≥500 次;阶段 5 每章再补 30~60 次用于核实 每章字数 每章 ≥11,000 有效中文,全页 ≥100,000 风格一致 所有章节必须用知乎回答体,含作者块 + 操作条 诚实性 不同版本/来源的矛盾信息如实标注,不隐瞒不谈化 结构完整性 拼接后 HTML 含顶栏/问题头/侧栏/页脚,不丢 CSS class 标签每章 <code> 开闭数量必须匹配,<code> 内不得嵌套块级标签;严禁 <code> 与 <strong> 交叉嵌套 头像真实性 禁止编造头像图链;真实人物身份须交叉印证(≥2 独立来源);图链须经 curl 亲测可达;不足时诚实降级 DiceBear 作者块统一 所有章节作者块使用同一 HTML 结构(zh-answer__author-avatar > img),禁止变体;头像 URL 由阶段 4.5 统一配置 图片路径 网页中所有 <img> 的 src 必须指向本地 ./images/ 相对路径(如 ./images/ch-01.png),禁止外链图片 CSS 类名隔离 页面级包裹层使用 zh-page(非 zh-body),章节正文容器使用 zh-answer__body;避免同名类在不同层级复用;多个 <style> 块合并去重 组装前扫描 运行 assemble.py 前必须先跑 scan_html.py,问题清零后方可拼接
报错与降级
异常 处理 某搜索代理返回 <50 条来源 要求该代理补充,或补派一个额外搜索代理 某章节有效字 <11,000 要求该撰写代理扩写,或补派一个"扩写代理"追加内容到同文件 assemble.py 提示某文件缺失 检查文件名映射,更新 CHAPTERS 候补列表 组装后全页 <100,000 找出最弱 3 章,每章补 ≥(缺口÷3+2000) 字 撰写代理报错(无来源/编造 URL) 在对应章节末尾追加警告标记,必要时重写该章 scan_html.py 报 问题 逐文件手工修复后重跑 scan_html.py 确认清零,再进入 assemble.py 组装后页面字体异常/全是等宽字 优先排查 标签完整性(而非修改 CSS),运行 scan_html.py;常见根因是 内嵌套了块级标签 assemble.py 二次运行无效 <!-- ASSEMBLE --> 首次运行后被消费,需手工还原占位符(见 6.4)或从 index_skeleton.html 还原骨架子代理 Write/Bash 权限被拒 检查 .claude/settings.local.json 中 permissions.allow 是否包含 Write/Edit/Bash 对 _draft_*.html 的授权(见阶段 6 前置说明) 头像图链全不可达(通道探测失败) 降级到 DiceBear 风格化 SVG,标注"本轮头像因网络限制使用 DiceBear 替代" 真实人物身份无法交叉印证 降级到 DiceBear,标注原因(如"GitHub 用户 X 身份无法确认,以 DiceBear 替代") 组装后 author-avatar 缺失 用 grep -o 'author-avatar[^<]*' ./other/_draft_*.html 逐行确认(不依赖跨行正则),缺失的章节回阶段 4.5 补配头像 搜索代理集体触顶(主配额耗尽) 减少并发数至 floor(剩余配额/72),优先 WebFetch 抓取官网文档(不计入搜索配额) scan_html.py 在 Windows 上报 UnicodeEncodeError 加 -X utf8 标志运行:python -X utf8 ./other/scan_html.py;或 set PYTHONIOENCODING=utf-8 撰写代理 <code> 自检不通过 代理在草稿完成后立即修复 <code> 开闭/嵌套问题;问题消灭在草稿内,避免 assemble 阶段重跑多轮
资源文件
references/css-template.css — 知乎风格 CSS(直接嵌入 <style>)
scripts/assemble.py — 通用拼接与字数核验脚本(使用前编辑 CHAPTERS)
scripts/scan_html.py — HTML <code> 标签预扫描脚本(assembly 前兜底)
scripts/wordcount_check.py — 独立字数核验 + <code> 完整性检查(每章写完后运行)
templates/writing_agent_prompt.md — 章节撰写代理 Prompt 模板
templates/search_agent_prompt.md — 搜索代理 Prompt 模板
templates/author_block.html — 统一作者块 HTML 模板
TROUBLESHOOTING.md — 12 类高频异常及处理方法(遇到异常先读此文件)