WorkBuddy自己创建Skill自己用,实现自产自销
目录
一、拾枝杂谈
之前我用 WorkBuddy 配合 Playwright 成功收集了自己 CSDN 后台的文章数据,并写入飞书表格统一管理,我对 WorkBuddy 的干活能力算是有了初步认可,感觉搭建多平台数据矩阵是一件可以实现的事情。
但是由于 CSDN 是我开始收集的第一个平台,所以做了大量的准备工作,包括框架安装和环境搭建问题、 playwright 操作登录态的方式选择问题、飞书的写入格式问题,等等。
这篇文章主要是记录我用 WorkBuddy 实现微信公众号数据采集过程中遇到的有意思的事情。
二、多平台统计字段不一致的问题
1.问题出现的原因
在向 WorkBuddy 确认了上下文情况后,我便直接让 WorkBuddy 开始尝试爬取 微信公众号 的后台数据。
有了之前 CSDN 的经验,WorkBuddy 写得脚本第一次爬取就成功了,而且也成功写入了飞书知识库。
但是我检查后发现,WorkBuddy 忽略了一个重大问题:不同平台的数据统计情况是不一样的,主要体现在统计字段不同,比如微信公众号不存在“展现量”这一字段,但是有“在看”,“转载”这样的特有字段。也就是说,每个平台的数据统计字段是不完全匹配的。
WorkBuddy 之前用了 CSDN 风格的列(如展现量、收藏)来统计文章,但是微信公众号根本没有这两个字段,反而漏掉了微信特有的“在看”,“转载”等字段。

2.问题解决的方案
为了解决这个问题,我和 WorkBuddy 达成了一致:即不同平台的字段差异问题,以各个平台为准。
比如之前统计的都是 CSDN 的文章数据,在写入飞书表格时,表格统计的字段就按照CSDN上的来;
现在统计的是微信公众号的数据,就按照微信公众号的来;之后的平台也是同理。也就是说不同平台对应的飞书表格的字段,要适配对应的平台。

WorkBuddy 在 config.py 中为每个平台加了 data_fields 字段定义,这样的话每个平台都有各自的列了。
比如这样:
CSDN = {
...
"data_fields": ["日期", "展现量", "阅读量", "点赞", "收藏", "评论"],
}
WEIXIN = {
...
"data_fields": ["日期", "阅读量", "点赞", "在看", "分享", "评论", "转载"],
}
接着,它又把 weixin.py 改为输出微信原生字段,动态构建 daily_stats,daily_stats 就是"这一行要写入飞书表格的数据"。也就是说:从 config.py 读出微信有哪些字段,就动态生成哪些字段的值。 平台字段变了,代码不用改。
3.又出现了新的问题
上面的解决方案只能解决新发布的文章的统计问题,可是我之前已经自己统计了两篇文章了,而且我用的是通用的字段。。。
也正因为我是先尝试了“手动统计”的滋味,才明白手动统计是不可能的,它就是个无底洞,把你的时间全吞进去。
所以现在出现一个新问题:之前统计的飞书子表的字段是通用的,但是现在我想的是每个平台都统计它各自的数据字段。所以新发布的文章统计的字段和旧文章统计的字段出现了差异。
于是我让 WorkBuddy 重新统计微信公众号的文章数据,并且我还告诉WorkBuddy:“如果我之前自己统计的字段有问题,你就把匹配的字段留下,不匹配的字段删了,缺少的字段再补充加上”。
三、Skill提炼
1.过犹不及
我让 WorkBuddy 重新统计文章数据,结果它直接用 --rewrite 模式清空并重写了全部5个飞书子表,把我之前统计好的旧数据全删光光了!
不幸中的万幸,飞书表格自带“查看修订记录”,也就是历史版本。我打开被清空 的子表,恢复到了清空前的版本。
我心说这还得了,于是我和 WorkBuddy 再次强调了一遍字段差异的处理方式,并用AI都能听懂的话详细解释了一遍。
WorkBuddy 终于明白了我的需求,随后它开始将功补过,先是修复了脚本代码,包括新增 _read_sheet() + migrate_schema_preserve_data():读出现有表,把旧行按新列重排(保留匹配值、新列留空、旧多余列丢弃、同日期旧行去重),然后再写回,整个过程不删任何行;以及将 write_scraped_data 逻辑改为:匹配到节点且列不一致、且未加 --rewrite 的情况下,默认走安全迁移。
并且我直接让 WorkBuddy 为我创建了一个专门解决“多平台统计字段不匹配”的 Skill。
2.这个Skill是干嘛用的?
WorkBuddy 给它起名叫 platform-field-matching(平台字段匹配约定),本质是一条 “项目约定 + 操作手册”,专门解决我这次踩的坑。
platform-field-matching 主要解决两个问题:
- 字段不匹配问题:每个平台的统计字段不一样,飞书表只能用该平台自己真实存在的字段。不能把 CSDN 的列(展现量、收藏)硬套到微信上——微信根本没有这两个,反而应该有 “在看”、“转载”等微信公众号才有的数据字段。
- 改字段时丢数据问题:把之前"删不匹配字段"的指令钉死成正确语义——删字段 = 删列,不是删行;历史数据行必须完整保留。
总之就是一句话铁律写在了 Skill 里:飞书某平台的列 = 该平台后台实际提供的字段;匹配字段留、不匹配字段删列、缺的字段补列,同时所有已记录的数据行原样保留。

up 在这里卖个关子,因为这个 Skill 是七个平台通用的,并且后续的自动化任务也会用到,而且后面其实还做了改动,所以这里就先不放具体的内容了。后续我会考虑在总览篇进行补充说明,或者开源到 Github。
3.趁胜追击
因为我在飞书表格中,是这样设计结构的:知识库下面,每个平台我单独创建了一个表格,但是每个平台下面我是以单篇文章为单位,每篇文章我都创建了子表。
并且我最初还设置了统一的表格格式:
- 表头那一栏(第一行):字体 加粗 + 字号 14pt + 黄底填充
- 日期那一栏下面的数据:字号 12pt
- 其他栏下面的数据:正常默认,无特殊要求
但是这里面有个坑,就是 Feishu API 的 font_size 单位是 px(像素),但 UI 上显示的是 pt(磅),所以要根据 px 和 pt的换算公式,调整代码。事实上这部分已经在代码里实现了。
feishu_writer.py 的 _apply_table_formatting,就是负责新建 或者 重写子表时给表头设 font_weight=bold + font_size=19 + 黄底,给日期列(A 列)数据设 font_size=16,其余列不动,之前微信 5 张子表重写时就是按这个格式落地的。
于是我想着把这个“格式约定”也提炼成一个Skill吧。
但是 WorkBuddy 这里给我的建议是,直接并进现有的 platform-field-matching Skill 里,作为单独一节,而不是新建一个。它给出的理由是,方便集中管理,达到少而精的效果。
我一想这不tm扯淡么,之前的 platform-field-matching 这个Skill,作用范围有限,主要是解决之后有新平台写入飞书时的字段匹配问题。但是我现在的表格格式问题可不只是适用于这个“加新平台”的问题,任何包括“日期”列的表格,涉及到创建,修改时,插入时,都可以调用这个Skill!
在我的威压下,WorkBuddy 也只能乖乖照做,又给我创建了一个 feishu-table-formatting Skill。而且我还让 WorkBuddy 把日期值的格式问题也加到了这个 Skill 里面。
随后,我让 WorkBuddy 根据这两个 Skill 重新收集微信公众号的文章数据,完美解决了平台字段不一致问题和新旧数据不兼容问题。
四、相关代码及说明
1.配置类有关代码:
config.py 中与微信相关的代码如下:
WEIXIN = {
"name": "微信公众号",
# 公众号后台首页
"creator_url": "https://mp.weixin.qq.com/",
# 公众号后台首页(带参数)
"manage_url": "https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN",
# 发表记录页面:这是实际抓取数据的地方
"publish_record_url": "https://mp.weixin.qq.com/cgi-bin/appmsgpublish?sub=list&begin=0&count=20",
# 飞书父表格 ID(总览表)
"feishu_sheet_token": "大家帮我点个赞吧!",
# 飞书父节点 ID(wiki 节点)
"feishu_wiki_node_token": "迅高智能",
# 所属飞书知识空间(引用全局变量)
"feishu_space_id": FEISHU_SPACE_ID,
# 是否启用采集
"enabled": True,
# 微信公众号的真实统计字段(7 个)
"data_fields": ["日期", "阅读量", "点赞", "在看", "分享", "评论", "转载"],
}
2.爬取数据的相关代码:
weixin.py 代码有将近600行,这里就不都放了,给大家摘取了一部分很有意思的代码. 大家可以先看一下,如下:
async def _extract_weixin_articles_js(self) -> list:
"""Extract article list from WeChat publish record page via JS.
WeChat embeds the full publish list as a `publish_page` JavaScript
object in the page. The data is more reliable than parsing DOM.
"""
js_result = await self.page.evaluate(r"""
() => {
const results = [];
const parseErrors = [];
if (typeof publish_page === 'undefined' || !publish_page || !publish_page.publish_list) {
return {
pageTitle: document.title,
url: window.location.href,
articleCount: 0,
error: 'publish_page not found',
articles: results,
};
}
const publish_list = publish_page.publish_list || [];
publish_list.forEach((publish_item, idx) => {
let info = publish_item;
// Some WeChat pages embed publish_info as a JSON string; others expose the object directly.
if (publish_item.publish_info && typeof publish_item.publish_info === 'string') {
try {
const decoded = publish_item.publish_info.replace(/"/g, '"');
info = JSON.parse(decoded);
} catch (e) {
parseErrors.push({index: idx, error: String(e), sample: String(publish_item.publish_info).slice(0, 100)});
return;
}
}
const sent_time = info.sent_info ? info.sent_info.time : 0;
const publishDate = sent_time ? new Date(sent_time * 1000) : null;
const appmsg_list = info.appmsg_info || [];
appmsg_list.forEach(app => {
results.push({
title: app.title || '',
article_id: String(app.appmsgid || ''),
publish_time: publishDate ? publishDate.toISOString().split('T')[0] : '',
publish_timestamp: sent_time,
stats: {
'阅读量': app.read_num || 0,
'点赞': app.old_like_num || 0,
'在看': app.like_num || 0,
'分享': app.share_num || 0,
'评论': app.comment_num || 0,
'转载': app.reprint_num || 0,
},
});
});
});
return {
pageTitle: document.title,
url: window.location.href,
articleCount: results.length,
publishPageType: typeof publish_page,
publishListLength: publish_page.publish_list.length,
parseErrors: parseErrors,
articles: results,
};
}
""")
因为它不是从HTML里面爬数据,而是直接读JS 全局变量。普通爬虫的思路是打开页面,找表格,然后读取里面的文字。
微信公众号后台不是这样做的,它是把整段发表记录数据,直接以一个名为 publish_page 的 JavaScript 对象形式,嵌在页面代码里,而这段代码做的就是:在浏览器里执行一段 JavaScript,直接访问 window.publish_page,把里面的数据掏出来,这比解析 DOM 更加稳定,因为 DOM 结构一变(class 名调整、布局改版),爬虫就坏;但 publish_page 这个变量名和字段结构相对更稳定。
Δ总结
- 好的,以上就是这篇文章的全部内容了,感谢阅读!
- 需要注意的是,在 WorkBuddy中,项目级 Skill 和 全局 Skill是不一样的。
- 项目级 Skill 只在当前这个工作区的 Skills 面板里出现,需要手动点开调用;它不会进入全局注册表。
- @skill: 命令(以及会话自动注入机制)搜索的是用户级全局注册表 ~/.workbuddy/skills/。
- 最终,up选择让 WorkBuddy 把两个都全局化,因为全局 Skill 在自动化运行时比项目级加载更可靠;项目专属字段映射文档放到全局也不会出错。唯一差别是:项目级便于团队共享/版本管理,全局级只对个人账号生效。
更多推荐
所有评论(0)