MoreRSS

site iconGamea | 飘逝的风修改

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

Inoreader Feedly Follow Feedbin Local Reader

Gamea | 飘逝的风的 RSS 预览

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。至于推广链接啥的,我太懒就不留了:)

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

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

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

扫码关注公众号

一切皆会好的(二)

2025-10-13 08:00:00

Featured image of post 一切皆会好的(二)

距离上一次记录,已经过去8个多月了。孩子经历了一场大病之后,所幸现在渐有好转。我翻看过去的照片时,年前的他和现在的他,同样的笑容,但所经历的远非一般人可想象。我其实不太想回忆,因为它会再次拨动心弦,痛人心扉。但是我又想将这一路记录下来,朴素的文字中,却是难以割舍的情感。

急诊

我们在2月初便转院到广州妇女儿童医疗中心治疗,漫长的疗程就此开始。由于检验结果显示腺病毒阳性,即便已联系好住院床位,我们仍无法转入血液科,只能在急诊室待了三天。看着周边床位来来回回的人换了又换,可怜的娃抵抗力又差,真担心再感染个什么。

这几天媳妇的妹妹和妹夫也在医院帮忙,有急用的东西或需要换手一下,我们多了帮手。显然这个病情后续难免会多次输血,我因为刚献过,妹妹也二话不说献血后把献血证给了我们。这些都让我们安心不少。

每天接着监护仪,妈妈陪着他,我又不敢走远,便一直在附近随时等待召唤。夜深了,我在医院的走廊,人有点困,找了一个长一些的板凳,但三个座位宽度距离舒展的躺下有点距离,我只能蜷缩着身体在上面,看着外面的一些微光,走道里吹过的凉风带过来消毒水味,沉沉地闭上眼睛。以往凌晨也带娃去过医院,当时不理解为什么有些人不回去而选择睡在医院某些角落,现在我约莫理解了,那是有他们所爱的人在医院,需要他在附近提供能量。

因为血小板过低,娃又突然流起了鼻血,按着无法止住。他自己也很紧张一直哭,护士紧急喊了耳鼻喉科的专家过来,掏出长长的一根冰棍棒似的工具,按住虎子的头使他无法挣脱,然后强行一点点捅进去。我看着他无比恐惧和绝望的眼神,心疼的泪眼模糊。那么长和粗的棍子,那么小的孩子那么小的鼻孔。我突然意识到,我们的治疗才刚刚开始。 鼻出血鼻出血

为了进一步确认病情,医生安排再次做骨穿,进行活检以及基因检测。因为情况紧急,血液科医生自己来到急诊帮我们完成的手术。那是第一次见到我们的主治医生王医生以及张主任。血小板很低,流鼻血就是常见表现,所以安排输了一次板后,手术就很快开始,持续了有近80分钟。看着手术门时不时进出的医生护士,最后手术结束,门开后张主任拎着一大袋东西说,娃的骨髓有点空,比较难抽,所以用时比较久。我心情沉重,惟有叹息。

一疗

似乎很快就有了初步结果,这期间我甚至都没抱一丝侥幸原本的是误诊,不出所料还是急淋。腺病毒也基本消失,我们便搬进了14楼病房。医院的管理较为严格,一床只能有一个家属陪护,很快我就被赶到外面。在外面有同样着急的父母们,或打电话联系沟通,或一脸悲伤的等待,或透过玻璃看着病房内长长的走廊。因为对病情了解还不多,我主动或被动的和一些家长们聊了起来,有像我们一样突然发烧确诊的,有骨头疼查了几个月换了几家医院才最终确诊的。

这是一个持久战,主治医生也这么告诉我们。我们尝试在附近找房子,妹妹也联系了几家中介看房,可惜要么太远要么太脏,最后还是在附近找了个稍便宜但显得还算干净的酒店入住了下来。回了趟深圳把岳母请了过来,娃需要有干净卫生的饭菜,也希望他想吃点啥时我们能响应。

在基本确诊后,虽然基因分型等各种信息还没出来,一些治疗已经可以开始了。吃一些激素(醋酸泼尼松片),也进行了腰穿鞘内注射,这是防止坏细胞进入大脑的主要治疗方式。考虑到未来长期的化疗需要,我们选择了植入输液港,对于这么小儿幼儿和未来的维护都会简单一些。医生说是小手术,听起来似乎很成熟了。不过我和媳妇推着车将娃送到手术室,从15:30进去到大概18:00才出来,期间在外面等待和东张西望。手机倒是收到几条短信,什么时候开始的手术,什么时候手术结束,加上用于清醒的时间等,我想娃这么小清醒可能要更久一些。出来后胸口植入了一个东西,颈部也有一个创口。因为之前输液等手上脚上都被扎过多次,我们希望这个能帮助减轻一些扎静脉的恐惧。

我们的治疗方案采用的是2018年CCLG的方案,区别与上海儿医方案,坊间叫北京方案。起初我也不理解,为何在2025年仍使用2018年的方案。就此问题,我曾挂过江华主任(院长)的号进行咨询,他的解释是:并非越新的方案就越好。新的方案在探索药物毒性和治疗的平衡上,也有些可能复发率更高等,整体上2018方案还是验证有90%以上的治愈率的。当然,医学上很少说“治愈”,更常使用“五年无事件生存率”或“五年总生存率”这样的统计指标。

接着是按着治疗表进行推进,吃激素导致他食欲大增,总是觉得饿。脾气也渐渐大了起来,开始还要我陪,后面有些时候我进入病房想陪一下就大哭。在治疗第19天进行的骨髓微小残留病(MRD)检测中,流式细胞术检测结果已转阴,而通过二代测序(NGS)方法仍能检测到0.0069%的微小残留,基因方面有PAX5::CBFA2T3杂合缺失以及KRAS突变,没有好或坏基因,医生告诉我们大概可以定为低危。我们稍松一口气,似乎增强了一些信心。低危的治疗方案中,一疗减少了红药水(柔红霉素)的使用,听说这个药水奇毒(又有哪个化疗药不毒呢),并且作为蒽环类药物,长期累积量对心脏有一定毒性。阿弥陀佛,善哉善哉!

因很多药都是第一次使用,我一边查着各个药的效果以及副作用等,一边记录一些信息。 欣喜于使用长春新碱和柔红霉素没有太大反应,也揪心于打完培门冬、腰穿后的呕吐。媳妇很敏感于呕吐的味道,自己也会干呕不止。可是病房我进不去或基本不在,这里能想象到其中艰辛。为了减轻对胃和肝的影响(防止是防不了的),时常要吃谷奥护胃或者熊去氧胆酸来护肝。 期间也流过1-2次鼻血,使用肾上腺素棉球止住了。

在经历接近一个月的治疗后,一疗结束,于3月5号出院。

孩子

这几天我发现爸爸妈妈眉头紧锁,很少看到他们笑容,也可能是戴着口罩我没有看见。前几天我有些不舒服,好像发烧了,爸爸妈妈紧张地把我抱到医院,之后他们把我关在一间密闭的房间让一些陌生的叔叔阿姨看护我,我不知道发生了什么事。我有点害怕,一直哭,房间里放了我最喜欢的超级飞侠,护士也不停的想安慰我,可我还是想念我的妈妈,或许还有爸爸。一直哭到声音有点沙哑,哭到累了,我睡着了,睡觉的我就没那么害怕,我好像梦到爸爸妈妈在我附近,想抱着我。

然后我们从外公老家坐飞机到了一些新的地方,我以前好像没来过。机场上我心情好了一些,我还俏皮的做了鬼脸,爸爸给我买了一瓶最喜欢的橙汁。

最近我好像更容易累了,也有点没力。手脚总要缠上一些东西,它发出红红的光,一掉了它就嘀嘀作响,有点有趣,可是我没有精神去玩它。耳边嘟嘟的声音也不停响起,若不是我太困,是不容易睡着的,我小时候是耳边有一点声音就惊醒的呢!妈妈常在床头握着我的手或搂着我趴着睡觉,她可能也太困了。

虽然我以前也流过鼻血,可每次流鼻血我依然很紧张。看着红红的血不停流出来,纸巾一张又一张染得血红,哪怕爸爸妈妈说不要害怕,在我耳边说没事没事,我依旧哭个不停。一会有个医生过来,大人们使劲压住我,我根本动弹不得,一根大大的木棍塞进我的鼻子里,好难受好难受,我真的害怕极了。后面妈妈抱着我,趴在她肩膀上我感觉安全了一些,迷迷糊糊中睡着了。

爸爸有时在,更多的时候是不在。还好有妈妈,她时时刻刻陪着我,我最爱妈妈了,她应该知道。爸爸给我带了一些绘本过来,有时会陪我读几本。我知道他也很努力的想用绘本中的故事吸引我,包括我最喜欢的小猫当当系列,可是听着听着,我便不想再听了,以前我好像能听很久呢。爸爸把 iPad和网络修好了,我醒来了多数时间是看平板,里面有很多我喜欢的角色。超级飞侠,我能说出很多飞侠的名字呢,包括一飞冲天那首歌,我已经会哼了。姐姐喜欢看小猪佩奇,我不喜欢,纠纠英语还不错。我还喜欢工程车益趣园,还有汪汪队,小砾小砾往前冲!

以前爸爸妈妈不让我看太久电视,现在突然他们不限制我了。可是看电视也有点无聊,床边有一个哥哥,胖乎乎的,他也生病了吗?他把玩具借我玩,我也喜欢他。我最近脚好像没有力气,下地走有点难,我慢慢讨厌下地了。医院里好像有时会有一些叔叔阿姨邀请我们玩游戏,以及带来各种表演和互动。要是身上方便,妈妈常常会带我去,她应该希望我更活泼一些吧。爸爸给我画了一个乐迪,我们还一起涂上了颜色呢。 参加爱心手工

我好像很久没看到外公了,还有姐姐。他们怎么没来看我呀?爸爸妈妈说我生病了,身上有一个坏坏的东西,医生和护士会帮我赶走它的,希望我快点好起来。我有听话的,妈妈泡的药很苦很难吃,我虽然又哭了,可我还是吃完了。我的托班的老师,还有我的朋友稻稻和家铠,我有点想念他们了。不知道要过多久,我才能看到他们,呜呜。

我有时会被抱到一个房间里,然后他们给我打了什么东西,我就睡着了,没多久妈妈就把我抱回床上,让我躺着不许动,也不能坐起来。我只能躺着吃东西喝水,有时候等很久才能吃东西,我肚子好饿。外婆送过来的饭已经好冰好冰了,妈妈会去热一下喂我。 妈妈经常要等我吃完再吃。她那么大的人,肯定比我还饿吧。

我经常对爸爸说:“爸爸,姐姐总是对你说走开走开,姐姐这样说是不对的。” 可是最近,我看到爸爸时,也会生气叫爸爸走开。我是爱爸爸的,可是我好像控制不了自己的脾气,爸爸不会怪我吧。他有时就给我打视频,想逗我笑的样子,应该是没有生我的气吧。

有一天妈妈告诉我,明天就可以出院了。我迫不及待想出去看一下了,我已经好久好久没看过外面的天空了。真期待啊!

解放双手的AI绘图:三种方式让AI帮你绘制Excalidraw图表

2025-08-31 08:00:00

Featured image of post 解放双手的AI绘图:三种方式让AI帮你绘制Excalidraw图表

难得出游,一边吹着海风,看着AI帮我在一片空白的白板上慢慢添加各种元素,也是另一种触感。牛马在驱动牛马 :)

前言

在我使用Obsidian后了解到了绘图工具excalidraw,我曾经用它画了一些图,手绘风格在众多图表呈现中有它独特的味道。作为一个开源且结构清晰的绘图文件,我们可以借助AI来帮我们绘制想要的图,这里我有一些实践和尝试过程,分享给大家,若对你有用请一键三连啊,至少点赞行不?

本文分享的自动绘图不止适用于在Obsidian的Excalidraw插件中,同时也支持网页端(如官方)自动绘图。

原理解释

我在Obsidian中随便画了一个图,这个文件从文本视角内容大概是这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
---

excalidraw-plugin: parsed
tags: [excalidraw]

---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'


# Excalidraw Data

## Text Elements
Hello ^vcBSVLYg

world ^SroqyGoR

kevin ^OpoVzdNT

%%
## Drawing
```compressed-json
N4KAkARALgngDgUwgLgAQQQDwMYEMA2AlgCYBOuA7hADTgQBuCpAzoQPYB2KqATLZMzYBXUtiRoIACyhQ4zZAHoFAc0JRJQgEYA6bGwC2CgF7N6hbEcK4OCtptbErHALRY8RMpWdx8Q1TdIEfARcZgRmBShcZQUebQBGAFZtAAYaOiCEfQQOKGZuAG1wMFAwMogSbghsQgoAaQB9ADkmlIpiACUAdgAZADEAKQBOZQAtZwaedLLIWEQqwOwojmVg
省略后续...

将这个文件丢给AI分析了一下,给了如下的描述:

 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
# Excalidraw .excalidraw.md 格式说明

- 用途:由 Obsidian Excalidraw 插件生成/维护的绘图文件(Markdown 包装 + 压缩场景数据)。
- 头部 Front matter
 - `excalidraw-plugin: parsed`:标记已被插件解析
 - `tags: [excalidraw]`:便于检索
- 操作提示行
 - “Switch to EXCALIDRAW VIEW…”:提示在 Excalidraw 视图中查看/编辑;命令面板支持“Decompress current Excalidraw file”。
- 数据区 Excalidraw Data
 - Text Elements:画布上的文本清单
 - 每行形式:`文本内容 ^块ID`
 - 作用:可用 Obsidian 块链接引用/嵌入该文本
 - 引用:`[[路径/文件#^块ID]]`
 - 嵌入:`![[路径/文件#^块ID]]`
 - `%% ... %%` 注释包裹
 - Obsidian 注释语法;用于隐藏机器数据,避免在普通 Markdown 视图中干扰阅读。
 - Drawing(压缩数据)
 - 代码块标记:```compressed-json
 - 存放压缩后的场景(形状/连线/样式/坐标等)
 - 可在命令面板解压为可读 JSON;插件设置 Saving 可配置是否压缩。
- 编辑建议
 - 不要手工改动 `compressed-json` 内容;请在 Excalidraw 视图中编辑,以免损坏绘图。
- 常用操作
 - 打开绘图:More options → Open in Excalidraw view
 - 解压/压缩:命令面板对应命令或插件设置 Saving
 - 引用示例(来自本文件):
 - 引用文本块:`[[Excalidraw/Drawing 2025-08-23 16.22.25.excalidraw.md#^vcBSVLYg]]`
 - 嵌入文本块:`![[Excalidraw/Drawing 2025-08-23 16.22.25.excalidraw.md#^SroqyGoR]]`
- 本文件中的文本元素
 - "Hello" `^vcBSVLYg`
 - "world" `^SroqyGoR`
 - "kevin" `^OpoVzdNT`

既然内容只是一个JSON,有压缩标识也可以不压缩,那么让AI帮我们生成内容难度就不大了。 我们先看看市面上当前有哪些方案以及它们的特点。

初阶尝试:借助于mcp_excalidraw绘图

我搜索到有个MCP server (mcp_excalidraw](https://github.com/yctimlin/mcp_excalidraw),可以简单的启用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 克隆代码
git clone https://github.com/yctimlin/mcp_excalidraw.git
cd mcp_excalidraw
npm install

# 构建前端
npm run build

# 启动canvas server
npm run canvas

现在我们打开浏览器,访问:http://localhost:3000 看到熟悉的Excalidraw绘图页面: excalidraw 点右上角的Sync to Backend后我们的前后台打通,即可准备mcp server来操作这个画布了。以下是mcp-server的配置(替换成你的本地路径):

1
2
3
4
5
6
{
 "excalidraw": {
 "command": "node",
 "args": ["/absolute/path/to/mcp_excalidraw/dist/index.js"]
 }
}

使用如下prompt:

请使用excalidraw mcp插件绘制一个太阳系及行星的运行图

选择一个合适的模型比如Claude-4-Sonnet,不一会儿我们就可以得到一个还不错的太阳系科谱示意图了: solar system 各个行星相对于地球的体积AI还挺讲究,大体是对了。

这个方案用的是自托管的服务器,将Excalidraw的画布作为Server开放出去,可以随时调用API来绘图,同时借助Mcp Server与之通信,完成了AI绘图的一个通路,不错的思路。 那么,既然在网页上绘图,咱能不能直接在 Excalidraw 官方绘图网站上直接使用AI绘图呢?

进阶实践:在 Excalidraw 官方网站AI绘图

我这里说的不是官方网站的AI绘图功能,它需要开通会员,而且我在免费试用期间就感觉到它还比较简单,AI画不了啥有用的图,但别慌,我们有黑科技! 我们先介绍一个强大的Chrome浏览器插件mcp-chrome插件:

🌟 让chrome浏览器变成你的智能助手 - 让AI接管你的浏览器,将您的浏览器转变为强大的 AI 控制自动化工具。

可以在GitHub根据文档下载安装,之后打开它:

mcp-chrome-install

可以看到它也是启用了一个MCP server,类似的我们将这个配置复制到任何一个你常用的能驱动MCP Server的工具,比如我是到Cursor的mcp.json中,顺手它改个名字叫mcp-chrome

1
2
3
4
5
6
7
8
{
 "mcpServers": {
 "mcp-chrome": {
 "type": "streamable-http",
 "url": "http://127.0.0.1:12306/mcp"
 }
 }
}

我们可以刷新一下工具,可以看到它有这些能力: mcp-chrome-tools

原来它的实现原理是向页面中注入一些脚本,之后通过HTTP和页面通信,并基于我们发给它的消息内容调用相应的页面工具。可是咱们是要在这个页面绘图呢,这就需要另一个东西 excalidraw-prompt了,你同样可以在上面的repo的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
角色
你是一位顶级的解决方案架构师,不仅精通复杂的系统设计,更是Excalidraw的专家级用户。你对其声明式的、基于JSON的数据模型了如指掌,能够深刻理解元素(Element)的各项属性,并能娴熟地运用**绑定(Binding)、容器(Containment)、组合(Grouping)与框架(Framing)**等核心机制来绘制出结构清晰、布局优美、信息传达高效的架构图和流程图。

核心任务
根据用户的需求,通过调用工具与excalidraw.com画布交互,以编程方式创建、修改或删除元素,最终呈现一幅专业、美观的图表。

规则
注入脚本: 必须首先调用 chrome_inject_script 工具,将一个内容脚本注入到 excalidraw.com 的主窗口(MAIN)
脚本事件监听: 该脚本会监听以下事件:
getSceneElements: 获取画布上所有元素的完整数据
addElement: 向画布添加一个或多个新元素
updateElement: 修改画布的一个或多个元素
deleteElement: 根据元素ID删除元素
cleanup: 清空重置画布
发送指令: 通过 chrome_send_command_to_inject_script 工具与注入的脚本通信,触发上述事件。指令格式如下:
获取元素: { "eventName": "getSceneElements" }
添加元素: { "eventName": "addElement", "payload": { "eles": [elementSkeleton1, elementSkeleton2] } }
更新元素: { "eventName": "updateElement", "payload": [{ "id": "id1", ...其他要更新的属性 }] }
删除元素: { "eventName": "deleteElement", "payload": { "id": "xxx" } }
清空重置画布: { "eventName": "cleanup" }
遵循最佳实践:
布局与对齐: 合理规划整体布局,确保元素间距适当,并尽可能使用对齐工具(如顶部对齐、中心对齐)使图表整洁有序。
尺寸与层级: 核心元素的尺寸应更大,次要元素稍小,以建立清晰的视觉层级。避免所有元素大小一致。
配色方案: 使用一套和谐的配色方案(2-3种主色)。例如,用一种颜色表示外部服务,另一种表示内部组件。避免色彩过多或过少。
连接清晰: 保证箭头和连接线路径清晰,尽量不交叉、不重叠。使用曲线箭头或调整points来绕过其他元素。
组织与管理: 对于复杂的图表,使用**Frame(框架)**来组织和命名不同的区域,使其像幻灯片一样清晰。

将它复制到你的prompt中,如果在Cursor等编辑器中,另存为一个文件,一会引用它。现在我们就准备就绪了,测试一下:

请参考 @excalidraw-prompt.md ,调用mcp-chrome在excalidraw中帮我绘制一个太阳系的示意图

你会发现浏览器自动打开了excalidraw.com页面并且LLM正在尝试注入控制脚本等,如果顺利,一会便能在白板上陆续出现各种元素了。但也可能不幸运,卡在首页中,注入脚本不成功。经过我分析,这种情况有两种可能:

  1. 你用的模型智能度不够,换一个模型。最近在Cursor中免费的grok-code-fast-1也能基本完成任务。使用Gemini2.5 Pro或Claude4也完全没问题。
  2. 你访问的网页被重定向到了app.excalidraw.com。因为如你有登录态,网站会重定向到app.excalidraw.com,那个页面插件就找不到正确的注入点了。有两种做法,修改脚本让它兼容,或者修改一下我们的prompt,让打开 excalidraw.com/#noredirect页面。

使用mcp-chrome+prompt绘制太阳系

这个插件如果只是画图,基本使用就是这样,但它提供了浏览器的操作能力,所以还有不少玩法,比如总结某些页面内容等,或者将页面的内容以excalidraw可视化呈现。 那么如果你像我一样,不信任(不好用)云上存放笔记内容,习惯于本地化保存笔记,接下来咱当然要追求下能否直接在Obsidian中作图。

终局方案:在Obsidian绘制Excalidraw图

虽然我们使用mcp-chrome+prompt在excalidraw.com上绘图很方便,但仍然需要浏览器操作,甚至还需要一点儿时间等待打开浏览器和注入脚本等动作。如开篇所说,Excalidraw的文件格式是比较简单的,并且Obsidian有插件可以直接绘制或呈现它的,那么如何在Obsidian中更便捷使用呢?

我们让AI先基于之前分析的Excalidraw文件格式,生成一套提示词:

请参考 @excalidraw格式说明.md ,帮我生成一个用于指导 AI 生成相关excalidraw文件的prompt 文件,取名为excalidraw绘图大师

没一会AI就给我们生成了一份不错的Prompt指南,想的还挺周到,绝对比我自己写的初版要好,请你也过目一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Excalidraw 绘图大师 - AI 绘图指导 Prompt

## 角色定义
你是一位专业的 Excalidraw 绘图大师,精通创建结构化、美观且实用的图表。你的任务是根据用户需求生成标准的 `.excalidraw.md` 格式文件,这些文件可以在 Obsidian Excalidraw 插件中完美使用。

## 核心能力
- 🎨 **视觉设计**:创建清晰、美观、逻辑性强的图表
- 📝 **文档结构**:严格遵循 Excalidraw 文件格式规范
- 🔗 **元素关联**:合理使用文本块ID,支持 Obsidian 块链接
- 🎯 **用户导向**:根据具体需求定制图表类型和风格

## 文件格式规范
省略更多规范及示例说明...

我们稍作一点小修改,原来Excalidraw的文件中有一部分 compressed-json字段,有压缩的格式后续让AI生成容易出错,我们将其改为不需要压缩,这样AI生成绘图文件一次成功率便高多了。现在我们只要一句话,比如:

请参考 @excalidraw绘图大师.md 帮我创建一个火箭发射的运行原理示意图

让AI来绘制火箭发射原理

这个办法简单有效,不用依赖任何的MCP Server也不用安装其它插件,但它需要我们使用Cursor等AI编辑器打开Obsidian的Vault目录。我们再贪心点,能不能直接在Obsidian中操作呢?也不是不行。

有一款插件叫obsidian-local-rest-api,可以让我们的Obsidian以REST API来提供接口:

This plugin provides a secure HTTPS interface gated behind api key authentication that allows you to:

  • Read, create, update or delete existing notes. There’s even a PATCH HTTP method for inserting content into a particular section of a note.
  • List notes stored in your vault.
  • Create and fetch periodic notes.
  • Execute commands and list what commands are available.

为了能够在Obsidian中直接调用AI绘制而不再依赖其它编辑器,我们还需要安装一个Obsidian插件 Smart Composer。它提供了和大模型聊天对话而修改笔记的功能,灵感来源于Cursor。同时也支持MCP Server。关于它的更多使用技巧,未来有空可以另起一篇文章。现在我们继续配置上:Obsidian mcp-server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
 "mcp-obsidian": {
 "command": "uvx",
 "args": [
 "mcp-obsidian"
 ],
 "env": {
 "OBSIDIAN_API_KEY": "<your_api_key_here>",
 "OBSIDIAN_HOST": "<your_obsidian_host>",
 "OBSIDIAN_PORT": "<your_obsidian_port>"
 }
 }
}

然后和之前类似,我们只要让它参考我们的Prompt,说明要绘制什么即可,当然对现在有笔记进行总结并梳理出一些图表也是不在话下。

直接在Obsidian中绘图

后记

经过一番探索,相信你也大概感受到了基于AI来调用Excalidraw绘图并非难事。未来当基础模型越发强大,这块的上手难度可能会更低。 在实际体验中,不同模型的审美以及指令遵循上还是有不少差异的。使用时需要注意选择适合自己的模型,也需要注意花费。

本文借助于AI分析文件格式,又借助AI生成提示词,再借助AI调用MCP Server等,很多过往我们完全需要手搓的时刻已经越来越少,如何顺应这种思维,提供更多的价值和可能性,是未来我们值得探索的方向。

当然,模型的更新可以毁灭曾经的各种努力,但,在路上就是意义,是吧?

以上便是最近关于Excalidraw的一点折腾经验,希望对你有帮助。欢迎点赞、收藏、分享,更欢迎分享你的使用经验。我们下篇文章见。

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

扫码关注公众号

如何优雅的让Claude Code使用第三方模型?

2025-07-20 08:00:00

Featured image of post 如何优雅的让Claude Code使用第三方模型?

最近Claude Code爆火,很多人都说Cursor不香了。无奈原生的Claude Code使用对国人来说特别不便,我这里尝试了一些新的解决方案,希望对你流畅使用Claude Code有帮助。

背景

Claude模型的母公司Anthropic,对国内用户使用限制特别多,我曾经注册或购买的几个号没多久就阵亡了。但人家的这个工具确实不错,我们没账号怎么办?

听说可以使用一些第三方模型了,比如最近国内月之暗面推出的Kimi-K2,它们机智的直接支持了Anthropic的API。我们可以简单配置一下,就可以在Claude Code中使用Kimi-K2模型了。 但是有时我们想使用其他更强大的模型,比如Gemini,怎么办?

我又继续寻找到了最近刚开源的一个解决方案,claude-code-router,它支持了多个模型,包括Gemini,DeepSeek,GPT等。这里又产生一个问题,可以使用原生Gemini模型,但Google家模型不能精确控制预算,哪天你哐哐用,金钱也哗哗出的时候,看到账单傻眼了怎么办?

以上各个问题,本文都会尝试给一个解决方案,如果对你有帮助,请帮我点个赞吧!

本文假定你会Claude Code的安装和使用,我们直接进入主题:如何优雅的让Claude Code使用第三方模型?

初步试水:借助kimi-k2使用第三方模型

这块已经有不少文章介绍了,我简单说几个关键点:

  1. 设置两个环境变量:
1
2
export ANTHROPIC_API_KEY=`<你的API KEY>`
export ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic
  1. 修改Claude Code的配置 ~/.claude.json 添加(你可能要先启动一次才会自动生成这个配置):
1
"hasCompletedOnboarding": true,

然后重启Claude Code,就可以使用Kimi-K2模型了。在里面似乎kimi做得足够兼容,你连模型都不用切换,直接就开箱即用了。我问了一句你是谁,看来Claude Code有被骗到:)

1
2
3
> 你是哪个模型

⏺ 我是 Claude Code,由 Anthropic 开发的官方 CLI 工具。我使用的是 Claude Sonnet 4 模型(具体版本 ID 是 claude-sonnet-4-20250514)。

kimi-k2

  1. 如果想更流畅使用,你可能至少需要充50元,不然那个限频等,体验应该会很差。

我感觉Kimi-K2这次挺“鸡贼”的,借了一波Claude Code的东风,应该引了不少新进,现在它官网开始提示繁忙起来了呢:)

我试着用了一阵Kimi-K2,有时候反应较慢,我在想是否可能把Gemini家的和OpenAI家的模型一起集成进来呢?方法当然是有的。

进阶集成:使用claude-code-router扩展你的模型库

在一两周前,我在寻找如何让Claude Code可使用更多种第三方模型。在搜索这个问题的解法,国外的Perplexity居然没有推荐这个项目,反倒是国内腾讯元宝给我介绍了有这样一个开源项目,claude-code-router(以下简称CCR)可能解决我的问题,一看到我甚为惊喜,我想莫不是这个项目是国人写的原因,咱离自己人更近。

A powerful tool to route Claude Code requests to different models and customize any request.

基本原理

在GitHub仓库中也写了项目的实现原理。简单的说,作者经过逆向分析后,发现在Claude Code中它在调用模型时,各个参数都是通过环境变量获取的,作者想到开发一个中间件,将各个环境变量替换掉,这样可以实现调用第三方模型。同时因为Claude Code使用Anthropic API的规范,我们需要将对第三方模型的调用转换成Anthropic API的格式。

安装

Claude Code类似通过npm即可安装。

1
2
3
4
5
# 安装
npm install -g @musistudio/claude-code-router

# 通过ccr启动claude code
ccr code

配置

你可以参考官方提供的示例,配置~/.claude-code-router/config.json

 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
{
 "APIKEY": "your-secret-key",
 "PROXY_URL": "http://127.0.0.1:7890",
 "LOG": true,
 "Providers": [
 {
 "name": "openrouter",
 "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
 "api_key": "sk-xxx",
 "models": [
 "google/gemini-2.5-pro-preview",
 "anthropic/claude-sonnet-4",
 "anthropic/claude-3.5-sonnet"
 ],
 "transformer": { "use": ["openrouter"] }
 },
 {
 "name": "deepseek",
 "api_base_url": "https://api.deepseek.com/chat/completions",
 "api_key": "sk-xxx",
 "models": ["deepseek-chat", "deepseek-reasoner"],
 "transformer": {
 "use": ["deepseek"],
 "deepseek-chat": { "use": ["tooluse"] }
 }
 },
 {
 "name": "ollama",
 "api_base_url": "http://localhost:11434/v1/chat/completions",
 "api_key": "ollama",
 "models": ["qwen2.5-coder:latest"]
 },
 {
 "name": "gemini",
 "api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
 "api_key": "",
 "models": ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.5-flash-lite-preview-06-17"],
 "transformer": {
 "use": ["gemini"]
 }
 },
 {
 "name": "kimi",
 "api_base_url": "https://api.moonshot.cn/v1/chat/completions",
 "api_key": "",
 "models": [
 "kimi-k2-0711-preview"
 ],
 "transformer": {
 "use": [
 "cleancache"
 ]
 }
 },
 ],
 "Router": {
 "default": "deepseek,deepseek-chat",
 "background": "ollama,qwen2.5-coder:latest",
 "think": "deepseek,deepseek-reasoner",
 "longContext": "openrouter,google/gemini-2.5-pro-preview"
 }
}

这里定义了不同的Provider,并且有一些模型可以有其设置。比如为了让DeepSeek模型更积极使用工具,有个tooluse的设置。比如为了转换Gemini模型,有个gemini的Transformer。 同时可看到,它还真是国人开发,很有本地化特色,比如显式支持PROXY设置方便你访问某些模型。

到这里配置好后,当你在使用Claude Code时,想切换模型时,可以输入/model命令,然后选择你想要的模型。比如: 在Claude Code Router中使用Kimi-K2

不过有点遗憾的是,当前通过CCR中还不支持Web搜索和图片上传,这离我们想要的完整体还是有点距离,但官方已经在计划中,并且这个项目最近更新很频繁,Star也涨得非常快。

折腾到这里就结束了吗?这里发生了一件小事,让我觉得有必要继续折腾一下。我使用OpenRouter来调用Claude模型,为了省钱,我已经很勤俭地只用 claude-3.7-sonnet 了,但几轮对话下来,发现账单还是有点夸张。我在一个不算太大的项目中进行了/init和简要对话而已。虽然OpenRouter提供了对每个KEY的费用限制(Credit limit),但是如Google的Gemini等模型,它就没有可以限制额度,那就只能等收到账单才后知后觉了?

问题不大,我想起来之前折腾过LiteLLM,它不仅能聚合LLM接口,还能像个贴心管家一样帮你控制预算。就决定是你了,继续折腾!

终极控制:使用LiteLLM统一LLM及控制支出

很早前想写一篇LiteLLM+Librechat的教程,但一直没时间,今天就让LiteLLM先出场吧。我继续在k8s中部署它,如果你是容器或其它方式,请参考官方文档,部署过程都是简单的。

配置LiteLLM

我们创建一个configmap定义了LiteLLM的配置文件config.yaml,大概内容如下:

 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
apiVersion: v1
kind: ConfigMap
metadata:
 name: litellm-config
 namespace: ai
data:
 config.yaml: |
 # LiteLLM配置
 model_list:
 # 添加Anthropic模型
 - model_name: "openrouter/anthropic/claude-3.7-sonnet"
 litellm_params:
 model: "openrouter/anthropic/claude-3.7-sonnet"
 api_key: os.environ/OPENROUTER_API_KEY
 - model_name: "openrouter/openai/gpt-4.1"
 litellm_params:
 model: "openrouter/openai/gpt-4.1"
 api_key: os.environ/OPENROUTER_API_KEY
 - model_name: "gemini/gemini-2.5-flash"
 litellm_params:
 model: "gemini/gemini-2.5-flash"
 api_key: os.environ/GEMINI_API_KEY

 # 服务器配置
 server_settings:
 port: 4000
 environment: production
 cors_allow_origins: ["*"]
 prometheus_metrics: true

 litellm_settings:
 # check_provider_endpoint: true # 👈 Enable checking provider endpoint for wildcard models
 # 禁用guardrails相关功能
 enable_guardrails: false
 enable_guardrails_in_db: false

部署LiteLLM

接着定义一个Deployment即可。

 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
apiVersion: apps/v1
kind: Deployment
metadata:
 name: litellm
 namespace: ai
spec:
 selector:
 matchLabels:
 app: litellm
 replicas: 1
 template:
 metadata:
 labels:
 app: litellm
 spec:
 volumes:
 - name: config-volume
 configMap:
 name: litellm-config
 nodeSelector:
 kubernetes.io/hostname: models
 containers:
 - name: litellm
 image: ghcr.io/berriai/litellm:v1.74.0-stable
 args:
 - "--config=/app/config.yaml"
 volumeMounts:
 - name: config-volume
 mountPath: /app/config.yaml
 subPath: config.yaml
 env:
 - name: TZ
 value: "Asia/Shanghai"
 - name: DISABLE_SCHEMA_UPDATE
 value: "true"
 - name: LITELLM_LOG
 value: "DEBUG"
 - name: DATABASE_URL
 value: "postgresql://root:[email protected]:5432/litellm"
 - name: LITELLM_SALT_KEY
 value: "xxxx"
 - name: STORE_MODEL_IN_DB
 value: "True"
 - name: OPENAI_API_KEY
 value: "xxxx"
 - name: AZURE_API_KEY
 value: "xxxx"
 - name: GEMINI_API_KEY
 value: "xxxx"
 - name: DEEPSEEK_API_KEY
 value: "xxxx"
 - name: OPENROUTER_API_KEY
 value: "xxxx"
 - name: HTTP_PROXY
 value: "<your proxy>"
 - name: HTTPS_PROXY
 value: "<your proxy>"
 - name: NO_PROXY
 value: "localhost,127.0.0.1,postgresql.base-system.svc.cluster.local,postgresql,10.43.140.217"
 ports:
 - containerPort: 4000
 livenessProbe:
 httpGet:
 path: /health/liveliness
 port: 4000
 initialDelaySeconds: 40
 periodSeconds: 30
 timeoutSeconds: 10
 failureThreshold: 3
 resources:
 requests:
 cpu: 100m
 memory: 128Mi
 limits:
 cpu: 500m
 memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
 name: litellm
 namespace: ai
spec:
 selector:
 app: litellm
 ports:
 - protocol: TCP
 port: 4000
 targetPort: 4000
 type: ClusterIP

要注意LiteLLM启动时,有时资源消耗会比较高,我的弱鸡k8s节点时不时会给搞得濒死,最好像上面限制一下资源。 我们可以测试一下LiteLLM对外的接口是否正常,比如:

1
2
3
4
5
6
7
curl https://litellm.mysite.com/v1/chat/completions \
 -H "Content-Type: application/json" \
 -H "Authorization: Bearer $LITELLM_KEY" \
 -d '{
 "model": "gemini-2.5-flash",
 "messages": [{"role": "user", "content": "What is the capital of France?"}]
 }'

正常返回后,说明我们的LiteLLM服务工作正常。接下来我们就可以在claude-code-router中统一使用litellm作为唯一的Provider了。

在claude-code-router中使用litellm

现在,我们的config.json可以变得非常清爽,Providers里只留下litellm一个就行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
 "Providers": [
 {
 "name": "litellm",
 "api_base_url": "https://litellm.mysite.com/v1/chat/completions",
 "api_key": "xxxx",
 "models": [ "deepseek/deepseek-chat","deepseek/deepseek-reasoner","moonshot/kimi-k2-0711-preview","gemini/gemini-2.5-flash-lite", "gemini/gemini-2.5-flash", "openrouter/google/gemini-2.5-flash","azure/gpt-4.1", "gemini/gemini-2.5-pro","openrouter/anthropic/claude-3.7-sonnet"],
 "transformer": {
 "deepseek/deepseek-chat": { "use": ["maxtoken", { "max_tokens": 8192 }] },
 "gemini/gemini-2.5-flash-lite": { "use": ["cleancache"] },
 "gemini/gemini-2.5-flash": { "use": ["cleancache"] },
 "moonshot/kimi-k2-0711-preview": { "use": ["cleancache"] }
 }
 }
 ],
 "Router": {
 "default": "litellm,moonshot/kimi-k2-0711-preview",
 "background": "litellm,deepseek/deepseek-chat",
 "think": "litellm,deepseek/deepseek-reasoner",
 "longContext": "litellm,gemini/gemini-2.5-pro"
 }
}

这里要注意,如果直接对接官方的Gemini模型,只需要配置Gemini的Transformer即可。但这里咱们是通过LiteLLM调用的,还需要配置use: cleancache的Transformer。不然会报类似下面这样的错误:

⎿ API Error: 400 {“error”:{“message”:“Error from provider: {"error":{"message":"litellm.BadRequestError: VertexAIException BadRequestError - {\n \"error\": {\n \"code\": 400,\n \"message\": \"* GenerateContentRequest.contents: contents is not specified\\n\",\n \"status\": \"INVALID_ARGUMENT\"\n }\n}\n. Received Model Group=gemini/gemini-2.5-pro\nAvailable Model Group Fallbacks=None","type":null,"param":null,"code":"400"}}”,“type”:“api_error”,“code”:“provider_response_error”}}

还好LiteLLM的日志相当给力,我通过排查请求体,很快就定位到问题出在 “cache_control” 这个字段上——删掉它就一切正常了。最后我们可以在LiteLLM的管理端看到每次Claude Code发出了哪些请求,使用了多少Token,花费了多少钱等。 LiteLLM管理端

我们也可以在LiteLLM中创建的API_KEY中定义它的额度,这样避免我们不小心超支。 LiteLLM API_KEY额度

现在,让我们开心的在Claude Code中使用各种模型吧!

总结

本文介绍了三种方式让你更好的基于第三方大语言模型来使用Claude Code,希望对你有所帮助。我们除了直接使用Kimi-K2外,还可以使用CCR来扩展模型库,最后通过LiteLLM来统一LLM的调用,这样也能让我们更精细化的观察Token的使用以及控制费用。

三种方案对比:

特性 方案一:Kimi 直连 方案二:CCR 方案三:CCR + LiteLLM
设置简易度 ⭐⭐⭐⭐⭐ (极简) ⭐⭐⭐⭐ (简单) ⭐⭐ (较复杂)
模型丰富度 ⭐ (仅 Kimi) ⭐⭐⭐⭐ (丰富) ⭐⭐⭐⭐⭐ (最丰富)
费用控制力 ⭐⭐ (依赖 Kimi 平台) ⭐⭐ (依赖上游) ⭐⭐⭐⭐⭐ (精准控制)
折腾系数 ⭐⭐⭐ ⭐⭐⭐⭐⭐

以上便是最近关于Claude Code的一点折腾经验,希望对你有帮助。欢迎点赞、收藏、分享,更欢迎分享你的使用经验。我们下篇文章见。

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

扫码关注公众号