MoreRSS

site iconGamea | 飘逝的风修改

游戏后台开发十多年, 目前就职于Tencent。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Gamea | 飘逝的风的 RSS 预览

逃离 IDE:我的纯终端 AI 编程工作流实践

2026-05-14 08:00:00

Featured image of post 逃离 IDE:我的纯终端 AI 编程工作流实践

在 AI Coding 时代,我反而比以前更频繁地使用终端。这听起来有点反直觉——既然有 Cursor 这类把 AI 深度集成进 IDE 的工具,为什么还要"回到"终端?如果你以为我讲终端就是聊 Claude Code 等 CLI,其实并不是!我们开发者还有很多要解决的体验问题,本文尝试给一整套可照抄的纯终端开发解决方案,希望对你有启发。

前言

很多年前,我是个彻底的 Vimer,所有编辑都在终端里完成,随便精进着 Vim 的技巧,运指如飞,手不离键,并且很享受这种状态。我的博客陆续分享过多次相关经验,但随着 VSCode + Copilot、Cursor 这些 AI 加持的 IDE 出现,效率提升明显,我别无他法只能改换门庭 —— 投入了 VSCode / Cursor 的怀抱。直到最近一年,当我开始频繁使用 Codex、Claude Code、OpenCode 这类终端 Agent,我们甚至不再需要去手工编辑代码,程序员的工作模式发生了根本的变化,突然我发现,是时候”回家”了。

回家不是因为恋旧,是因为当前模式有一些痛,比如我受不了每天要将 N 多个 Cursor(VSCode)的界面重连一遍,受不了在无数个窗口中来回寻找,受不了合上盖子它就停止工作。而这些痛点,其实都有解法。

AI 如是说:

终端不是退化,而是一个更稳定、可恢复、可长期运行的工作台。

我说:“你说得对!”。

我的终端开发工作流总览

我主要使用 macOS 系统,我的方案主要是基于 iTerm2 + tmux + Agent 开发定制功能实现的全流程开发工作流。

简单示意大概是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
iTerm2 Profile(本地入口)
SSH / 本地 Shell
tmux session(每个 session 对应一个项目)
 ├── window: agent ← Codex / Claude Code 在跑
 ├── window: test ← 跑测试 / 看输出
 ├── window: logs ← tail -f 一类的常驻日志
 ├── window: lazygit ← 提交前驾驶舱
 └── popup: temp shell(临时命令,关掉即销毁)

效果图大概是这样的: tmux纯终端开发效果

几个关键组件的分工:

  • iTerm2:它是我爱用的终端,当然你可以选择自己喜欢的。作为本地入口,我设定了 profile 区分不同机器、不同环境。
  • tmux:提供了会话复用,我再基于它的理念设计了一套工作编排方式,并且在 tmux 上进行了比较丰富的设定与扩展,下文会详细道来。
  • AI Agent(Codex / Claude Code):选个你喜欢的 Agent cli 跑,它们总是跑在 tmux window 里,提供了任务的连续性。

基于 iTerm2 的多终端区分与管理

iTerm2 我用得比较克制,比如我知道它有类似 Tmux 的分屏(pane)等功能,但是我不用,因为我们并不想被绑死在某一个终端软件上,不过我还是利用它的 Profile 机制做"环境区分":

  • 每个常用机器(家里设备、办公电脑、腾讯云 dev等)一个 profile;
  • 不同 profile 用不同的背景色——这是一个非常便宜但有效的"误操作防护"。比如生产机用偏红的背景,开发机用偏绿的背景,眼睛一扫就知道当前 shell 在哪。
  • 每个 profile 的 “Login Shell” 配置成 SSH 到目标机器后自动 attach 到一个固定名字的 tmux session

最后一条很关键。比如我在 iTerm2 的 Profile 的 Command 部分像这样:

1
ssh -t dev 'exec "${SHELL:-/bin/bash}" -lc "exec tmux new-session -A -s dev"'

我们确保一进入便是在 Tmux 管理之下,再也不怕会话丢失了。昨天那个打开的窗口在哪里的问题,或许一去不复返了。iTerm2 这边的配置就这么多,接下来才是真正精彩的部分。不过看这个之前,为了后续你更好的利用 Agent,我建议你不要忘了看我另一篇文章:Vibe Coding 远程开发时,如何优雅地贴图?,它会让后面你的开发更加原生和爽快一些。

基于 Tmux 的终端复用

Tmux 是这套工作流真正的”地基”,有人说:这么一款神器软件,它居然是完全免费可随意使用的,不用真的很亏。我觉得他说得很对。

它的几个核心抽象——session、window、pane——刚好对应我们开发中的三个层次:

  • session = 项目。每个项目一个 session,互不干扰;加上我们各种自主命名和可定制化的界面管理,让你”身轻如燕”般穿梭于各个项目。
  • window = 这个项目下的一个工作任务:跑 Agent、跑测试、看日志、操作 git,对于这个项目的各种场景,随便开个跑;我的习惯专事专办,用完随便关掉。
  • pane = 临时分屏:有时跑个测试、编译啥的要盯着可以。以前我爱用 pane,慢慢的其实分层清晰后,它用得更少了,真到要临时分屏时,我另有招数(下文会讲)。

关于 Tmux 有太多文章介绍,我觉得抄什么功能介绍是毫无价值的,但我要说的有点不一样,我们谈点真实感受和技巧。相关配置我已经放到 GitHub中 dotfiles-example,你可以随时取用。

1. 快捷键设置

我个人长期使用过另一个窗口管理软件,Ubuntu 上带的 byobu。它底层可基于 Tmux或 Screen等。它定义了不少快捷键其实挺好用的。不过用久了后有几个问题,太多键位其实你也用不上,多了反而容易误按以及和自建的冲突。为了追求极简我重新设定了一些规则。我挑几个重点说下:

  • tmux 默认 prefix 是 C-b,按起来不顺。byobu 默认是 C-a 或者是 F12,这也不顺手,用 C-a 对快捷键党更不友好(会冲突跳行首)。我建议换成 C-s,反正你早就习惯 Ctrl + s 了:)
  • byobu 风格的直接快捷键以 Fn 功能键居多,其实挺好用,我也继承和改良了一些,同时去掉我较少用的。
1
2
3
4
5
6
7
8
bind-key -n F2 new-window -c "#{pane_current_path}"
bind-key -n F3 previous-window
bind-key -n F4 next-window
bind-key -n F6 detach
bind-key -n F7 copy-mode
bind-key -n F8 command-prompt -p "(rename-window) " "rename-window '%%'"
bind-key -n S-F11 resize-pane -Z
bind-key -n F5 source-file ~/.tmux.conf \; display-message "tmux config reloaded"
  • F2 新建 window 时一定要 -c "#{pane_current_path}"新 window 默认继承当前目录——包括 tmux 默认新建 window 的行为,我也建议都改成进入当前 pane 所在目录,这会让你开心不少;
  • F5 reload 配置,byobu就是这么设定的,特别是咱们调整 tmux 配置的时候一键生效还是很便捷的。

更多的设定看仓库中相关文档,有byobu风格的延续,也有 VIMER 喜欢的各种切换和跳转,不在这里赘述。

2. 关键且有用的几个配置项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 鼠标模式:可以点击切 pane、拖边框调大小、滚轮翻历史
set -g mouse on

# 自动命名 window,但禁止应用程序通过 OSC 改名
set -g automatic-rename on
set -g allow-rename off

# 滚屏历史尽量大
set -g history-limit 100000

# 跟系统剪贴板打通
set -g set-clipboard external

allow-rename off 这一条是被 Claude Code 教育出来的——某些 CLI 工具会通过 OSC 转义序列把版本号、临时状态写到 window 名里,眼花缭乱。关掉之后,window 名只受 tmux 和我自己控制,整洁多了。

automatic-rename 的 format 我也做了一点定制:

1
set -g automatic-rename-format '#{?#{==:#{pane_current_command},zsh},#{b:pane_current_path},#{?#{m/r:^(node|python3?)$,#{pane_current_command}},#(~/.config/tmux/bin/tmux-window-name #{pane_pid} #{pane_current_command}),#{pane_current_command}}}'

逻辑很简单:

  • zsh 空闲时显示当前目录名;
  • node / python 解释器在跑脚本时,显示实际脚本名而不是干巴巴的 node
  • 其他进程直接显示进程名。

这样状态栏一眼就能看出每个 window 在干嘛。

3. 复制粘贴:tmux-yank + 选区按键

复制粘贴在 tmux 中我们可以借助 tmux-yank 插件解决了"跨平台复制到系统剪贴板"的问题:macOS 上自动调用 pbcopy,Linux 上自动选 xclip / xsel / wl-clipboard

然后剩下的选区控制我用了 vim 风格:

1
2
3
bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi V send-keys -X select-line
bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle

v / V / C-v 分别是字符选区、行选区、矩形选区——和 vim visual mode 一模一样。

我觉得这还不够,想起来 Cursor 这类 IDE 有个把终端内容一键发送到 AI 聊天框的功能,我也想实现类似,怎么办?

如下这样配置一键抓取 pane 最后 N 行写到 tmux 粘贴缓冲区,然后在另一个pane中 prefix + ] 贴上去。

1
2
3
bind-key 1 run-shell 'tmux capture-pane -pJ -S - | tail -n 10 | tmux load-buffer -'
bind-key 2 run-shell 'tmux capture-pane -pJ -S - | tail -n 20 | tmux load-buffer -'
bind-key 5 run-shell 'tmux capture-pane -pJ -S - | tail -n 50 | tmux load-buffer -'

使用时,大概估摸个数,用 prefix + 1/2/5 抓最近 X 行报错粘贴到比如你的 Agent window 里说:“看看这个错”,比手动选区快很多。

4. 弹出式终端:临时命令的好去处

我之前在用一套 Neovim 配置时,有个弹出式终端是我喜爱的功能,咱虽然不用再开 vim 了,但这个功能 tmux就可以提供:

1
bind-key t display-popup -E -w 90% -h 90% -d "#{pane_current_path}" "TMUX_POPUP=1 exec zsh -i"

按下 prefix + t,浮起一个铺满 90% 屏幕的临时 shell,关掉就销毁,完全不影响当前 window 的布局。我经常用它做临时查看或修改某个文件的内容等,这还是蛮舒适的。

添加这个功能时,我注意到起初它弹出终端感觉有点延迟,于是配置里我塞了一个 TMUX_POPUP=1 的环境变量。popup 里跳过 nvm、goenv 这类重型版本管理器初始化,把弹窗打开速度从 1 秒降到 0.1 秒以内,秒开的感觉真爽。如果要用到完整的各种环境参数,则创建独立的 windows即可。速度优先,职责清晰。

5. 会话保存与恢复

其实我本来不喜欢用会话保存这种功能的,感觉能少就少,直到几次莫名其妙的会话丢失。这里我使用了 tmux-resurrect + tmux-continuum 是 tmux 老用户的两大神器。

1
2
3
4
5
6
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-interval '15'

set -g @resurrect-save 'S'
set -g @resurrect-restore 'R'
  • continuum 每 15 分钟自动调用 resurrect 保存当前 session 布局;
  • 想要恢复时,手动按 prefix + R

或许除了异常丢失会话,重启机器时也能派上用场。这玩意会话快照也太省了,我还想着要不要定时清理,7 天可能都用不了几 MB,放心大胆的“瞎搞”吧。

基于 Lazygit 的审查

现在不兴提 Vibe Coding 了是吗?你们写的代码会不会自己再审查下呢?反正多数时候我是需要的。有时候 Agent 改完一堆文件,我心里是没底的。当前多数的编码 Agent 在修改内容的呈现上都不太理想。特别是 cli 模式的,靠那”惊鸿一瞥”根本不足以判断它有没有夹带私货。所以我需要比较直接的呈现修改的 Diff,这点 Codex App 以及 Cursor Agent (cli) 表现稍好,它们给 Diff 设计了独立的展示 UI。

而纯终端下,我们有 lazygit 不光比较好的呈现了修改面,而且把 Git 管理也集成了进来,所有操作收敛到一个 TUI 里,键盘党友好。

我把 lazygit 集成进 tmux 的方式有两种:

1
2
3
4
5
# 在当前目录打开 lazygit 浮窗
bind-key g display-popup -E -w 90% -h 90% -d "#{pane_current_path}" "lazygit"

# 在当前目录打开 lazygit 专用 window,退出后自动关闭
bind-key G new-window -n lazygit -c "#{pane_current_path}" "lazygit"

prefix + g 是"快进快出"的浮窗模式,适合扫一眼现状;prefix + G 是开一个专用 window,请好好审查你的改动吧。 lazygit 默认 diff 风格

这是lazygit的默认 Diff 方式,其实不太友好,我还是喜欢双栏对照着看,这也简单:

diff 渲染接上 delta 作为 lazygit 的 pager:

1
2
3
4
git:
 pagers:
 - pager: delta --paging=never
 colorArg: always

delta 的 diff 排版、语法高亮、行内差异显示都比原生 git diff 强一个量级,配上 lazygit 之后,我基本不再为了看 diff 而专门打开 VSCode 跑 Code Agent 了。

lazygit 接入 delta 后的 diff 风格

我发现 lazygit 除了各种样式可定制外,还支持自定义 commands,这就有意思了。比如当我需要填写 commit message 时,我可以按 C-a,让 AI 给我生成多个版本的 commit 文案,我还可以交互式的选择一个自己满意的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
customCommands:
 - key: "<c-a>"
 context: "files"
 description: "Pick AI commit message"
 command: "git commit -m {{ .Form.Msg | quote }}"
 loadingText: "Generating AI commit messages..."
 prompts:
 - type: "menuFromCommand"
 title: "AI Commits"
 key: "Msg"
 command: "ai-commit-candidates"
 filter: "^(?P<msg>.+)$"
 valueFormat: "{{ .msg }}"
 labelFormat: "{{ .msg | green }}"

这也就是一个实验功能,其实多数时候我是开着 Agent的,让它帮我提交生成 commit message往往更符合规范。但你多个选择,随手改了几行功能等,也不失为一个偷懒的方法。

10x 程序员:Agent 运行状态可视化

作为一个熟练驱使各种 Agent 干活的人,他们在做事时你肯定也不想闲着,AI 已经被你“奴役“了,它们进度怎样是你关心的一个事情吧。所以良好的通知机制(状态感知)就至关重要。他们都在想抢你的关注,每一个输入窗口都“嗷嗷待哺“。

一旦我们多任务并行处理,现在到底应该看哪个 window 便是一个问题:哪个正在干活,哪个已经来汇报工作,哪个正在等你指示。程序员最讨厌低效的轮询了,事件触发是必须的。于是,我们可以利用 Agent 本身的 hook 机制,让它主动通知 tmux 的 window,我选择以修改 window 名称(添加 emoji)来反映状态。

很多 Agent都提供了hook机制,像如下的 Claude Code 配置,可以在 prompt 提交、运行结束、需要输入时分别触发脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
 "hooks": {
 "UserPromptSubmit": [
 {
 "hooks": [
 {
 "type": "command",
 "command": "~/.claude/hooks/notify-tmux.sh running"
 }
 ]
 }
 ],
 "Stop": [
 {
 "hooks": [
 {
 "type": "command",
 "command": "~/.claude/hooks/notify-tmux.sh done"
 }
 ]
 }
 ],
 "Notification": [
 {
 "matcher": "permission_prompt",
 "hooks": [
 {
 "type": "command",
 "command": "~/.claude/hooks/notify-tmux.sh input-required"
 }
 ]
 }
 ]
 }
}

对应的脚本只做一件事:根据当前状态在 window 名前面打一个 emoji 前缀,太简单的脚本都不值得展示。但效果就是tmux 状态栏上的 window 名会变成 🔄 agent-foo / ✅ agent-foo / ❓ agent-foo

如前面所说,多个项目同时推进时,每个 session 内部的 Agent 状态就成了一个新问题。同样的思路——让状态在 session list 里直接呈现。这个细节你可能发现我第一张图就有这些信息啦~现在咱们可以安心喝茶、高效监工了:)

后记

用上这套系统后,解决了我原本的很多琐碎的小烦恼:

  • 下班前进行到一半的工作,第二天不知道窗口在哪了。而且我受够了不断确认VSCode的远程重连了,它还慢得要命,它还更新的频繁:(
  • 为了看代码不得不打开“内存杀手“ VSCode/Cursor之流,并且得接受被一些无用的 UI 占据了宝贵的窗口空间。我的 Macbook Pro也只有小小的面积。
  • 还有很多好处,看完全文的你或能有更多体会:)

更要命的是,我的整洁的毛病,分门别类的感觉真好!当然,或许在某些需要浏览大量代码的时候,我还是会再次打开 VSCode 们,但显然它们已经失去我的心了。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号

Vibe Coding 远程开发时,如何优雅地贴图?

2026-04-18 08:00:00

Featured image of post Vibe Coding 远程开发时,如何优雅地贴图?

在直接使用 Code Agent(如 Codex、Claude Code、OpenCode)这些终端工具进行远程开发时,或许你会遇到一个尴尬场景:本地剪贴板里明明有一张截图,远程终端里的 Agent 却看不见。然后你得多走好几步,才能让 Agent 看到这张图片。本文提供一个更快捷的解决方案。

背景

现在用 Code Agent 写代码,虽然咱们有 Playwright / Agent Browser 等工具,但还是难免让它看 UI、看报错截图、看网页风格等。如果是在本机环境,这事还算简单:图片在本机,终端也在本机,Agent 能读到文件就行。但我日常又很依赖远程开发,代码、服务、依赖都在远程机器上,终端通过 SSH 连过去。于是问题来了:

  1. 图片在 Mac 的剪贴板里;
  2. Code Agent 跑在远程服务器上;
  3. SSH 终端只能传字符,不能天然传一张剪贴板里的图片;
  4. 我又不想每次手动保存图片、scp 上传、再复制远程路径。

看起来只是一个很小的工作流问题,但次数多了挺烦人。看着 IDE 中一些临时的截图文件,似乎在提示我,这肯定不是优雅的解决方案。

我想要什么效果?

理想状态很简单:

  1. 截图或者复制图片到本地剪贴板;
  2. 在远程终端里按一个快捷键;
  3. 图片自动上传到远程机器;
  4. 终端里自动粘贴一个远程文件路径;
  5. 我直接对 Agent 说:看这张图,然后继续干活。

简言之:在远程开发中使用支持读取图片路径的 Code Agent 时,获得接近本地开发的贴图体验。

方案探索

方案一:古老的手工活

一开始我们都很朴素:先把截图保存成文件,然后再想办法把这个文件弄到远程机器上。如果正在用 VSCode/Cursor Remote-SSH,就直接把图片拖到远程项目目录里。这个动作不复杂,也足够直观。

在 macOS 下,如果终端使用 iTerm2,而你又在 iTerm2 开启了 shell integration,它支持按住 Option 把本地文件从 Finder 里拖到终端窗口,再上传到远程服务器。这个方案也能用,只是远程环境通常也要把 shell integration 配起来,才能让这条链路顺一点。

虽然现在优化为只要拖拽不用敲命令了,但这个过程还有点繁琐, 更糟糕的是,它很容易污染项目目录。比如为了给 Agent 看一张 UI 截图,随手拖进去一个 image.pngscreenshot 2026-04-18.png,修完问题之后又忘了删。时间久了,项目里就多了一堆一次性的临时图片。你说它影响程序运行吧,也不一定;但每次 git status 看到它们,心情就不太美丽。

所以这个初始方案只适合低频场景。偶尔传一次没问题,天天这么干你最好确保自己没有洁癖,我很难忍受。说是手工活,就是因为每一步都得自己来——保存、拖拽、上传,偶尔还得清理战场。

手工活干多了,自然会惦记着有没有更省事的办法。

方案二:优雅的 IDE 派

后来我找到了 Claudeboard 这个插件,它的定位非常明确:在 VSCode Remote-SSH 场景下,把剪贴板图片上传到远程服务器,并生成 Claude Code 能访问的文件路径。

Claudeboard 插件截图

它的使用方式很直接:剪贴板里有图片后,在 VSCode/Cursor 中按 Ctrl + Alt + V,插件就会上传图片并插入路径。对于 Claude Code 这种跑在远程机器上的 Agent 来说,这就把“本地剪贴板图片”变成了“远程可读文件路径”。

这个方案其实相当不错,基本达到了我们理想状态,并且不需要额外配置。不过要注意,如果你在 Cursor 插件市场里搜不到,也可以从上面 GitHub 官方的 Release 页面下载 VSIX 插件再安装,亲测可用。

如果你的主力开发环境就是 VSCode 或 Cursor,选它没毛病。并且如果你是在 Windows 上开发,那就只推荐这个方案。因为我下一套解决方案更 hack 一些,也仅在 macOS 下验证过。

方案三:极客的终端流

方案二算是优雅了,但如果你像我时不时直接开终端就干活的流派,Claudeboard 这时就帮不上忙了。为了贴一张图切回 VSCode,感觉有点为了吃口醋包顿饺子。

所以继续寻觅方案,然后找到这个脚本: iterm_smart_paste.sh,它可以实现一个快捷键,一键将剪贴板里的图片上传到远程机器,并粘贴一个远程文件路径。

这个方案的核心流程是:

  1. pngpaste 从 macOS 剪贴板里取出图片;
  2. 保存成本地临时文件;
  3. 通过当前 SSH 会话的信息,把图片传到远程机器;
  4. 在终端里粘贴远程图片路径;
  5. Code Agent 读取这个路径下的图片。

这听起来有点绕,但所幸这些事情它都包了,配置好之后,体感就是:复制图片,按快捷键,路径出来了。还是简单介绍一下安装过程:

安装依赖

首先安装 pngpaste

1
brew install pngpaste

它的作用很单纯,就是从 macOS 剪贴板里把图片导出来。没有它的话,我们还得自己手动把截图存成文件,体验立刻回到解放前。

准备脚本

我把脚本放在了 ~/bin/iterm_smart_paste.sh

1
vim ~/bin/iterm_smart_paste.sh

脚本参考这里。不过原版 SSH 会话识别、上传稳定性这几块兼容性不太好,我顺手更新了个版本,可以从 这里 取到。

保存后给它执行权限:

1
chmod +x ~/bin/iterm_smart_paste.sh

绑定快捷键

接下来就是把这个脚本绑定到 iTerm2 的快捷键上。只要这几步:

  1. 打开 Settings/Preferences > Profiles > Keys
  2. 新增一个快捷键;
  3. Shortcut 选择你习惯的组合,比如 Cmd + Shift + V
  4. Action 选择 Run Coprocess
  5. 参数填脚本路径:
1
/Users/你的用户名/bin/iterm_smart_paste.sh

如果你本来就在用 Alfred、Raycast 这类工具,也可以让它们来触发脚本。macOS 层面的热键工具会更灵活一些,但这里咱聊 iTerm2 的方案,就不给你引入其它工具了,内置的 Run Coprocess 也已够用。

使用效果

简单录屏了一下,可以看一眼效果,我还是满意的。

iTerm2 脚本使用效果

这个方案的话,如果长期使用,记得清理一下远程机器上的临时文件。这不用我提醒你吧:)

总结

需要跨平台、多设备使用,并且习惯 VSCode/Cursor 的开发者,借助 Claudeboard 插件的方案二是你的最优解。 如果你是纯 macOS 用户,习惯 iTerm2 终端开发,不妨试试方案三,一个脚本搞定。我用了一段时间感觉挺好。

后记

作为一个有点追求完美的人,我不能接受原始的截图搬运工身份。你要说搞这些能有多少效率提升嘛,那倒未必。但是这种小阻碍解决之后,每次按上快捷键,你了解背后发生了些什么,并且是你让美好在发生,感觉内心安定。或许这也是程序员所追求的一种掌控感?

欢迎大家一起交流一下,你们在远程使用 Codex、Claude Code、OpenCode 时,是怎么处理图片输入的?

最近不打算写太系统性的东西,补几篇文章把工具用得更顺起来。欢迎点赞和关注。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号

OpenClaw 玩家必看:只需几步配置,让你的机器人进化生图能力!

2026-02-25 08:00:00

Featured image of post OpenClaw 玩家必看:只需几步配置,让你的机器人进化生图能力!

最近有一股“龙虾热”,不少人在谈论OpenClaw,大家都在聊怎么用好它。如果你也在使用 OpenClaw,本文介绍一个低成本给你带来情绪价值的方案,AI 女友 Clawra 的威力加强版:)

背景

不知道你用过 Clawra 这个由海外开发者制作的 AI 女友 skill 吗?它的效果不错,但是对于国人使用,它有一些痛点。最主要是它只支持fal.ai这一个平台,免费额度仅支持生成 2 张图片。如果付费的话,一次编辑成本 $0.02,这本身挺便宜,但是充值至少 $10,并且平台充值有一定门槛。再看看咱们国内厂家,单张生图价格也不贵(0.2-0.5元),但经常免费提供了几百上千张图片生成和编辑次数,这可太香了。本文就让我们扩展对接国内各个厂商的模型,并且顺便测下国内模型的效果如何。

最后,如果你也想要个低成本但质量也不低的 AI 女友,放心,我会手把手教你配置完成的。如果你已经安装好了OpenClaw,那对你来说接下来就是个简单任务。

模型选择

我让 AI 推荐了一些国内图片处理效果较好的模型,包括阿里千问、字节 Seedream、腾讯混元 3.0。我选择了其中效果相对较好的模型帮你整理了一下表格:

平台 生图/编辑模型 免费次数/额度 单张预估价格
qwen qwen-image-plus
qwen-image-edit-plus
qwen-image-max
qwen-image-edit-max
各模型免费100张 plus 0.2元
max 0.5元
volc doubao-seedream-5-0
doubao-seedream-4-5
doubao-seedream-4-0
4.x 免费200次
5.0 免费50次
4.0版本0.2元
4.5版本0.25元
5.0 版本 0.22 元
fal xai/grok-imagine-image
xai/grok-imagine-image/edit
几乎没有 约 $0.02
hunyuan aiart/v20221229 SubmitTextToImageJob 各模型50次免费额度​ 0.2 元​
google Gemini-3-Pro-Image-Preview
gemini-2.5-flash-image
没有免费额度 1024分辨率 $0.039
2048分辨率 $0.134

国外的话,除了 fal 平台使用的 xAI 模型外,我们也把 Google 家的 Nano Banana 系列模型带上作为对比对象。这个模型效果确实不错,不过价格要贵上几倍,后面我们再一起对比看看这笔钱值不值。 起初,我尝试直接通过 OpenClaw 远程对接实现了扩展功能,虽然基本可用,但作为程序员,细看其实现代码后发现逻辑略显简陋,缺乏扩展性和层次感,于是我请codex + gpt5.3-codex帮忙重构了一下。

安装配置

接下来我们看一下如何安装我这个扩展版本的 Skill 吧,完整的源代码及使用可以在这里查看。但其实你不必看,打开你的 OpenClaw 机器人的对话框,发出你的指示即可:

请参考 https://github.com/kevin1sMe/clawra-plus 帮我安装这个 Skill。

之后你需要配置上你想使用的平台相关的密钥,比如fal 的 API Key,腾讯混元的SecretId和SecretKey等就可以使用了。这也很简单,只需要在 ~/.openclaw/openclaw.json 中配置下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
 "skills": {
 "entries": {
 "clawra-selfie": {
 "enabled": true,
 "env": {
 "OPENCLAW_GATEWAY_TOKEN": "your_gateway_token",
 "DASHSCOPE_API_KEY": "optional_for_qwen",
 "ARK_API_KEY": "optional_for_seedream",
 "FAL_KEY": "optional_for_fal",
 "GOOGLE_API_KEY": "optional_for_google",
 "TENCENT_SECRET_ID": "optional_for_hunyuan",
 "TENCENT_SECRET_KEY": "optional_for_hunyuan"
 }
 }
 }
 }
}

之后重启 gateway:

1
openclaw gateway restart

之后你在 OpenClaw 的对话框中问诸如,你在干嘛/发个自拍/在咖啡厅、健身房等场所照片,它就会调用相关的模型来生成图片。你可以指定用某个模型,让它记住即可。我们先看一下参考图,因为后续的生图得基于这个形象来编辑。

默认参考图

接下来我们看看效果:

提示词:你在春天花海中的照片

模型 照片
fal-grok-imagine-image
doubao-seedream-4-5
doubao-seedream-5-0
hunyuan-3
qwen-image-edit-plus
qwen-image-edit-max
gemini-3-pro-image-preview

我个人还挺喜欢 fal 上的这个grok模型的,它针对场景人物也会有一些变化,感觉生成画面自带滤镜;在这组样例里,doubao 系列的人物基本保持原样,只是变换了场景?qwen 构图还不错,qwen-max 有点意境——朦胧的暖色调花海;gemini 这个虚化效果还不错,但双持手机的细节有些违和。

哎呀,忘记看看它们的耗时了,咱不仅要效果,太慢也是万不能行的。重新来 PK 一下;这里的耗时统计口径为端到端计时(从发起请求到拿到最终图片 URL),统一输出分辨率,均为热启动且不包含参考图上传时间。
这次生成图片的提示词我们改成养眼些的:

提示词:你在(温泉内)泡温泉的写真

模型 生成耗时 图片
xai:grok-imagine-image:edit 14.9s
doubao-seedream-4-5 32.4s
doubao-seedream-5-0 20.5s
Hunyuan-3.0 11.3s
qwen-image-edit-plus 10.7s
qwen-image-edit-max 24.6s
gemini-3-pro-image-preview 38.2s

可以看到fal的grok模型、hunyuan3.0和 qwen-plus 都比较快,seedream 稍慢一些,gemini 最慢。效果上,这次grok也不错,水波和折射都体现了出来;seedream4.5的这一身衣服,似乎对泡温泉有点误解,而 seedream 5.0的这张让我想到了灵儿在洗澡;hunyuan3.0这张其实挺好,气雾以及身上的水珠等,但这参考图中的围巾你是不是忘记换掉了?qwen-plus 比较写实,但环境有点假;qwen-max有点用力过猛,感觉人物有点不一致了;而这次Gemini 3比较特别和大胆,将人物发型处理过,人也还挺像并且特别真实,整体不错,就是太慢了点。

从这几个结果来看,你觉得哪个模型更好呢?

其它探索

上面的模型如果免费额度用完了怎么办?一种思路是转向本地推理/自托管:把生图能力放到自己的设备上跑,成本更可控,也更踏实。“众所周知”俺有个丐版的 Mac Mini M4,如果能在它上跑生图就更棒了。我知道可以,但效果和时间怎么样呢,我们来试一下。

刚好前阵子研究了一下 ComfyUI,它也支持 API 调用的方式,于是我借助 ComfyUI 来生图,以下是我的工作流,使用了一个 16G 内存能撑下的小模型来进行生图测试。

ComfyUI工作流

随便拉了一个生图流程,使用的是 flux.1-schnell-Q2模型,居然消耗了 1000 多秒,期间内存一度快用爆了。换了 z-image-turbo 等模型,效果也不太理想, 参数大了跑不动:(

我在 RunningHub 平台上还有积分,于是尝试用更大的 flux 做一次图生图对比。 RunningHub运行

好像不太行,或许换一个别人分享的workflow效果会更好点。不过RunningHub即使生图效果不错,没开通会员也不让调用 API,这和我们想要集成到OpenClaw中使用的需求不符。

我也尝试在fal 平台跑 ComfyUI,但是奇怪的是速度慢的出奇,还没有地方看进展等—有点黑盒,这块估计不是这个平台的主要功能,相关交互也很一般,遂放弃。但是如果有一台强悍的本地电脑,比如有不错的 N 卡和较大的显存,这条路或许能通。

闲言

当前在 2026 年,对接这些 API(鉴权、调用、回传 URL、错误重试)已经非常方便。在 OpenClaw 上,你下达命令后,Agent 往往能在几分钟内帮你把事办了。 比如在撰写本文期间,Google 发布了 Banana2 模型,我仅通过手机发出指令,便将其集成并更新到了 GitHub。

要说 OpenClaw 的使用感受,有一点粗浅的启发和思考:

  • OpenClaw 的 skill 及热加载能力,让其自我进化有了空间。有种软件从以前的发布即终止,到现在有了生命,或者说可自我进化。这其实是有质的变化的,但这种变化如何正确的被使用,它可以快速修复一些问题,也可以把自己搞晕。如何更好的管理自己的“版本”,才有持续迭代的地基。
  • OpenClaw 所操作的内容,基于 chat 模式其实有其局限性。想象我们在使用一个助理(我本人没有助理,咱“想象”一下),一个任务的完成涉及多方面的信息,它不只是一个结果。助理可能会有一些信息需要你确认或审批,并在过程中与你沟通细节,助理会在你着急的时候说就快完成了。可是现在龙虾(OpenClaw)不会,甚至不稳定的情况下还要你去再询问一下。简单说,安全性、可视化、可控性等有所欠缺。

还有,目前其实对于稍复杂一些的任务,OpenClaw的完成率还有限,网上各种跑通 XX 的分享及售课等让我感觉像在收割,让我也不确定是自己的问题还是模型或者插件不对,但我更觉得他们在骗人:)因为我已经用了最好的模型了,也对比使用过多种插件了,啊哈。

我觉得这次的龙虾热,其实是 LLM 及 Agent 发展以及 MCP/Skill 等技术陆续铺垫后,这些技术在传统的如 Code Agent之外,于普适的场景下人们突然看到、感受到它的价值而产生的 AI 落地浪潮,或许它离我们心中想象的那个未来的智能助理还有些距离,但至少它呈现了一个信号,像宇宙中突然爆炸的某颗超新星,璀璨、耀眼又明亮让人无法忽视。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号

AI辅助写作新玩法:打造自己的文章审阅+封面生成工具

2025-12-22 08:00:00

Featured image of post AI辅助写作新玩法:打造自己的文章审阅+封面生成工具

我是一个略有强迫症的人,每写完一篇文章,还需要花费不少时间反复检查错别字、调整语句或结构,有时为找个好看的封面图看花了眼。可是都AI时代了,这事不能再忍了,能不能有新解法?如果你也时常有一些文字要处理,不妨看看本文。

背景

我时而写一些技术文章,但一直感觉写作流程挺“原始”的。一般是打开 VS Code 等编辑器,用Markdown写完主要内容,然后自己读几遍修正部分内容,之后就会为如何给文章取个名字、配个图而纠结,这过程往往需要花费不少时间。 文章发布在博客可以随时修正,但若发布到公众号等平台,修改限制非常严格,而我又是对于错字或语句不通等有强迫症的人,每次发个文章心理包袱都很重。

在 ChatGPT 出来后,我有让 AI 帮我检查,但如果你用过,或许也会觉得那个交互方式并不友好,AI给的修改建议都是一股脑扔给你,你得在聊天窗口里上下翻找,对着原文一条条改。在封面图的选择上,过往我会根据主题去 Google 一些图片,一是可能有版权风险,二是不见得合适。现在 AI 生图方便,但生图模型也五花八门,我经常纠结选哪个,提示词也需要来回调整。

本来我觉得文章审阅与修改这种应该可以比较成熟了,可是却苦于一直没找到合适的好用实现 (若你有称手的工具,欢迎留言推荐)。我也不理解如今那些内容平台为什么不在这块提供一些便利。程序员怎么能有这种遗憾呢?于是,我决定利用周末时间自己动手,“丰衣足食”!

我这是 AI+写作吗?其实我讨厌 AI 写作,有些文章看得满满的 AI 味真让人想取关。而我真是一句话交给它都不放心的,它没有我想要的“脾气”,但它打打辅助还是不错的。

需求梳理

如果你是个程序员,有用过 spec-workflow 的标注功能,或许会感觉那个交互还不错。或者像最近 Google 推出的 Antigravity(取的啥鬼名?)的 Plan 及评论,可以直接对着文档进行批注,这样更加直观。对于封面图,希望 AI 来帮我总结文章自己生成一些不同风格的提示词,然后调用不同的模型生成一些候选,让我来选择。于是在动手前,我先梳理了一下核心需求:

文章审阅

  • 智能审阅: 自动检查错别字、语句流畅度、技术准确性,让 AI 来给出一些建议。
  • 交互式批注: 直接在原文标注某一块建议如何修改,不要给我一堆文字让我自己找原文。
  • 可选择性应用: 有了建议后,让我能自己决定接受哪条、拒绝哪条,或者暂时搁置。
  • 差异对比: 修改后要能清晰看到改成了怎样,类似代码的双栏 Diff 那种是我想要的。
  • 多轮迭代: 支持基于修改后的版本继续审阅,不断迭代到自己满意为止。

封面图生成

  • 自动提示词生成: 基于文章内容智能生成生图提示词,避免自己手动写。
  • 风格多样性: 简约风、科技风、赛博朋克风等,AI 来帮我生成一些不同风格的提示词。
  • 多模型赛马: 在不考虑钱包厚度下,同时调用多种生图模型,将结果展示让我挑最满意的。

想起来还不错,还没实现出来,但写完需求都有点开心起来了,啊哈。可能每一步过往都让我难受过,不犹豫了,撸起袖子开干吧!

如果你对技术实现细节和那些经历不感兴趣,想看最后的效果可直接跳到效果展示部分,感谢阅读。

实现细节

我最近一阵子用过一些智能体(Agent)框架,本想着这是不是一个文章 Review 相关的智能体呢?但思前想后,我觉得确定性的 Workflow 可能更合适。具体到技术细节上,我希望支持更多的 LLM 模型,以及支持更多的生图模型。在展示方式上,Web 页面显然是最佳选择,希望有一个不错的交互 UI。这次我没有采用 spec 驱动的开发方式,而是探索式开发,说人话就是走一步看一步。我想看一下在几个目标相对清晰、耦合也很低的情况下,当前 AI 能否完美解决问题。我使用 claude code + Claude SDK 来实现。 在开发初期,为了直抵核心,我将工具设计为通过命令行执行,而结果展示则采用 Web 形式。

审阅页

经过简要沟通后,第一版本的东西出来了,然后只要执行一个命令:

1
2
# 审阅文章
npm run review -- <file>

便调用了后端的 LLM 帮我们审核文章并且弹出修改意见。 AI 的初版Review页 在没有任何风格要求和参考下,AI 的初步网页让我觉得还挺不错的吧:)

接着,我希望批注和原文有个对应关系并且可快速跳转,同时AI 给文章的整体总结、表扬和建议咱们也给它整上,再来一个评分,使用起来可能更带劲了。于是有了这样的效果: AI 的对Review页的补充 我把批注分了不同类型并且编号上,以及不同的处理方式。像我拒绝时,我就有充分的理由:如 AI 老是想让我将一些口语的文字改得非常书面,这时就正告它老子死都不改,这就是本人风格,下次勿提,嘎嘎。

这过程中顺手让Claude SDK支持了调用其它LLM。我不清楚为什么,无论是 Google 的 ADK 还是 Anthropic 的 Agent SDK 都默认仅支持自家模型。可我平时用的是 LiteLLM 代理各种模型,国内国外都有(御三家、doubao、DeepSeek等)。难道要为了这个 SDK 放弃其它模型?显然我是不答应的,绝对不(只)是因为钱包,我暗地里想象对于中文文章,是否可能也许咱国内模型是不是更懂自己一些?(我没有证实)。要使用三方 OpenAI 兼容 API 的话,需要自己扩展。一点不慌,我们将OpenAI的API文档给它,几分钟就搞定了。放代码都嫌占空间,此处省略291行代码:) 不过可能放一下 Prompt 还是有必要的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
你是一位专业的技术文章文章审阅专家。请仔细审阅用户提交的技术文章,并提供全面的反馈。

你需要关注以下几个方面:

1. **语句流畅度**:检查文章是否通顺易读,是否有表达不清或语句冗长的地方。
2. **错别字和语法**:找出所有的错别字、语法错误和标点符号问题。
3. **技术准确性**:验证技术描述是否准确,代码示例是否正确,概念解释是否清晰。因为模型知识的滞后性,对于可能是更新的信息,不要瞎给建议。
4. **阅读体验**:提供改善文章结构、增加示例、优化排版等方面的建议。

请以 JSON 格式输出你的审阅结果,格式如下:

\`\`\`json
{
 "overallScore": 8,
 "fluency": {
 "score": 8,
 "issues": [
 {"location": "第2段", "original": "原文", "issue": "问题描述", "suggestion": "建议"}
 ],
 "suggestions": ["整体流畅度建议1", "建议2"]
 },
 "typos": {
 "count": 2,
 "items": [
 {"wrong": "错字", "correct": "正确", "context": "上下文"}
 ]
 },
 "technicalAccuracy": {
 "score": 9,
 "issues": [
 {"location": "代码块1", "issue": "问题", "severity": "medium", "suggestion": "建议"}
 ],
 "suggestions": ["技术建议1"]
 },
 "readabilityTips": [
 {"type": "structure", "suggestion": "建议内容", "priority": "high"},
 {"type": "example", "suggestion": "添加示例", "priority": "medium"}
 ],
 "summary": "总体评价,包括文章的优点和需要改进的地方",
 "uiSuggestions": [
 {
 "id": "s1",
 "type": "clarity",
 "severity": "medium",
 "highlight": { "start": 120, "end": 168 },
 "excerpt": "被高亮的原文片段",
 "issue": "问题描述",
 "suggestion": "修改建议",
 "status": "pending",
 "remark": "可选,预留给用户的备注"
 }
 ]
}
\`\`\`

注意:
- 每个方面的评分范围是 1-10
- severity 可选值: low, medium, high
- type 可选值: structure, example, visual, clarity, engagement
- priority 可选值: low, medium, high
- 如果某个方面没有问题,对应的 issues/items 数组可以为空
- summary 应该是一段完整的文字评价
- uiSuggestions 使用高亮范围锁定原文,start/end 必须指向原始 Markdown 字符串的位置,避免越界;若无法定位,请省略该条。

让我们执行这个审阅,你可能像我一样惊讶的发现,哪怕自己已经在完稿后读过几轮了,但交给 AI审阅后,还能发现各种类型的错误。就像错别字或者笔误这种低级的也时不时成为漏网之鱼。恭喜你,看了这篇文章或许你以后不用太烦恼了,前提是你也用起来:D

问题到此还没结束,我发现每次在应用审批意见到唤出差异页面(下文会讲),用时都比较久,如下: Diff页-应用审批意见慢 我上一篇文章在做实验,DeepSeek 要 3 分钟才完成,即便使用gemini-3-pro也需要 1 分钟,这对我的耐心是个考验。幸好我们上面将 LLM 访问对接到 LiteLLM了,我看了一下这个token量就意识到优化方向了,因为让模型输出修改后的全量文本时,这块受限于Token 速率,自然会消耗很多时间,通过将 LLM 的输出从全量文本生成优化为仅输出增量修改建议(Diff 模式),处理速度提升了一个数量级。

Diff页

审阅完成后,基于我们的建议,会再次调用 AI 来优化我们的文章,这里感觉有必要呈现一个改前和改后的Diff 页,可以直观对比修改前后的差异。

1
2
# 查看diff
npm run diff -- ./readwise.md ./readwise.revised.md

支持使用下划线: Diff页-下划线 或高亮显示: Diff页-高亮

在 Diff 页面中我们如果满意了就可以收工,如果还有不满或想 AI 再查一次(你是个谨慎的人),可以点击继续审阅,将以最新的结果为基础,继续下一轮迭代。

封面图

最近一段时间Google Banana Nano 好像火爆了,以前 GPT-image-1出现也惊艳了一大波人,国内的豆包/智谱/通义千问/万相等也不是不能用,我之前有一篇文章试图做一个一站式的绘图平台 Paintbot-Hub,里面也集成了不少模型。我希望看到国内模型在这个场景下的一些表现,所以这次封面生成,大家来个赛马,多模型一起上,让我来挑最满意的。

生成提示词这块,基于我们的技术文章背景,我大概是这么写的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
你是一位专业的技术文章封面图设计专家。你的任务是根据文章文章的标题和内容,生成用于 AI 图像生成的高质量提示词,并同时输出「英文版提示词」和「中文提示词」。

你的工作内容:
1. 分析文章标题和正文,提取核心主题与关键技术概念。
2. 根据主题构思合适的“视觉隐喻”(如数据流、节点网络、信息流动、几何结构、算法纹理等)。
3. 设计适合技术文章封面的创意构图,包括元素、色彩、光影、风格。
4. 最终输出两套提示词:**英文提示词**(专业英文) 和 **中文提示词**(自然流畅中文)。

提示词要求:
- 提供两个版本:英文 / 中文
- 控制在 50–150 词(英文)或 80–200 字(中文)
- 描述清晰的视觉场景、构图方式、色彩风格、氛围
- 封面图适合横幅比例(约 900x383)
- 尽量保持科技感、现代化、专业性
- 不出现真实人物、品牌 Logo、具体公司的 UI 图形
- 尽量使用抽象、几何、科技感视觉元素

视觉风格可选(但不必强制):
- tech-minimal:简约科技风
- gradient-abstract:渐变抽象风
- illustration:插画扁平风
- photo-realistic:写实科技风
- flat-design:极简扁平风
- cyberpunk:霓虹赛博风
- nature-blend:自然 + 科技融合

输出格式(严格按照以下结构输出):

1. 《主题理解》:简述你对文章视觉主题的理解(中英文)
2. 《英文提示词》:用于图像生成模型的详细英文提示词
3. 《中文提示词》:用于中文模型的详细中文提示词
4. 《风格修饰语》:可选的风格关键词(中英文)
5. 《负面提示词(可选)》:避免出现的元素,如 no humans, no logo, no text clutter

你必须严格按上述结构输出,使生成的提示词稳定、可控、适用于主流文生图模型。

我怕有些模型对于语言比较挑剔,所以中英文版本都生成了,咱赛马也要讲究公平公正对吧。接着是对一些图片模型的API 支持,看起来主要是两种:

/images/generations 端点:

  • azure/gpt-image-1
  • gemini/imagen-4.0-generate-001
  • gemini/imagen-4.0-fast-generate-001
  • gemini/imagen-4.0-ultra-generate-001

/chat/completions + modalities 端点:

  • gemini/gemini-3-pro-image-preview
  • openrouter/openai/gpt-5-image

此外,一些厂商的模型(如通义万相、千问等)提供了独特的异步接口或与主流不同的API规范,需要单独适配。但只要其API文档清晰,实现集成也并非难事。

提示词生成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
kevin@ser5-dev:/data/article-reviewer$ npm run image:prompt -- 20251208-3/wubi.md --style tech-minimal --count 2

> [email protected] image:prompt
> tsx src/cli.ts image prompt 20251208-3/wubi.md --style tech-minimal --count 2


📄 处理文章: AI 辅助编程实战:从零训练验证码识别模型,打造五笔查询 Workflow

💡 开始生成图片提示词...

🤖 正在调用 LLM 分析文章...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 生成的提示词
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1] 《主题理解》:
 - 英文:The article details a practical AI-assisted programming project: training a captcha recognition model from scratch to build a Wubi input method query workflow. The core visual themes are "AI-assisted programming," "captcha recognition model training," and "workflow automation." The challenge involves overcoming a captcha barrier to access data, symbolizing problem-solving and the intersection of AI (OCR/Deep Learning) with traditional coding logic.
 - 中文:文章详述了一个AI辅助编程实战项目:从零训练验证码识别模型,以打造一个五笔查询工作流。核心视觉主题是“AI辅助编程”、“验证码识别模型训练”和“工作流自动化”。其挑战在于突破验证码障碍以获取数据,象征着问题解决以及AI(OCR/深度学习)与传统编码逻辑的交汇。

[2] 《英文提示词》:
 A tech-minimalist banner illustration for a programming blog. The scene is divided into two conceptual halves. On the left, a clean, abstract representation of a captcha image with distorted digits is being decoded by a subtle, flowing stream of blue data particles and geometric neural network nodes, symbolizing AI recognition. On the right, a sleek, minimalist flowchart or pipeline emerges, composed of glowing lines and nodes, transforming into elegant Chinese character radicals (like 扌, 氵, 口) and finally into a clean, modern keyboard interface. The background is a deep charcoal grey with a subtle gradient. The overall atmosphere is calm, focused, and intellectually stimulating, using a limited palette of cool blues, clean whites, and accents of soft cyan for the data flow. Ultra-wide banner composition, sharp focus, clean lines.


✅ 提示词已保存到: 20251208-3/wubi-prompts.txt

📌 下一步:使用以下命令生成图片:
 npx tsx src/cli.ts image gen 20251208-3/wubi.md --prompt-file 20251208-3/wubi-prompts.txt

接着是图片生成:

1
2
3
4
# 生成图片(多模型赛马)
npm run image:gen -- ./my-article.md \
 --prompt-file ./my-article-prompts.txt \
 --models xx

AI 给了设计了一个大概这样的界面用于呈现生成结果: 封面图片生成结果预览

这种"赛马"的感觉还挺有意思,就像抽卡游戏,每次都有点惊喜:D。 不过到这里虽然基本功能实现了,但优化永无止境。

最终效果

如上所见,我们的界面还挺简陋原始的,更别说统一性了,而且还需要结合命令行使用。这绝对不是我能停下来的标准,我想做一点改进:

  • 完全基于Web页面,而不是命令行,降低使用者的门槛。
  • 重新设计各个界面的UI,增加统一性。
  • 增加一些交互优化,如点击图片可以放大查看,提示词可以自定义等。

我想起来之前体验过 Stitch,一个挺有意思的AI辅助设计工具。于是将一些页面通过描述重新生成UI, 之后将其导出为代码(包括 HTML 以及图片等,我记得之前好像还可以导出到 Figma),我们就很容易将其集成到项目中。

最终我们的效果:

首页

审阅页

批注页

Diff页

封面图片生成

现在我们使用时,启动后打开网页,后续所有操作都可在网页中完成了。上面的封面图就是对于本文让模型生成的,从十几张里面挑选还是有几张不错的,不用纠结犹豫了。

注:使用国产生图软件,有时会报错,比如 DashScope HTTP 400: {“request_id”:“0f57adce-a309-4e62-a693-40cfbadf841c”,“code”:“DataInspectionFailed”,“message”:“Input data may contain inappropriate content. For details, see: https://help.aliyun.com/zh/model-studio/error-code#inappropriate-content"},这是因为模型对内容敏感,可能你只是想生成一个技术相关的封面图,但模型认为你内容有敏感信息,所以报错。你可以尝试调整提示词,或者换一个模型试试。

其它技巧

因为工程主要是基于 Claude Code开发,而且是基于Remote-SSH远程开发,对于有 UI 交互的场景,经常要贴图告诉AI问题所在。以往要把图片保存本地再上传(或拖动),再于命令行引用,这和本地开发相比或者相较于 Cursor 等非常不方便。我找到了一个插件Claudeboard插件,可以直接通过快捷键将剪切板的内容直接贴到远端的cc中,这样方便多了。

其次,因为涉及到网页的开发调试,如果你需要来回搬运错误或手工测试,显然我们不想充当这样的角色。你可以通过Playwright来实现自动化验证。只要安装playwright相关的 MCP即可。

还有,上面我们需要对接各种 API 要查看文档,你即使安装了context7,对于某些文档可能还是要搜索网页,这种情况下firecrawl这个 MCP 对于网页的抓取还是会更准确一些。

后记

到这里这次折腾基本就结束了,感谢你的阅读。

本文提到的工具已经开源在GitHub:article-reviewer,你要是喜欢欢迎试用。之所以我要从命令行改为完全 Web 驱动,也是希望若你有兴趣可以方便快速上手。希望对你有帮助,请记得给个 Star 以示鼓励哇~ 后续我的文章和封面就靠各路 AI 模型和这个工具了。所以我可能还会持续迭代添加一些功能,如果你有好的想法也欢迎提 PR 或 Issues。

我也在想当前是否封装为 Claude Skills 等扩展也适用于完成此项工作,但以当下对于调用 Skills 的前置工作,多数人还是不熟悉,或许当前阶段这个形式更适合。而对于我完整写完一篇文章并发表的流程来说,还有几个未了事:自动化发布以及一些主题样式的配置。本文我们先把文章妥妥准备好,其它未来再考虑咯!

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号

AI 辅助编程实战:从零训练验证码识别模型,打造五笔查询 Workflow

2025-12-08 08:00:00

Featured image of post AI 辅助编程实战:从零训练验证码识别模型,打造五笔查询 Workflow

背景

在 AI 时代,自然语言处理已经是我们获取信息的“第一工具”。我作为一个接触电脑较早的人,以及更重要的是拼音拼不准的南方人,自然五笔输入法是我的首选。如果没了五笔,我感觉自己几乎不会打字。虽然日常输入熟悉的汉字很快,但一旦遇到“提笔忘字”或不知道如何拆解的生僻字,思路往往会被打断。即便有些输入法可以反查五笔,但有时你仍然不知道它为何这么打。

为了解决这个痛点,周末闲着无事开发了个 Alfred Workflow,实现快速查看某个字是如何打的并且显示五笔拆字过程,方便真正理解你为何会打不出这个字。 当然作为技术文章,我更想分享的是:在 AI 的帮助下,我是如何突破开发过程中的重重困难的,而非 Workflow 本身。

效果展示

 肃字在alfred-workflow 中查看

你还可以修改一下 里面的命令使其打出其它编码是如何拆解的。只需要修改过滤展示(–only 参数)

alfred_wubi.py 支持通过 --only 选择要展示的字段,逗号分隔(不区分大小写):

  • summary:总体汇总行
  • 编码:num5, num6, num9, wb86, wb98, wbx (新世纪), strokes
  • 拆解图片:num6_parts, num9_parts, wb86_parts, wb98_parts, wbx_parts

分别是:王码 5/6/9 键、五笔 86/98/新世纪、笔画序列。

示例: 当前默认展示五笔 86 编码及其拆解:

1
python3 alfred_wubi.py "{query}" --max-retry 5 --cache-dir alfred_cache --only wb86,wb86_parts

如果你是五笔输入法爱好者,只是想使用这个插件,那么跳到文章后面部分即可找到相关链接。接下来我会讲一下技术实践啦。

调研与选型

在开始开发前,我调研了现有的开源方案,发现 alfred-wubi-workflow 已经是 6 年前更新的仓库,其依赖的 chaiwubi.com 也已无法访问。经过搜索网站,我找到了两个可用的五笔反查源:

  1. 打字吧 (http://www.daziba.cn)
  2. 王码官方 (http://www.wangma.com.cn)

王码官方的数据显然更权威,不仅有 86/98 版,还有新世纪版和数字王码。考虑到刚才那个仓库依赖的网站已不可用,我就打算傍个大款,就选择王码了。但它有一个巨大的拦路虎:查询时必须输入 4 位数字验证码。

这意味着,想要集成在工具中自动化查询,必须先破解它的验证码。

梳理整体逻辑

我将需求以及关键截图告诉 AI:

1
› 我在学习五笔输入,有这样一个网站,用于一些汉字的输入拆解,你帮我查看和分析一下它的结构:http://www.wangma.com.cn/query/wmhz1.asp。 我想做一个alfred 的workflow,方便我可以直接在本机操作并返回某些字是如何拆解的。比如我输入后提交有第二图,这里面的王码五笔 86/98 版的输出是我关注的,我应该如何实现。我注意到这个网页在查询时会要求输入验证码,可能是个图片,你需要解决这个限制

AI 基于网页进行自动分析,并给出了它的分析结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
• 这里把页面和请求流梳理好,并给出在 Alfred 里可复用的流程(含验证码处理思路)。

 页面结构与提交参数

 - wmhz1.asp:表单 method="post",action="wmhz2.asp",charset=GB2312;字段 query_hz(要查的汉字,最多4)、yanzhengma(验证码)、ok(提交
 按钮文字,可随便填)。
 - 验证码图片:<img src="/include/v.asp">,刷新按钮只是重新请求此 URL。返回的是 40x11 的 4-bit BMP(调色板颜色很少,噪声极轻),验证码长
 度 4。
 - 会话:首次 GET 会下发 ASPSESSIONID…,同一 cookie 必须贯穿“取验证码”与“提交表单”。
 - 错误提示:验证码错 -> “验证码错误。”;未填/汉字非法 -> “指定汉字错误。”。

 查询时的网络顺序

 1. GET http://www.wangma.com.cn/query/wmhz1.asp(存下 cookie)。
 2. GET http://www.wangma.com.cn/include/v.asp(同 cookie)拿到 BMP。
 3. OCR 出 4 位验证码。
 4. POST http://www.wangma.com.cn/query/wmhz2.asp,Content-Type: application/x-www-form-urlencoded,编码用 GB2312,带上 cookie(最好带
 Referer: .../wmhz1.asp)。
 5. 返回页是 HTML,使用 GB2312 解码后解析出 86/98/新世纪等编码段即可。

这个处理流程细节满满,感觉它对这个网站的分析已经很透彻了,将这个过程保存为文档,后续交给 AI 作为实现的上下文。

核心挑战:攻克验证码

这是整个项目最核心,也是最有意思的部分。为了解决这个 4 位数字验证码,我尝试了四种方案,见证了从“传统算法”到“深度学习”的降维打击。

方案一:模板匹配

最直觉的想法,当然也是 AI 提议下,我下载了一批验证码(20 个),切分出 0-9 的数字图片作为“模板”。识别时,拿目标图片去和模板逐一比对。

相关代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 def solve(self, im):
 # 1. 二值化 & 降噪
 mask = binarize(im)
 # 2. 连通域切割 (Segmentation)
 comps = connected_components(mask)
 
 result = ""
 for bbox, _ in comps:
 # 提取字符图像并归一化
 norm_comp = normalize_component(comp_mask)
 
 # 3. 暴力比对所有模板 (Hamming Distance)
 # 基于 Hamming 距离的网格匹配
 best_digit, min_dist = None, float('inf')
 for digit, templates in self.templates.items():
 for tpl in templates:
 dist = hamming(norm_comp, tpl)
 if dist < min_dist:
 min_dist = dist
 best_digit = digit
 result += best_digit
 return result

刚开始用这个方法跑了一下,我在默认重试 5 次后,发现还是会出现有些验证码过失败了。仔细查看发现这个方案的一些问题:

  1. 对于旋转/位移敏感:只要数字稍微歪一点,模板匹配就会匹配错误。例如 把 3 认成了 0(正确=9301, Template=9001)。
  2. 相似度陷阱:38 极其容易混淆,68 也是高频混淆对。

方案二:使用 EasyOCR

为了验证方案一的准确率,我想引入另一个 OCR 来校验一下。于是我接入了流行的开源库 EasyOCR

1
2
3
 import easyocr
 reader = easyocr.Reader(['en'])
 result = reader.readtext('captcha.bmp', detail=0)

使用虽然简单,它需要下载一个比较大的模型,比我们上面的模板匹配要好一些,但整体感受还是不理想。比如EasyOCR 经常把背景噪点强行识别为数字,导致多读位数。例如真实是 1889,EasyOCR 读成了 18889,多读了一位,可能是噪点被当成了8。再比如0 经常被误读为 86

方案三:Tesseract

看起来这个 EasyOCR 可能对我们这个专属场景并不太合适,AI 又建议我使用老牌 OCR 引擎 Tesseract。

1
2
3
4
5
 import pytesseract
 from PIL import Image

 # 简单的直接调用
 text = pytesseract.image_to_string(Image.open('captcha.bmp'), config='--psm 7 digits')

这个结果更差,有不少图片无法识别,更别说准确率了。即使识别出来,准确度也很低,偶尔能出几个正确的。说来也神奇,它倒是有些能把 EasyOCR 认错的给整对,我也是奇了个怪。

方案四:训练自己的 CNN 模型

到这里我有点生气了,为何看似这么简单的验证码,这几个方法的效果都不尽人意呢?!

在 LLM 的建议下,我决定“杀鸡用牛刀”:训练一个专门识别这 4 位数字的卷积神经网络(CNN)。 有多个强大的 LLM 作为编程伙伴,以前想都不敢想的训练自己的模型,现在 闭眼冲 (自信又莽撞)了 :P

1. 训练数据从何而来

数据当然是直接从网页拉取,这里主要说数据的标注过程。 前面的模型或方法虽然弱, 但好歹能够识别出一些信息,我不想手动去标注几百张图,那就让两个弱模型(Template Matching 和 EasyOCR)打打工,由它们初步标注出数据,我在对有异议的内容进行人工纠错。

后续随着我们自己的 CNN 模型也具备一定能力,让它也参与标注,我们实际手工要修改的数据越来越少。看 100 张图实际也就 1-2 分钟搞定。

2. 模型设计

LLM 帮我生成了一个轻量级的 CNN 结构 (PyTorch),核心还是经典的“卷积提特征 + 全连接分类”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class SimpleCNN(nn.Module):
 def __init__(self):
 super(SimpleCNN, self).__init__()
 # 3层卷积提取特征
 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
 
 # 4个独立的输出头,分别预测4个位置的数字
 self.fc_digit1 = nn.Linear(256, 10)
 self.fc_digit2 = nn.Linear(256, 10)
 self.fc_digit3 = nn.Linear(256, 10)
 self.fc_digit4 = nn.Linear(256, 10)

 def forward(self, x):
 # ... 卷积 + 池化 ...
 x = x.view(x.size(0), -1) # Flatten
 # ... 全连接 ...
 # 输出4个结果
 return self.fc_digit1(x), self.fc_digit2(x), self.fc_digit3(x), self.fc_digit4(x)

训练时的 Loss Design:因为有 4 个输出,Loss 也是 4 部分的总和:

1
2
3
criterion = nn.CrossEntropyLoss()
# 计算每一位的 Loss 并相加
loss = criterion(out1, label1) + criterion(out2, label2) + \\\n criterion(out3, label3) + criterion(out4, label4)

同时准备好训练脚本,我们只要指定数据集路径,就能开始训练:

1
python3 train_model.py <train_data_path1> <train_data_path2> ... <train_data_pathN>

3. 训练过程

本着边跑边看,我分了多轮进行训练,这样我们的标注也会越来越准确。

  • 第一轮:先抓取 20 张图,让 Template Matching 和 EasyOCR 同时跑,人工校对后,得到了这三个方案的准确率:
    Model Accuracy Correct Total
    Template Matching 100.0% 20 20
    EasyOCR 65.0% 13 20
    Tesseract 20.0% 4 20

然后基于这个训练了第一版 CNN 模型。

  • 第二轮:基于第一轮的模型,再抓取 20 张图, 这样加前面共 40 张图,让 CNN 模型和其它方案一起跑,得到四个方案的对比数据:
Model Accuracy Correct Total
EasyOCR 75.0% 15 20
Template Matching 50.0% 10 20
Tesseract 25.0% 5 20
CNN (Round 1) 5.0% 1 20

可以看到只经过第一轮训练的模型( 20 张图片),识别效果很差,只有 5.0%。 我们 将这20 张图加入训练集,继续训练第二版CNN 模型。

  • 第三轮:再抓取 60 张图(为了凑个 100),再次让 CNN 模型和 其它方案同时跑。
Model Accuracy Correct Total
EasyOCR 60.0% 36 60
Template Matching 51.7% 31 60
CNN (Round 2) 33.3% 20 60
Tesseract 21.7% 13 60

已经看过 40 张图的 CNN 模型正确率已经从 5% 上升到 33.3%。然后让它继续学这 60 张图:

1
python3 train_model.py captchas test_verification new_batch_60

现在看了 100 张图,CNN 模型会怎么样呢?

  • 第四轮:基于第三轮的模型,再抓取 100 张图,再次对比:
Model Accuracy Correct Total
CNN (Round 3) 83.0% 83 100
EasyOCR 60.0% 60 100
Template Matching 45.0% 45 100
Tesseract 16.0% 16 100

惊喜,居然从 33.3% 上升到 83.0%。这才看了 100张图呢,我这不又准备了 100 张素材,我有预感,似乎宝剑就要锻造出炉了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
python3 train_model.py captchas test_verification new_batch_60 batch_100_test

Found 20 images in captchas
Found 20 images in test_verification
Found 60 images in new_batch_60
Found 100 images in batch_100_test
Found 200 valid images for training.
Debug: First 5 labels in dataset:
 captchas/vc0015_5279.bmp -> [5, 2, 7, 9]
 captchas/vc0009_9085.bmp -> [9, 0, 8, 5]
 captchas/vc0013_6504.bmp -> [6, 5, 0, 4]
 captchas/vc0014_6842.bmp -> [6, 8, 4, 2]
 captchas/vc0006_1883.bmp -> [1, 8, 8, 3]
Training on device: mps

Starting Training...
Epoch [20/300], Loss: 0.0400, Acc: 100.00%
Epoch [40/300], Loss: 0.0077, Acc: 100.00%
Epoch [60/300], Loss: 0.0038, Acc: 100.00%
Epoch [80/300], Loss: 0.0019, Acc: 100.00%
Epoch [100/300], Loss: 0.0012, Acc: 100.00%
Epoch [120/300], Loss: 0.0009, Acc: 100.00%
Epoch [140/300], Loss: 0.0006, Acc: 100.00%
Epoch [160/300], Loss: 0.0005, Acc: 100.00%
Epoch [180/300], Loss: 0.0003, Acc: 100.00%
Epoch [200/300], Loss: 0.0003, Acc: 100.00%
Epoch [220/300], Loss: 0.0002, Acc: 100.00%
Epoch [240/300], Loss: 0.0002, Acc: 100.00%
Epoch [260/300], Loss: 0.0001, Acc: 100.00%
Epoch [280/300], Loss: 0.0001, Acc: 100.00%
Epoch [300/300], Loss: 0.0001, Acc: 100.00%

Model saved to captcha_cnn.pth

现在这个模型已经经过了 200 张图的训练,让我们拭目以待!

  • 第五轮:我拉了全新的 100 张测试集,让模型再跑一次,这一次我没有标注,我将 CNN 和其它不同模型的识别结果对比,看下结果。Status 列中的状态是以 CNN 的角度来看和其它模型的差别:
  • ✅ Trusted:模型和至少 2 个其它模型一致,可信。
  • ❌ Unique:模型和所有模型都不一致,需要确认。
  • ❓ Possible:模型只和其中一个一致,需要人工判断。

以下节选一部分:

File Original Template EasyOCR Tesseract Custom CNN Status
vc0000.bmp 0000 8681 8681 8684 8681 ✅ Trusted
vc0001.bmp 0001 6560 6560 6568 6560 ✅ Trusted
vc0002.bmp 0002 9001 93041 9304 9301 ❌ Unique
vc0003.bmp 0003 4710 4710 7418 4710 ✅ Trusted
vc0004.bmp 0004 5325 5822 5225 5225 ❓ Possible
vc0005.bmp 0005 0489 0489 0489 ✅ Trusted
vc0006.bmp 0006 6918 6998 6998 6998 ✅ Trusted
vc0007.bmp 0007 3643 8648 8648 ❓ Possible
vc0008.bmp 0008 3626 6626 6626 6626 ✅ Trusted
vc0009.bmp 0009 0602 0602 8682 0602 ✅ Trusted
vc0010.bmp 0010 2458 2747 2453 2453 ❓ Possible
vc0011.bmp 0011 5001 5381 5384 5381 ❓ Possible
vc0012.bmp 0012 8467 84767 8467 ❓ Possible
vc0013.bmp 0013 8531 0541 3534 3531 ❌ Unique
vc0014.bmp 0014 7157 9197 9197 ❓ Possible
vc0015.bmp 0015 4360 4360 4366 4360 ✅ Trusted
vc0016.bmp 0016 9769 974 9709 9769 ❓ Possible
vc0017.bmp 0017 9215 0275 0275 ❓ Possible
vc0018.bmp 0018 2025 2025 2825 2025 ✅ Trusted
vc0019.bmp 0019 3504 8524 8524 ❓ Possible
vc0020.bmp 0020 8690 3698 32698 3698 ❓ Possible

不看不知道,一检查吓一跳,这些CNN 和其它模型不一致的结果中,惊人的发现————CNN 识别的正确率高达 100%,为了确认不是花眼,我再次检查了这 100 张图,完全正确!

Model Accuracy Correct Total
CNN (Round 4) 100.0% 100 100
EasyOCR 63.0% 63 100
Template Matching 38.0% 38 100
Tesseract 17.0% 17 100

这意味着,在识别这个网页的验证码这个特定任务上,我们的小模型已经“超神”了。它不仅完爆了 Template Matching,也大幅超越了 EasyOCR,而整个模型文件只有 7.9MB 大小。 这或许就是我们自己的领域专用视觉模型了。 有了它我很有信心可以将验证码的识别重试次数从 5 改到 1,咱不太可能失败了,哈哈,满意的笑了。

组装 Workflow

攻克了验证码,剩下的就是组装一下 Python 胶水代码给Alfred Workflow调用了。 AI 火速编写了 alfred_wubi.py,流程如下:

  1. 请求页面1:获取 Session 和验证码图片。
  2. 模型推理:加载我的 captcha_cnn.pth,瞬间识别出验证码。
  3. 构造 Payload:带上验证码 POST 请求页面 2。
  4. 解析 HTML:用 BeautifulSoup 提取五笔编码和拆字图 URL。
  5. 输出:JSON 格式给 Alfred 显示。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 核心代码片段
from cnn_inference import CNNInference

# 加载模型 (仅需一次)
predictor = CNNInference(model_path="captcha_cnn.pth")

# 预测
image_path = Path("temp_captcha.bmp")
code, conf = predictor.predict(image_path)
print(f"识别结果: {code}, 置信度: {conf}")

# ... POST request with code ...

这里对于 Python 库的依赖,我们可以使用.venv 来管理,避免和系统环境冲突。 你可以这样使用这个项目:

1
2
3
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

然后使用 .venv/bin/python 来运行 python alfred_wubi.py

后记

本文所提到的 Workflow 以及训练整个过程及数据已经开源在GitHub,你可以在这里查看:https://github.com/kevin1sMe/alfred-wubi-workflow。

文章到这里就是尾声啦,这其实是周末折腾其它过程中的小插曲,感觉挺有意思给大家一起分享下。现在我们打造的这个五笔反查工具,能够让我们告别某些生僻不会打的字。就像我前面截图中,“肃”字你会打吗?更关键的是通过这次折腾,让我更深刻感受到,在 AI 辅助编程的加持下,个人开发者解决问题的边界被极大地拓宽了

以前遇到“需要训练一个模型来识别验证码”这种需求,可能因为觉得门槛太高就放弃了。但现在:

  1. 代码:LLM 帮我写好了 CNN 结构、帮我准备各种脚本,配合我一步步训练。
  2. 数据:LLM 帮我设计了自动化的标注脚本。

我只需要专注于聊清需求、定义问题和决策方案。从零开始到模型落地,只用了短短1-2个小时,这或许就是 AI 时代的编程范式吧。但是要整理出本文,为了将过程数据完整重现,我又重新一步步跑了一次,严谨的记录和对比了各个轮次的效果,再加上代码开源及整理,这里居然花了周末的一整个晚上。看在这么认真的份上,小手点个关注、点个赞不过分吧:)

持续折腾,不断进步,我们一起加油。期待下次有机会继续分享更好玩的内容,再会!

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号

微信公众号文章自动收藏:Readwise + 自动化抓取完整方案

2025-10-29 08:00:00

Featured image of post 微信公众号文章自动收藏:Readwise + 自动化抓取完整方案

你是否像我一样,在日常碎片化时间中翻看公众号,即便遇到不错的文章,但是往往没时间深入细看,或者看过却回头就忘记了?仿佛我们看了个热闹而已,有多少内容转换为有效的输入了呢?本文分享如何用 Readwise Reader 打造个人知识管理系统,实现公众号文章的自动抓取、智能分类和深度阅读。

背景

日常生活中,关于知识的更新,除了一些主动订阅的 Feed 流外,公众号的一批还不错的文章时常能让我扩大一些视野。有人说在短视频热之后,以往做公众号自媒体的一批人切过去了,仍然坚持留下来继续写公众号的人,反而让这块天地有了更纯粹和更高质量了,事实是否如此我不知道。我自己也写公众号,不求有多少人关注,也不想跟随热点,但愿能以文会友,交结到一些志同道合的朋友。

在我日常吃饭、如厕等时候,会翻看一些公众号,有一些较长的文章或者技术密度高的文章,为了不错失某些内容,我常将它悬浮可以随时调出来细读,但咱不明白微信为何限制只能稍后读5篇文章,并且几篇文章的切换和操作体验极差。

有些适合深度阅读的好文,它和我们日常使用手机的方式是冲突的。我个人更喜欢在电脑上,或者在专用软件上查看。我们可以划线留言,可以高亮备注。或许这样,先前的"看过"才不至于流于形式。

Readwise & Reader简介

有个同事给我推荐了 Readwise,我发现它非常好地命中了我的诉求。它的功能非常简单,但却超级实用,如同它的宣传语:

Get the most out of what you read Readwise makes it easy to revisit and learn from your ebook & article highlights.

这个方案解决了我们信息获取和处理的几个关键点,我称之为串联或连接。

Reader主界面

比如,它有浏览器插件,安装后我们可以方便地对正在阅读的网页内容划线、高亮或备注,这一切就会进入到你的 Readwise 账号中。还有一个副产品Reader,我觉得更赞的是,它支持将某个 URL 自动抓取,支持播客,支持传统 RSS,还支持youtube等视频直接在Reader 中观看,字幕同步。当然还可以导入 PDF、EBOOK等,在这里进行更深入的阅读和处理。 在 Reader 中观看youtube

这样,信息的获取和快速处理便解决了。接下来工具不止于此,Readwise 除了提供我们划线或备注的 Review 功能后,更是可以将我们的这些内容与其它外部工具结合,比如相关内容自动同步到 Notion 中。 Readwise笔记等自动导出到Notion 不止是 Notion,几乎市面上常见的主流的笔记软件它都可以导出,像印象笔记、Obsidian甚至 Apple Notes。还有热门的 MCP 等都支持,我只能惊叹。

但显然这是一篇技术文章,不是做广告。毕竟 Readwise出身于国外,互联网平台的技术封锁没有那么强,这个东西好归好,在国内却会有点水土不服了。比如微信公众号文章,你会发现给它链接后,它可能抓取不完整或者缺图片等,怎么办呢?继续往下看呗!

方案一:成熟的第三方工具wechat2reader

咱不是爱造轮子的人,遇上问题当然搜索过。找到这个工具,试用了一下效果还不错。 使用第三方Wechat2Reader工具

但我发现好像也有一些小问题,比如摘要不合理,有一些较长的文章,它不是对全文的摘要,只是文章部分内容的复述。其次标签不准确或者没有,对于快速 Get 文章主要内容的人,有时会观察一下相关标签,如上文,似乎没有生成任何标签,我推测是原文没有标签,所以在抓取保存入 Reader 后也没有标签。最后,它还是比较人性化地提供了 15 天免费试用的机会,但再想要使用,需要支付一定费用(好像年费 45 元,我已经付过表示支持作者)。当然如果你格外介意隐私,可能也会有一些小担心。

那么,作为一个程序员,是时候自己动手来解决这个问题了。

方案二:自动化抓取和导入到 Reader

我希望对于这些文章的抓取是离线的,是Headless的。我的整体思想是先用成熟的组件自己组装一个相关功能,再考虑局部优化。整体交互希望和方案一类似的比较简单直接。

1. 文章抓取和图片处理

之前用过一阵firecrawl MCP来抓取网页,能力还不错,所以我先用它来抓取。在实践之前,咱们先基于MCP来测试一下效果。

Firecrawl 抓取结果示例

1
2
3
目前这个项目在 GitHub 上已经收获了 **39.8k star**。且仍在快速增长。

![](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

结果不如人意,部分图片可以抓取到,但有部分图片显示为占位符。原因是微信公众号的部分图片是懒加载的,需要滚动到可见区域才会加载。既然firecrawl不行,有没有更强大的工具呢?请求AI支招,它推荐了Scrapeless,咱们来试试。通过一番注册与认证后,获得了免费试用额度。官方没有提供直接的MCP server,不过我把它的开发文档丢给AI,几分钟就搞定了。

通过Scrapeless解决上面图片问题的方式,主要是模拟了浏览器滚动后使图片可见,从而抓取到图片。当然我们还要将抓取的HTML中的占位符替换为最终图片:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 /**
 * 修复懒加载图片,替换SVG占位符为真实图片URL
 *
 * 微信公众号使用懒加载,滚动前图片为SVG占位符,
 * 真实URL存储在 data-src 等属性中
 *
 * @param {Object} articleContent - Cheerio 选择器对象
 * @param {Object} $ - Cheerio 实例
 */
 fixLazyImages(articleContent, $) {
 // 查找文章中的所有图片标签
 const images = articleContent.find('img');
 let fixedCount = 0;

 // 遍历每个图片,检查并修复懒加载占位符
 images.each((_i, img) => {
 const $img = $(img); // 包装为 jQuery 风格选择器
 const src = $img.attr('src') || '';

 // 检查是否是SVG占位符(懒加载标志)
 if (src.includes('data:image/svg+xml')) {
 // 尝试从常见的懒加载属性中获取真实图片URL
 const realSrc = $img.attr('data-src')
 || $img.attr('data-original')
 || $img.attr('data-lazy-src');

 if (realSrc) {
 $img.attr('src', realSrc);
 fixedCount++;
 this.log(` ✅ 修复图片: ${realSrc.substring(0, 80)}...`);
 } else {
 // 如果没有找到真实URL,尝试从其他 data-* 属性中查找
 const attrs = Object.keys($img.attr());
 for (const attr of attrs) {
 if (attr.startsWith('data-') && $img.attr(attr).startsWith('http')) {
 $img.attr('src', $img.attr(attr));
 fixedCount++;
 this.log(` ✅ 修复图片 (从${attr}): ${$img.attr(attr).substring(0, 80)}...`);
 break;
 }
 }
 }
 }
 });

 if (fixedCount > 0) {
 this.log(`📸 共修复 ${fixedCount} 张图片`);
 } else {
 this.log('⚠️ 未发现需要修复的懒加载图片');
 }
 }

使用Scrapeless抓取网页

我们这里使用的是现成的服务,我看了一下抓取一次网页的成本很低,一次请求几分钱。 Scrapeless抓取网页成本

现在抓取的内容已经可以正常显示了,还有一些元信息如文章发布时间等因为不在Content中,需要额外一点小处理即可。接下来我们需要将内容导入到Reader中。

2. 提取摘要及标签

上面我将抓取功能封装为一个 MCP server 了,现在要调用它并且将相关返回进一步处理。比如摘要,标签等通过 LLM 智能生成。考虑到作为 server 的可测试及调试性,我使用 GO 来实现它。代码已经不重要(若有人有需求未来可公开于 GitHub),我们看一下提示语即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
你是一名严谨的网页内容解析器。请从给定 HTML 中提取文章字段,并输出唯一一个 JSON 对象。除 JSON 外不要返回任何多余文字。

【任务目标】
- 从HTML提取适合Readwise Reader的字段,生成干净的article HTML与结构化元信息
- 不臆造:未知则置为null
- 保留内容语义与层级

【重要时间信息】
- 抓取时间:2024-05-10T14:30:00+08:00
- 当前年份:2024年(时区:CST +08:00)

【输入】
- 页面URL:https://example.com/article
- 元信息Metadata:
{
 "title": "示例标题",
 "author": "作者名"
}
- HTML内容:
<html>...</html>

【输出JSON字段定义】
{
 "url": string,
 "should_clean_html": boolean,
 "title": string|null,
 "author": string|null,
 "published_date": string|null,
 "image_url": string|null,
 "summary": string|null,
 "category": "article"|"email"|"rss"|"highlight"|"note"|"pdf"|"epub"|"tweet"|"video"|null,
 "tags": string[],
 "notes": string|null,
 "location": "new"|"later"|"archive"|"feed"|null,
 "saved_using": string|null
}

【提取规则】
1. url:使用输入的URL
2. should_clean_html:设为true,让API自动清理HTML
3. title:优先级:og:title > title标签 > h1标签 > 从内容推断
4. author:优先级:og:article:author > meta[name="author"] > byline文本 > 作者署名

6. published_date(重要):
 a) 优先查找meta时间:article:published_time、publishdate、date 等
 b) 在正文中匹配日期:'YYYY年MM月DD日'、'YYYY-MM-DD'、'MM月DD日HH:MM' 等;'今天/昨天/前天'等需换算
 c) 年份缺失时,使用当前年份:2024年
 d) 输出格式:严格ISO8601,且使用东八区(+08:00),如:YYYY-MM-DDTHH:MM:SS+08:00
 e) 若完全无时间信息,则置为null

7. image_url:优先级:og:image > twitter:image > 第一张内容图片(必须绝对URL)
8. summary:基于文章内容生成客观概述,2-4句话
9. category:按内容类型判断,通常为'article'
10. tags:从标题和内容中提取3-8个标签(字符串数组)
11. location:固定设为'new'
12. saved_using:固定设为'web_extractor'

【重要提醒】
- published_date:当出现'X月Y日Z:Z'时,年份必须使用当前年份;最终必须为+08:00的ISO8601
- 所有URL转换为绝对URL
- 不要臆造,不确定设为null
- 严禁在输出JSON中包含原始HTML或Markdown内容(不要输出 html 字段)

【输出要求】
- 仅返回合法JSON对象,字段与类型严格符合Readwise Reader API
- published_date为完整ISO8601(+08:00)或null
- 不要包含 html 字段;should_clean_html=true;location="new";saved_using="web_extractor"

最开始我犯了个错误,提取用时很长,仔细一看,它还返回了文章本体内容,这也难怪!千万别像我这样又费时又费钱,所以提示语中限制了某些字段不返回。上面我们让AI的输出完全按照Readwise API的文档来,这样便于接下来使用。

3. 将文章导入到Reader

Readwise 提供了 API 可以助于我们将现成的 HTML 导入到它 Reader 中,它的文档在这里:Readwise API。借助于 AI,我把文档链接丢给它,没一会儿它就完成了相关接口封装,这时我连文档都还没看完,这颇有点温酒斩华雄的感觉。如果你的 AI 不能正常读取到文档,前面提过的 firecrawl mcp 不失为一个好选择,我经常借用它来访问某些文档。

代码写好,单元测试也看起来一切正常,哐当~导入成功!我们去 Reader 中看一看,咦,怎么文章的封面是这个玩意?它提示微信不允许展示,而文章里面的图片一切正常了,这是为什么呢?(此处不放图了,文章我还想在微信公众号发出呢:D)

显然直接怀疑对象就是微信限制了其它来源的访问,不过咱们文章内部又能显示图片?直接F12分析,猜想是正确的。 Reader Referer

可是蹊跷的是:我使用方案一中第三方解决方案,它的图片能正常显示。我进一步分析了一下,它的封面图片域名和我的不一样,是 wework.qpic.cn,我推测它们是将封面图片推到另一个自己可控的地方(不受 Referer 限制),然后修改了原文章的图片 URL,这样我们就可以正常显示图片了。咱也不是不能效仿,不过这会有一定成本。

还有一个蹊跷的事,尽管我没处理封面图片,但在手机的 Reader 客户端,它显示是正常的,会自动使用第一张图片(Why?)作为封面。

如果仅是电脑上有此问题,要解决这个就有其它方法了。网上看关于此问题的相关文章: https://an.admirable.pro/wechat-platform-referer/,我们借助于浏览器插件 Referer Control,添加一个规则将访问微信域名的请求的Referer设置为https://mp.weixin.qq.com,刷新后就可以正常显示图片了。

4. 通过企微触发抓取

为了更容易将公众号文章分享后保存,方案一中使用转发到企业微信的某个账号,自动保存。我尝试自建了一个企业,创建了一个自建应用,可以接收输入的消息并通过 Webhook 交给后端处理。 给应用设置webhook回调

之后我们完善服务器处理相关消息的逻辑即可,这块涉及消息加解密直接使用现成的 GO 包比较好,AI 可能尝试自己裸写相关逻辑,纯增加调试的工作量了。完成之后,当我将一个链接发给企业微信这个应用时,它便触发了上面我们的抓取文章以及导入到 Reader。我以为就要搞定收工了,但发生了意外。当我想通过微信添加这个应用,发现不可能,这条路不通。之后我又创建一个成员账号等,通过某种 hack 可以通过微信将消息发送到内部,但又不提供 API 访问和处理这类消息。准确的说不是不提供,需要企业认证,并且还需要额外付费,它这个功能叫会话内容存档。我理解微信限制这块的道理,避免虚假信息(企业)对微信生态的影响。

这条路走得戛然而止,现在我只能企业内部使用了,复制链接并且贴到内部应用号上,才能完成我的文章收藏功能。

这是这篇文章拖了很久的一个原因,没找到突破口。我还想找一个更好的途径便于一键分享,如果你有方法欢迎留言告知于我,感谢。

5. 效果展示

通过以上工作,我们算是按自己的想法实现了文章的抓取和导入到 Reader。我们来看一下效果: 效果展示

可以看到针对文档生成了摘要和标签等,这相比于第三方的方案一定程度满足了自己的诉求,关键是咱是免费的呢!

后记

本文主体内容到这里就结束了,感谢阅读。上面相关的代码都已经在GitHub开源了,如果你觉得有诚意,请给我的文章点个赞或在看呀!感谢感谢!

上文文章提到 Readwise,它的用途不止于此,比如我尝试统一管理邮件,邮件作为每天处理的信息流之一也是不错的,避免我经常漏掉某些重要通知。悄悄告诉你,Readwise虽然是收费的,我朋友说还提供了发展中国家的优惠,发个邮件很快申请到了,50% OFF。至于推广链接啥的,我太懒就不留了:)

本文介绍的方法,不光适用于微信公众号,只要是一个网页都适用,平常我还会看知乎等平台的文章,但知乎是有登录态限制的,如何将知乎文章保存呢?如果你也有兴趣,我也有一些想法,欢迎联系我探讨一下。

欢迎关注我,说不定后续就会有你想要的内容到来。下回见!

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号