上一篇《Hermes Agent 安装踩坑手记》是用 AI 编程助手从安装到撰文全程自动化的。但把这篇手记发布到 Emlog 博客的过程,本身又踩了一连串编码坑——中文正文在后台编辑器里变成了一堆
\u9c \u80 \u91的单字节乱码。这篇复盘既是原文内容的速览,也是这次"发布自动化"踩坑的完整记录,希望能帮到同样在搞博客自动发布的人。
一、原博客内容速览:《Hermes Agent 安装踩坑手记》
原文记录了在 Intel MacBook Pro(i7, macOS 13.7.8)+ 受限网络(github.com 超时)下安装 Hermes Agent v0.20.1 的 5 个关键坑与救命方案:
- 官方安装器 git fetch 死锁 ——
install.sh卡在 "Existing installation found, updating...",根因是 github.com:443 被屏蔽。方案:跳过安装器,本地仓库齐全就直接手动建 venv。 --extra all249 包源码编译卡死 ——[all]extra 拉入 400+ 重型依赖,任一缺 wheel 就触发 sdist 编译 + git+https 死锁。方案:最小化起步,只装核心依赖uv pip install -e .。- cryptography 50.0.0 放弃 Intel Mac —— PyPI 只有 arm64 wheel,锁版本又必须装。方案:装 Rust toolchain 从源码编译。
- openssl-sys 找不到 OpenSSL —— 连 openssl.org 下载源也 301 跳 github 被拦。方案:patch Cargo.toml 启用
vendoredfeature,openssl-src 自带源码从 crates.io 下载。 - uv 每次构建重新解包 sdist 覆盖 patch —— uv 保证可重现的独立解包策略反而吃掉了 patch。方案:把 patched 源码复制到稳定目录,用
uv pip install <本地路径>。
核心经验:当官方安装器不工作时,排查顺序是:卡在哪步 → 是否网络屏障 → 缺 wheel 还是缺工具链 → 编译依赖 → patch 是否真被吃到。
二、发布这篇手记时踩的坑:后台中文乱码
把上面这篇手记发布到 Emlog Pro 博客后台时,遇到了第二层坑——正文中文全部变成乱码。
现象
通过浏览器自动化把 markdown 正文注入后台编辑器并保存后,前台文章显示成一堆形如 æè¿å¨ 的乱码。后台编辑器里看到的是一串 \u9c \u80 \u91 这样的单字节"字符"。
根因:Latin-1 误解(每个 UTF-8 字节当一个 Unicode 码点)
中文"最"的 UTF-8 编码是 3 个字节 E6 9C 80。注入时用了 atob() 解码 Base64,但 atob() 返回的是一个 Latin-1 字符串——它把每个字节直接当作一个 Unicode 码点(得到 U+00E6, U+009C, U+0080 三个字符),而不是还原成"最"这个汉字。
当这串"Latin-1 误解"的字符串被写进编辑器并保存,数据库里存的就是 3 个独立字符而非 1 个汉字。前台用 UTF-8 渲染时,这 3 个字符又各自被编码成 2 字节(C3 A6, C2 9C, C2 80),于是显示成 æÂ 一类的乱码。
第一次"修复"为什么没生效
我最初改用了 decodeURIComponent(escape(atob(b64))) 来正确还原 UTF-8——这步逻辑是对的。但事后复盘发现:内容并没有真正写进 Emlog 的底层 textarea。Emlog Pro 后台用的是普通 <textarea name="logcontent">(没有 TinyMCE/KindEditor 等富文本编辑器),而我之前注入的目标元素不对,等于"修对了解码、写错了地方",保存的还是乱码。
最终修复方案
- 定位真正的存储元素:探测确认
KE=false / TM=false / XH=false / contenteditable=0,编辑器就是<textarea name="logcontent">。 - 绕过 https 混合内容限制:原本想用本地 HTTP 服务 + XHR 喂数据,但 https 后台页禁止 http XHR(即使是 127.0.0.1)。
- 在 Exec 沙箱内串联 Shell + browser_evaluate:在代码执行沙箱里用 Shell 读取本地 Base64 文件,拼成 evaluate 脚本,用
decodeURIComponent(escape(atob(b64)))正确解码后直接设置textarea.value,并派发 input/change 事件。 - 绕过 readonly 按钮保存:保存按钮 states 是 readonly,DOM 点击不触发提交,改用 JS
.click()成功提交。
字节级验证(确认不是显示层巧合)
为排除"前台显示正确但数据库其实是双重编码"的可能,做了两处字节级验证:
- 前台 HTML 原始字节:
curl抓取?post=21,整页可被 UTF-8 解码,「最近在」字节为e6 9c 80 e8 bf 91 e5 9c a8(正确 UTF-8),双重编码字节C3 A6 C2 9C C2 80未出现(-1)。 - 后台重新加载:重新打开编辑页,PHP 从数据库读回的 textarea 内容,
encodeURIComponent产生%E6%9C%80(正确)而非%C3%A6%C2%9C(双重编码)。
两处一致,证明数据库存储的就是正确 UTF-8。
三、经验教训
- 注入前先确认目标元素:富文本编辑器(TinyMCE/KindEditor)和普通 textarea 的注入方式完全不同。先探测
window.tinyMCE / KindEditor / contenteditable再决定写哪里,别盲目写。 atob返回 Latin-1,中文必须二次解码:decodeURIComponent(escape(atob(b64)))是 Base64→UTF-8 的标准姿势,直接用atob的结果会得到每字节一码点的乱码。- https 页面禁止 http 资源:本地 HTTP 服务器喂不可行,改用沙箱内 Shell 读文件 + evaluate 注入,或把内容内联。
- readonly/禁用按钮用 JS
.click():DOM click 工具受 disabled/readonly 限制,element.click()能绕过。 - 验证要到字节级:"显示正常"不等于"存储正确",双重编码会骗过眼睛。用
curl+ 字节分析检查原始字节,或对比encodeURIComponent的输出,才能确认数据库真存对了。 - Exec 沙箱的 throw 输出法:沙箱默认不返回表达式值,用
throw new Error(JSON.stringify(...))能把诊断数据带出来,方便调试浏览器自动化。
如果你也在用 AI 助手或脚本自动发布博客到 Emlog,希望这篇复盘能帮你少走一些编码弯路。
—— 2026.08.16 于 島上隨筆
本文由 Trae CN AI 编程助手协助撰写,采用 Solo Agent 自主智能体模式完成博客发布与乱码修复全流程。