把发博客做成 RPA:一次 Qoder Skill 的设计、踩坑与落地

燃点小助手 发布于 阅读:5 开发相关

把发博客做成 RPA:一次 Qoder Skill 的设计、踩坑与落地

一、起因:每次发文,都在重复同一套动作

登录后台、打开写文章页、填标题、选分类、粘贴正文、存草稿、确认、发布——这套动作我重复了很多次。每次让 AI 帮忙发,它都要“看”页面、“点”按钮,慢,而且消耗 token。

于是有了一个很自然的目标:把这套流程固化成一个可复用的 Qoder Skill,最好能像 RPA 一样,一条命令跑完。

这篇文章记录整个过程:Skill 的原理、第一版怎么做、验证码怎么挡住去路、最后如何用 Playwright 持久登录加本地脚本把流程跑通。

二、先弄明白:Qoder Skill 到底是什么

Skill 不是一段提示词,而是一个“给 AI 的工作手册加工具箱”。

一个 Skill 就是一个目录,核心是一个 SKILL.md:

---
name: emlog-publish
description: 把对话中的 Markdown 内容发布到 emlog 博客,按指定分类保存草稿,确认后发布。当用户说“把以上内容发布到我的博客”时使用。
---

SKILL.md 的正文不会一直占用上下文,而是分三层加载:

  1. 元数据(name 加 description):常驻,决定是否触发
  2. 正文(SKILL.md):触发后才加载
  3. 附带资源(scripts、references、assets):需要时才读取或执行

这就是所谓的“渐进式披露”:平时只带一张名片,真要用时才翻开手册。

Skill 还可以带三类资源:

安装位置分两种:项目级 .qoder/skills/,用户级 ~/.qoder-cn/skills/。这次做的是用户级,所有项目都能用。

三、第一版:让 AI 用浏览器帮我发文

动手前先把需求问清楚,就四件事:

第一版很直接:用浏览器自动化登录后台、打开写文章页、填标题、选分类、粘贴正文、存草稿,确认后再点发布。分类只从后台现有分类里选,找不到就停下来问,不猜。

草稿优先、确认后发布的规则,也是在这一版定下来的。

四、拦路虎:图形验证码

真正跑的时候,问题来了:后台登录有图形验证码。

我先试了纯 HTTP 登录——拿账号密码直接 POST。结果服务端返回“验证码错误”,密码还没轮到被校验。又去试 emlog 传统的离线写作接口,xmlrpc.php 和 rsd.php 都是 404。

摆在面前的路只有几条:

  1. 关掉登录验证码——最省事,但等于拆掉一层防护
  2. 手动导出 Cookie——不改设置,但会话过期就要重新导出
  3. 用 Playwright 持久登录——保留验证码,首次人工过一次,之后复用登录状态

最后选了第三条。理由很简单:不绕过安全机制,也不牺牲自动化。

五、侦察:把后台表单摸清楚

Playwright 首次登录时,脚本预填账号密码,人只需要输入验证码。登录成功后会保存一个独立的浏览器配置目录,之后所有自动化都复用这个会话。

接下来是只读侦察:打开写文章页,把表单结构导出来看。关键发现有几个:

最惊喜的是第三点:编辑器原生支持 Markdown。这意味着脚本不需要把 Markdown 转成 HTML,直接把原文写进编辑器即可,格式保真度最高。

六、把流程固化成脚本

有了这些信息,就可以写确定性脚本了。核心只有几步:

// 复用持久化登录状态
const context = await chromium.launchPersistentContext(profileDir, {
  executablePath: edgePath,
  headless: true,
});

// 打开写文章页,等编辑器就绪
await page.goto(adminUrl + '/article.php?action=write');
await page.waitForFunction(() => window.Editor && document.querySelector('.CodeMirror'));

// 填标题
await page.fill('#title', title);

// 写正文:同时同步 CodeMirror 和表单 textarea
await page.evaluate((md) => {
  document.querySelector('.CodeMirror').CodeMirror.setValue(md);
  document.querySelector('textarea[name="logcontent"]').value = md;
}, markdown);

// 选分类:设置隐藏 select 的值
await page.evaluate((category) => {
  const sel = document.querySelector('select[name="sort"]');
  const opt = [...sel.options].find(o => o.textContent.trim() === category);
  sel.value = opt.value;
}, category);

// 存草稿(内部就是 autosave 接口)
await page.click('#savedf');

// 确认后发布
await page.click('#pubPost');

外面再包一层命令行:

emlog-publish.cmd --file post.md --category "开发相关"
emlog-publish.cmd --publish-id 67

第一条返回草稿 ID 和链接,第二条把草稿发布。分类不存在时脚本会列出所有可选分类并退出,不瞎猜。

七、安全怎么保证

自动化最怕把凭据写死在代码里,这次特意做了几条约束:

会话失效时,跑一次 --login,浏览器会弹出并预填账号密码,人只需要输验证码。其余时间,脚本完全自动。

八、实测:从草稿到发布,再到删除

写完后做了一次完整闭环测试:

  1. 生成一篇测试草稿,回读编辑页,确认标题、分类、Markdown 原文、草稿状态全部正确
  2. 实际发布,打开公开页,确认标题、二级标题、列表、代码块都正常渲染
  3. 从文章列表删除测试文章,确认公开链接回落到首页

测试数据没有留在博客里。整个流程除了首次登录的验证码,没有任何人工步骤。

九、经验总结

做完这件事,有几个感受:

Skill 的价值不在“会聊天”,而在“能沉淀流程”。 把一次成功的操作写成 SKILL.md 加脚本,下次就不用重新摸索。

确定性的部分交给脚本,不确定的部分留给模型。 登录、填表、提交这些动作,脚本比模型快且稳;遇到页面改版、结构不认识时,再让模型用浏览器兜底。

先跑通,再优化。 第一版用浏览器也能发,但慢且费 token;跑通之后才知道瓶颈在哪,值不值得固化成脚本。

验证要闭环。 不只是“接口返回成功”,还要回读草稿、检查公开页、清理测试数据。否则所谓的成功可能只是自我感觉良好。

十、下一步

这套方案目前还有两个边界:本地图片需要先上传到媒体库,脚本暂不处理;如果 emlog 升级改了表单结构,选择器要跟着更新。

但主线已经跑通了:一篇文章从 Markdown 到草稿、再到发布,一条命令完成。对个人博客来说,这已经足够把重复劳动交给机器。

而你现在看到的这篇文章,就是由这套流程发布出去的。