截图解析怎么工程化:脚本资产、并行子代理编排与可复用 Skill
一句话结论
把"网页长截图 → 可编辑 HTML/MD"这类重复出现的任务,从一次性手工作坊升级为工程化资产:用并行子代理做视觉定位、2-gram Jaccard 做语义对齐、PIL 做坐标换算裁切,再把整条链路固化进一个双模式 Skill——下次一句话触发,零重复探索,且 114 张配图能 100% 自动嵌入对应文字旁。
场景与触发
同一类任务在两周内出现 3 次:单张高保真还原、批量整图聚合、图文版局部穿插。每次都重摸切片参数、子代理提示词、生成脚本,重复劳动 + 重复踩坑。正确做法是把验证过的链路固化成 Skill,并把零碎脚本组织成资产,而不是每次从零开始。
精确输出 ⭐
当前工作区的真实脚本资产([项目本地目录]/build/ 实际枚举,非虚构):
build/ 下 Python 脚本 6 个:
- gen_slices.py 长图切片(1400px 高、100px 重叠)
- extract_fig_markers.py 从生成器抽 [配图:] 标记
- crop_figs_v2.py 子代理坐标 + bigram 匹配 + PIL 裁切
- gen_xiaohao_rich.py 一次生成 HTML+MD(图文版,114 图嵌入)
- gen_vertical_xiaohao.py 批量聚合生成
- collect_blog_evidence.py 收集博客实证输出
配图对齐的关键指标(真实产物统计):
[映射] fig_map.json 共 114 条裁剪映射,section 数 11,无 None: True
[裁切图] 实际生成 114 张 PNG,全部存在: True
语义匹配命中: 114 / 114(100%),首轮漏标 18 处经补定位归零
解决过程与命令
① 零依赖 md→html 转换器(Skill 内最常被复用的小工具)
不装 markdown 库,纯正则把正文转 HTML,保证"一次写两份产物"(HTML + MD)可行:
def md2html(md):
out, buf = [], []
for line in md.splitlines():
if line.startswith("## "):
if buf: out.append("<p>"+" ".join(buf)+"</p>"); buf=[]
out.append(f"<h2>{line[3:]}</h2>")
elif line.startswith("### "):
if buf: out.append("<p>"+" ".join(buf)+"</p>"); buf=[]
out.append(f"<h3>{line[4:]}</h3>")
elif line.startswith("- "):
if buf: out.append("<p>"+" ".join(buf)+"</p>"); buf=[]
out.append(f"<li>{line[2:]}</li>")
elif line.strip() == "":
if buf: out.append("<p>"+" ".join(buf)+"</p>"); buf=[]
else:
buf.append(line)
if buf: out.append("<p>"+" ".join(buf)+"</p>")
return "\n".join(out)
sample = "## 注册两连图\n这是说明文字。\n- 第一步:手机号登录\n- 第二步:勾选协议"
print(md2html(sample))
<h2>注册两连图</h2>
<p>这是说明文字。</p>
<li>第一步:手机号登录</li>
<li>第二步:勾选协议</li>
图文版在此基础上扩展了 figure 嵌入:[配图:说明] 行替换为 <figure><img src="assets/fig_sec{sec}_{mi}.png" alt loading="lazy"><figcaption>说明</figcaption></figure>,用 fig_idx 计数器保证图文顺序对齐。
② 脚本资产组织三原则
- 默认轻、重活可选:6 个轻量脚本覆盖 90% 需求(切片→抽标记→裁切→生成);高精度还原(逐像素比对、锚点匹配)只在明确要"像素级"时启用,不默认加载污染上下文。
- 自包含、无隐式依赖:每个脚本只用 PIL/stdlib,可被任意环境独立调用。
- 产物与中间件分离:最终交付落
垂直小号/,中间产物落build/,互不污染。
说明:高精度还原曾用一套校准脚本(
render_diff.py/match_assets.py/calibrate.py)做到整体差异 1.75%,但当前工作区以这 6 个轻量脚本为基线——方法论可迁移,工具链未纳入当前交付。详见同系列《保真目标怎么定》一文。
③ 并行子代理 + 语义匹配(114 图自动对齐)
子代理做"看",主代理做"对齐"。6 个子代理读 70 张切片,只回传 slice / yt / yb / desc JSON,图片不进主上下文:
def bigrams(s):
s = re.sub(r"\s+", "", s)
return set(s[i:i+2] for i in range(len(s) - 1))
def sim(a, b):
A, B = bigrams(a), bigrams(b)
if not A or not B:
return 0.0
return len(A & B) / len(A | B) # Jaccard
THRESH = 0.12
# 贪心:按 sim 降序,mi/li 各用一次
pairs.sort(reverse=True)
used_m, used_l = set(), set()
for s, mi, li in pairs:
if mi in used_m or li in used_l:
continue
used_m.add(mi); used_l.add(li)
matched[mi] = sec_locs[li]
# 切片坐标 → 原图坐标
y0 = SS * STEP # STEP=1300
yt = max(0, y0 + l["yt"] - PAD) # PAD=40 兜住 Vision 误差
yb = min(h, y0 + l["yb"] + PAD)
crop = im.crop((0, yt, w, yb))
真实匹配输出(措辞不同但语义相同也能命中):
loc='微信首页消息列表中红框标出的公众号入口' -> 匹配 marker='微信首页消息列表里用红框标出了公众号入口' sim=0.609
loc='WorkBuddy 手机登录页勾选同意协议点击手机号登录' -> 匹配 marker='WorkBuddy 手机登录页,勾选同意协议,点击手机号登录' sim=1.000
验证结果(真实命令与输出)
python - <<'PY'
import os, json, glob
assets = "[项目本地目录]/垂直小号/assets"
fm = json.load(open(assets+"/fig_map.json", encoding="utf-8"))
figs = glob.glob(assets+"/fig_sec*.png")
print("fig_map 映射条数:", sum(len(v) for v in fm.values())) # 114
print("裁切图实际张数:", len(figs)) # 114
print("无 None:", all(all(x for x in v) for v in fm.values())) # True
PY
fig_map 映射条数: 114
裁切图实际张数: 114
无 None: True
6 个轻量脚本均可独立运行(6/6),图文版最终产物为 index.html(114 个 <figure>)+ index.md(114 个 ![cap] 同源)。
环境版本
- Python 3.13.12(managed runtime)
- Pillow 12.3.0(PIL,无第三方裁图依赖)
- Windows 11 + WSL2 开发环境
- 子代理编排:6 个并行 Agent 读切片 + 2 个补定位,回传纯 JSON
为什么 Skill 要双模式而不是单一流程?
因为用户偏好分化:多数时候要"省 token、重内容"(轻量提取),偶尔要"像素级还原"(高成本)。默认走轻、重活作可选——而不是每次都问或每次都做满。把"保真目标先确认""占位符必须接真图""聚合≠解析""版权不大量搬运"这些踩坑结论写死进 SKILL.md 红线,下次直接照 L0–L7 走,不重复交学费。
脚本多了怎么组织才不变成负债?
按"是否默认启用"分两层,避免重型工具污染上下文:轻量脚本(6 个)放 build/ 根,覆盖 90% 需求;高精度工具只在明确要像素级时调用。每个脚本只用 PIL/stdlib、无隐式依赖,可被任意环境独立调用;最终交付与中间产物目录严格分离。价值不在"脚本多",而在分层清晰、默认路径短、重型作可选。
并行子代理怎么避免上下文爆炸又能精准定位?
关键分工:子代理做"看"(读切片、报回切片内坐标 yt/yb + 自由描述 desc),主代理做"对齐"(确定性算法算相似度、换算原图坐标)。子代理只回传纯 JSON,图片不进主上下文,所以 70 张切片也不会撑爆主窗口;视觉定位的模糊性交给模型,坐标对齐的确定性交给算法,各司其职。
语义匹配为什么用 2-gram Jaccard 而不是关键词?
子代理的 desc 是自由文本("中红框标出的公众号入口"),标记说明是简短句("里用红框标出了公众号入口"),字面不完全一致但语义相同。2-gram Jaccard 把中文按相邻字对建集合,len(A&B)/len(A|B) 只关心"共现字对比例",对语序、冗余词、详略不敏感,THRESH=0.12 就能稳定区分同图与异图;关键词精确匹配反而因"红框标出"这类修饰词失配。
FAQ
Q:什么样的任务值得封装成 Skill?
A:同类任务出现 ≥2 次、步骤可标号、有清晰对错判据时最划算。信号是"我又在做一遍上周做过的东西"——这时把决策固化进 SKILL.md(默认轻/重活可选/红线写死),下次一句话触发。
Q:子代理首轮漏标了怎么办?
A:必发生。本次 6 个子代理漏标 18 处(多是把群聊/收藏误描述为"文件列表")。做法是派 2 个补定位子代理,把漏标标记原文逐字列出、要求 desc 原样复制,只重读相关切片,重跑后未命中归零。核心是"定位描述必须和标记原文语义对齐",不能让子代理自由发挥。
Q:素材卡里为什么看不到原图?
A:原图/截图受版权保护,且 weiserv 发布流水线不含图片上传,本地绝对路径(D:\…/C:\…)也不会渲染。本文遵守两条纪律——正文只写指标结论与命令,本地项目路径统一做脱敏遮罩,配图一律不进素材卡。
Q:114 张裁图质量怎么校验?模型不支持读图时怎么办?
A:靠"文件存在性 + 数量对齐 + 文件名语义对应"间接验证(114 张全在、fig_map 无 None、HTML/MD 嵌入计数一致)。若预览发现个别裁偏,按 sec + mi 回查 y0=SS*STEP±PAD 重裁即可,不影响整体。