从 RAR 到华为云博客:AI Agent 全自动发布图文的技术实践
从 RAR 到华为云博客:AI Agent 全自动发布图文的技术实践
本文详细记录了用 Hermes Agent(AI 编程助手)从微信 RAR 文件提取文章素材、自动上传图片到华为云 CDN、最终一键发布到华为云社区博客的全过程,以及过程中遇到的 23 个坑与解决思路。
一、背景
收到同事分享的一个 case-article.rar 文件,里面是一篇关于「用华为云码道 CLI/TUI 开发幸运跑马灯抽奖网页」的实践案例文章,含 8 张操作截图。需求:读取 RAR → 提取 Markdown + 图片 → 发布到华为云社区博客。
目标很明确,但坑全在路上。下面用「发生了什么 → 怎么解决的」的方式记录每个环节。
二、完整流程总览
微信 RAR → 解压 → 读取 Markdown / PNG 图片
→ 启动 Edge 浏览器
→ 打开华为云博客编辑器
→ 获取 CSRF Token
→ 获取上传授权码 (auth_code)
→ 8 张图片逐一上传 FileServer (获得 CDN URL)
→ Markdown 转 HTML (嵌入 CDN URL)
→ 填充标题 / 内容 / 摘要 / 标签
→ 调用 window.saveBlog() 发布
→ 检测 /processcomplete 重定向确认成功
三、分步骤详解
3.1 解压 RAR 文件
Windows 没有预装 unrar / 7z,GitHub 下载被墙。解决:通过 npm 安装 win-7zip 包,其中自带 7z.exe。
npm install -g win-7zip
/c/Users/33808/AppData/Roaming/npm/node_modules/win-7zip/7zip-lite/7z.exe \
x "D:\...\case-article.rar" -o"D:\extracted" -y
3.2 启动浏览器并等待登录
使用 Playwright + Edge 浏览器:
const br = await chromium.launch({ headless: false, channel: 'msedge' });
const pg = await br.newPage();
await pg.goto('https://bbs.huaweicloud.cn/blogs/article', { waitUntil: 'load' });
坑:必须用 waitUntil: 'load' 而非 networkidle,华为云登录页有持续的心跳请求。
3.3 图片上传(最曲折的部分)
尝试 A:TinyMCE 粘贴自动上传 ❌
在编辑器中插入 base64 data URL 图片,期望 TinyMCE 自动上传到 CDN。等了 60 秒没反应。原因是华为云的 TinyMCE 没有配置 images_upload_handler。
尝试 B:WebUploader UI 上传 ❌
找到了页面中的文件输入框 input.webuploader-element-invisible,用 Playwright 的 setInputFiles() 选择文件。WebUploader 确实发起了上传,但图片没有插入到编辑器中。
尝试 C:直接调用 FileServer API ✅
拦截 WebUploader 的网络请求,发现了完整的 API 链:
Step 1:获取 CSRF Token
华为云的 CSRF cookie 是 HttpOnly 的(document.cookie 不可见)。通过 Playwright 的 page.route() 拦截 WebUploader 的真实请求:
await page.route('**/bisic/get-auth-code', async (route) => {
const headers = route.request().headers();
csrfToken = headers['csrf'];
await route.continue();
});
Step 2:获取上传授权码
POST https://devdata.huaweicloud.cn/rest/developer/fwdu/rest/developer/user/hdblogservice/v1/bisic/get-auth-code
Content-Type: application/json
csrf: <captured_token>
{"scene_type": "1", "file_postfix": "png"}
坑 1:必须用 POST,GET 返回 HD.92311001 parameter is error。
坑 2:必须传 JSON body {"scene_type":"1","file_postfix":"png"},空 body 返回 HD.92309999 internal error。
Step 3:上传文件到 FileServer
POST https://fileserver.developer.huaweicloud.cn/FileServer/uploadFile
Content-Type: multipart/form-data
file: <binary>
authCode: <from step 2>
坑:参数名是 authCode(驼峰),不是 auth_code 也不是 authcode。用错返回 HD.77701201 Required request parameter 'authCode'。
Step 4:获得 CDN URL
{
"result": {
"resultCode": "0",
"UploadFileRsp": {
"fileInfoList": [{
"fileDestUlr": "https://fileserver.developer.huaweicloud.cn/FileServer/getFile/...png"
}]
}
}
}
重复 8 次,得到全部图片的 CDN URL。
3.4 Markdown 转 HTML
自己写了一个轻量级 Markdown 解析器,支持:
#标题`代码块|表格-无序列表 /1.有序列表>引用---分隔线bold/ `code/link`[图:alt]图片(替换为 CDN URL)
关键:先统一处理 Windows 的 \r\n 换行符(坑:不处理的话标题正则匹配不到)。
3.5 填入编辑器并发布
标题
用 Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set 设值,绕过 UI 点击被弹窗拦截的问题。
内容
绕过 TinyMCE API(它会抛 TypeError: Cannot read properties of undefined (reading 'map')),直接写入 iframe body:
for (const f of document.querySelectorAll('iframe')) {
const d = f.contentDocument || f.contentWindow.document;
const b = d.querySelector('body');
if (b && b.getAttribute('contenteditable') === 'true') {
b.innerHTML = html;
if (typeof tinymce !== 'undefined' && tinymce.editors?.length) {
tinymce.editors[0].fire('change');
}
}
}
摘要
坑:#summary 是 textarea(HTMLTextAreaElement),不是 HTMLInputElement。用 input 的 setter 会报 Illegal invocation。直接用 s.value = t 即可。
标签
模拟 Enter 事件需要同时传 key: 'Enter' 和 keyCode: 13(部分 tagator 实现只认 keyCode)。
发布
调用页面的原生发布函数:
window.saveBlog({
titleData: title,
contentData: html,
introData: intro,
tagNameData: tags,
isLock: 0,
blogResource: 1,
isReship: 1,
disclaimerFlag: 1,
blogType: 0,
isOriginal: 1,
source: 1,
});
检测成功:页面 URL 重定向到 /processcomplete?type=blog。
四、23 个踩坑全记录
| # | 问题 | 现象 | 原因 | 解决 |
|---|---|---|---|---|
| 1 | 发表按钮禁用 | 灰色不可点 | 前端验证不感知 API 注入 | 调 saveBlog() 绕过 |
| 2 | TinyMCE 抛错 | setContent() 报 map 错误 | eco-tinymce.js 插件 bug | iframe body 直接写入 |
| 3 | saveBlog 不可用 | 调用时 undefined | 异步加载 | 轮询等待(30 次×2s) |
| 4 | 弹窗遮挡 | 点击超时 30s | .developer-modal 拦截 pointer events | display:none 事先隐藏 |
| 5 | API 404 | 请求 404 | 用了 bbs 域名 | 用 devdata.huaweicloud.cn |
| 6 | blogResource 越界 | HD.92311001 | 值不在 1-3 | 限制 1=原创 |
| 7 | 标签无效 | HD.92320035 | 非有效 product-item | 用已验证标签 |
| 8 | 字段名错 | API 不识别 | title 非 titleData | 用正确字段名 |
| 9 | CSRF header 错 | 403 no request_csrf | header 名应为 csrf | 用 csrf 非 hd-csrf |
| 10 | cookies 过期 | 登录跳转 | 会话短 | 单次会话完成 |
| 11 | Markdown 乱 | 标题不渲染 | \r\n 未处理 | 先转 \n |
| 12 | Edge 进程残留 | 多窗口混淆 | 反复重启 | 先 taskkill |
| 13 | saveBlog 无参 | isLock 错误 | 未传 submitData | 必须传完整参数 |
| 14 | 推广弹窗 | 阻断重定向 | 登录后推广界面 | 发布前隐藏 |
| 15 | product 错误 | HD.92320035 | 标签无效 | 用 AI Agent,华为云 |
| 16 | 草稿恢复失败 | saveId 未设置 | CSRF 校验失败 | 手动填充内容 |
| 17 | 摘要 type 错误 | Illegal invocation | #summary 是 textarea | 直接 s.value = t |
| 18 | 图片自动上传失败 | 无 CDN URL | TinyMCE 无 upload handler | FileServer API 直调 |
| 19 | 弹窗拦截点击 | click() 超时 | modal-cover 遮挡 | display:none + evaluate |
| 20 | 标签 Enter 事件 | 标签不生成 | 缺 keyCode | 同时传 key + keyCode |
| 21 | get-auth-code 需 POST | HD.92311001 | 默认用 GET | 改为 POST + JSON body |
| 22 | 参数名需驼峰 | HD.77701201 | auth_code 非 authCode | 用 authCode |
| 23 | CSRF token 不可读 | 403 | HttpOnly cookie | route 拦截 WebUploader 请求 |
五、最终效果
- 文章:「用华为云码道 CLI/TUI 从零开发「幸运跑马灯抽奖」网页应用」
- 正文:6,849 字符 Markdown → 10,705 字符 HTML
- 图片:8 张截图全部上传到华为云 CDN
- 标签:AI Agent, 华为云, CodeArts, 大模型
- 发布状态:✅ 成功
六、经验总结
- 能调 API 就别点按钮:前端 UI 自动化有上限,后端 API 直调更可靠。发表用
saveBlog(),图片用 FileServer API。
- 用页面原生函数:
window.saveBlog()比手动fetch()更可靠(自动处理 CORS、CSRF、状态管理)。
- CSRF 是隐形杀手:HttpOnly cookie 不可 JS 读取,但通过
page.route()拦截可获取。正确 CSRF 是一切 API 调用的前提。
- 先粗后精调试:先发布纯文字版确认流程通顺,再补图片。避免图片上传失败拖垮整体发布。
- evaluate 替代点击:弹窗遮挡 + 按钮禁用使
page.click()/fill()不可靠。用page.evaluate()通过 DOM setter 直接设值更稳定。
- POST body 不能省:
get-auth-codeAPI 看似简单,但scene_type和file_postfix缺一不可。
- 参数命名是细节魔鬼:
authCode不是auth_code,titleData不是title。一个字母差就是 API 不识别。
七、相关资源
- 华为云博客:https://bbs.huaweicloud.cn/blogs
- 码道 CLI 文档:https://support.huaweicloud.cn/usermanual-cli/codeartsagent_cli_0001.html
- Playwright:https://playwright.dev
- 最终发布脚本:
upload-all.mjs(捕获 CSRF → 上传图片 → saveBlog 发布 全自动)
本文本身也是通过 Hermes Agent 自动发布的——即用本文描述的方法发布了这篇文章。这是一种递归验证(笑)。
- 点赞
- 收藏
- 关注作者
评论(0)