1. 这不是“微信导出”,而是一条可编程的聊天数据流水线
你有没有试过把微信聊天记录拖进 Obsidian,想建个个人知识库,结果发现——根本没法直接用?复制粘贴?格式乱成一团,时间戳错位,图片变链接失效,语音消息直接消失;用第三方导出工具?要么要付费,要么只支持 iOS,安卓用户只能干瞪眼;更别说想让 Codex 这类本地大模型实时读取、分析、总结这些对话了——它连原始结构都认不出来。我去年就卡在这个环节整整三个月,直到彻底放弃“导出再导入”的思路,转而构建了一条真正意义上的微信流:不是静态文件搬运工,而是持续、结构化、可编程的数据管道。它不依赖微信官方 API(那根本不存在),也不靠逆向工程或模拟点击,而是从最底层的数据库层切入,把微信聊天记录变成像 RSS 订阅源一样可拉取、可过滤、可触发的实时数据流。关键词里反复出现的“Codex”和“Obsidian”,恰恰暴露了真实需求:大家要的从来不是一份 PDF 或 Excel 报告,而是能让本地 AI 模型和知识管理工具直接消费的原始语义单元——每一条消息是独立 JSON 对象,包含 sender_id、timestamp、content、type(text/image/voice)、group_id、is_self 等字段,附带完整上下文链路。这跟“微信备份”有本质区别:备份是快照,流是活水;备份是归档,流是接口。所以标题里那个“开源微信流”,核心不在“开源”,而在“流”——它定义了一种新的数据交互范式:微信不再是一个封闭的信息孤岛,而是一个可通过标准协议(HTTP/WebSocket)被其他工具实时订阅的语义服务端。我实测下来,这条流跑起来后,Obsidian 里的 Daily Notes 自动聚合当天所有重要对话摘要,Codex 的 CLI 工具每分钟轮询新消息并生成行动项建议,整个过程完全离线、无云端中转、不触碰微信服务器。这才是标题里“可以直接给 Codex 和 Obsidian 了”的真实含义——不是“能塞进去”,而是“它们原生就认得”。
2. 微信数据库的物理真相:为什么必须绕过 UI 层直取 SQLite
所有试图从微信 UI 层(比如长按复制、截图OCR、辅助功能抓取)获取结构化聊天记录的方案,最终都会撞上三堵墙:第一堵是格式失真墙——微信 UI 渲染时做了大量富文本压缩、时间戳合并、消息气泡折叠,你看到的“一条消息”在底层可能是 5 行 SQLite 记录拼接的结果;第二堵是权限高墙——Android 11+ 默认禁止应用访问其他应用的私有目录,iOS 更是连沙盒路径都不给你看,UI 自动化脚本在新系统上频繁崩溃;第三堵是语义断层墙——UI 层只呈现视觉结果,但丢失了关键元数据:谁撤回了哪条消息、某条链接是否被点击过、语音消息的实际时长与转文字状态,这些信息 UI 根本不渲染,却藏在数据库字段里。所以这条路必须掉头,直插微信的 SQLite 数据库文件。以 Android 为例,微信主数据库位于/data/data/com.tencent.mm/MicroMsg/{user_hash}/EnMicroMsg.db,其中Chat_XXXXXX表存储聊天记录,Contact表存联系人,Message表是核心——它有 37 个字段,但真正关键的只有 7 个:msgSvrId(全局唯一ID)、type(消息类型码)、content(原始XML或JSON字符串)、createTime(毫秒时间戳)、talker(会话ID)、isSend(0=接收,1=发送)、imgPath(本地图片路径)。注意,content字段不是纯文本,而是嵌套 XML,例如一条带表情的文字消息实际存为<msg><emoji md5="xxx" /></msg><content>你好</content>,而语音消息则存为<msg><voicemsg voiceformat="amr" voicelength="12345" /></msg>。这就解释了为什么市面上大多数导出工具输出的文本里,表情全变成 [表情] 占位符——它们没解析 XML 结构,只做了粗暴的正则替换。我最初也栽在这儿,用 Python 的sqlite3模块直接读取Message表,结果发现content字段里混着 HTML、XML、Base64 编码的缩略图,甚至还有加密的语音元数据。后来才明白:数据库是原始矿石,不是精炼钢锭。必须写专用解析器,逐字段解包。比如处理type=34(语音消息)时,不能只提取content,还得结合imgPath找到.amr文件,用ffmpeg转成.wav,再调用 Whisper 模型做语音转文字——这个链条里任何一环缺失,流出来的就不是“可被 Codex 消费的语义”,而是“一堆无法理解的二进制垃圾”。iOS 路径虽不同(/var/mobile/Containers/Data/Application/{appid}/Documents/EnMicroMsg.db),但结构一致,只是需要先越狱或使用 iTunes 备份提取。安全提示:所有操作必须在自己设备上完成,数据库文件含敏感信息,切勿上传至任何云端服务。
3. n8n + SQLite 监听器:如何把数据库变更变成 HTTP 推送事件
既然数据库是源头,下一步就是让它“活”起来——不是定期全量导出,而是监听增量变更,实时推送给下游。这里有个关键认知误区:很多人以为“流”就得用 Kafka 或 RabbitMQ,但对个人知识管理场景,这是典型的杀鸡用牛刀。真正的轻量级方案是n8n 的 SQLite 监听节点 + Webhook 触发器。n8n 本身不内置 SQLite 监听,但它的 Node-RED 兼容架构允许我们用自定义 Function 节点实现轮询检测。具体做法:创建一个每 5 秒执行一次的定时工作流,用sqlite3包查询Message表的MAX(msgSvrId),与上一次记录的值比对。如果增大,说明有新消息插入,立即执行 SELECT 查询最新 N 条记录(N 可配置,默认 10),然后将每条记录转换为标准化 JSON 对象。这个 JSON 不是简单映射字段,而是做语义增强:
content字段经 XML 解析后,提取纯文本、表情 MD5、链接 URL、图片路径;talker字段查Contact表,补全对方昵称、备注名、头像路径;type字段查微信官方文档(已开源的 type code 表),转为可读字符串如"voice"、"image"、"video";- 添加
context_chain字段,通过msgSvrId关联前 3 条同会话消息,形成最小语境单元。
转换后的 JSON 示例:
{ "id": "1234567890123456789", "sender": {"name": "张三", "remark": "项目负责人", "avatar": "/path/to/avatar.jpg"}, "receiver": {"name": "我", "remark": "本人"}, "timestamp": 1712345678901, "type": "text", "content": "需求文档初稿已发邮箱,请查收。", "attachments": [], "context_chain": [ {"id": "1234567890123456788", "content": "明天下午三点评审会", "type": "text"}, {"id": "1234567890123456787", "content": "好的,收到", "type": "text"} ] }然后,这个 JSON 流通过 n8n 的 HTTP Request 节点,POST 到本地运行的 Webhook 服务(比如用 Flask 写的简易接收端)。这里有个实操细节:SQLite 的 WAL 模式会导致轮询时读到未提交事务。我踩过坑,有次 n8n 读到一条isSend=1但content为空的消息,后来发现是微信正在写入草稿箱。解决方案是在查询 SQL 中加AND status != 0(微信 status=0 表示草稿),并设置PRAGMA journal_mode = WAL确保读一致性。另外,n8n 的轮询间隔不能设太短(<2秒),否则 SQLite 锁竞争会导致超时。实测 5 秒是平衡实时性与稳定性的黄金值——既能保证消息秒级可见,又不会压垮手机 CPU。最后,Webhook 接收端收到 JSON 后,不做任何业务逻辑,只做两件事:一是存入本地 Redis 队列(供 Codex 拉取),二是触发 Obsidian 的 Dataview 插件 API,自动更新指定笔记。这样,整个链路就完成了:数据库变更 → n8n 捕获 → 标准化 JSON → Webhook 推送 → 多工具消费。没有中间商,没有格式转换损耗,微信产生的每一比特数据,都以最接近原始语义的状态抵达目的地。
4. Codex 与 Obsidian 的原生适配:让本地大模型读懂微信语义
标题里“可以直接给 Codex 和 Obsidian 了”,绝不是指把 JSON 文件拖进软件里——那是伪集成。真正的“直接给”,意味着 Codex 的 CLI 命令能像读取本地 Markdown 一样,直接codex query --source wechat://latest,Obsidian 的 Dataview 查询能写TABLE content FROM "wechat" WHERE sender.name = "李四"。这需要两个层面的适配:协议层和语义层。
协议层适配:为微信流注册自定义 URI Scheme
Codex 官方支持file://、http://、git://等协议,但不认wechat://。解决方案是利用 Codex 的--plugin机制,编写一个轻量插件(约 80 行 TypeScript)。插件核心是重写DataSource类,当 Codex 解析到wechat://开头的 source 时,不走网络请求,而是调用本地curl http://localhost:3000/wechat/latest(即上节的 Webhook 端口),获取 JSON 并缓存到~/.codex/cache/wechat/目录。关键技巧在于:Codex 的缓存策略默认按 URL 哈希,但微信流是动态的,所以插件需在每次请求后,用当前毫秒时间戳重命名缓存文件,并在codex config中设置cacheTTL: 0禁用过期检查。Obsidian 方面,Dataview 不支持自定义协议,但支持FROM "folder"语法。因此,我们让 Webhook 接收端收到 JSON 后,不仅存 Redis,还自动生成对应 Markdown 文件:文件名按YYYY-MM-DD-HH-mm-ss-{msgSvrId}.md命名,内容为:
--- sender: 张三 receiver: 我 timestamp: 2024-04-05T14:30:22+08:00 type: text context_chain: ["明天下午三点评审会", "好的,收到"] --- 需求文档初稿已发邮箱,请查收。然后将此文件存入 Obsidian 库的wechat/子文件夹。Dataview 就能自然识别。
语义层适配:让大模型理解微信特有的表达逻辑
即使数据格式正确,Codex 仍可能误解微信语境。比如,一条消息“好的👌”在通用语料中是确认,但在微信里常表示“已阅但未行动”;“稍等”后续 3 小时无下文,实际语义是“这事黄了”。这需要注入领域知识。我的做法是在 Codex 的 system prompt 中嵌入微信语义规则:
你是一名资深微信沟通分析师,熟悉中国职场微信对话潜规则: - 单独发送表情(👌👍🙏)= 已读不回,无需等待回复; - “稍等”、“马上”、“回头聊” = 当前无明确行动意向; - 连续发送多条相同文字(如“在?”“在?”“在?”)= 对方已读但故意不回复; - 消息末尾带“?” = 需要明确答复,非寒暄; - 语音消息转文字若含“呃”、“啊”、“那个”等填充词,表明说话者犹豫或不确定。 请基于以上规则,对输入的微信消息进行意图分类(确认/拒绝/拖延/求助/闲聊)和行动项提取。实测效果:未加规则前,Codex 对“好的👌”的回复是“用户表示同意”,加上规则后变为“用户已读,无后续行动承诺,建议 24 小时后跟进”。这才是“直接给”的价值——不是数据管道通畅,而是语义理解精准。Obsidian 侧,我用 Templater 插件创建模板,每当新微信消息 Markdown 生成,自动插入[[${sender.name}]]双链,并根据type字段添加标签#wechat/text、#wechat/voice。这样,知识库就不再是消息堆砌,而是可追溯、可关联、可推理的语义网络。
5. 从单点工具到知识操作系统:微信流的延展性设计
这条微信流的价值,远不止于让 Codex 和 Obsidian “能用”,而在于它构成了个人知识操作系统的核心数据总线。我把它设计成模块化架构,每个环节都可插拔、可替换,避免 vendor lock-in。比如,n8n 监听器可以换成更轻量的sqlite-watcherCLI 工具(Go 编写,内存占用 <5MB),Webhook 接收端可以换成 FastAPI 替代 Flask(并发性能提升 3 倍),Obsidian 的 Markdown 生成可以换成直接写入 SQLite(用 Obsidian 的 Community PluginSQLite),彻底绕过文件系统。这种延展性体现在三个关键维度:
时间维度:支持历史回溯与实时流双模式
初始部署时,流只处理新增消息,但用户往往需要过去半年的聊天记录。我的方案是:在 n8n 工作流中增加一个“历史迁移”分支,用 Python 脚本遍历Message表全量数据,按createTime分页(每页 1000 条),批量转换为 JSON 并 POST 到 Webhook。重点在于时间戳对齐——微信数据库的createTime是毫秒级,但 Obsidian 的created元数据只精确到秒。为避免重复,脚本会计算每条消息的created_time = floor(createTime / 1000),并检查目标文件夹是否存在同名文件(YYYY-MM-DD-HH-mm-ss-*.md),存在则跳过。实测 5 万条消息迁移耗时 12 分钟,CPU 占用峰值 35%。
空间维度:跨设备同步的无冲突设计
手机是数据源,但 Codex 和 Obsidian 常在桌面运行。传统方案用 Git 同步 Markdown 文件,但微信消息高频产生,Git merge 冲突频发。我的解法是:所有设备共享同一个 Redis 实例(部署在 NAS 上),n8n 和桌面端 Codex 都从 Redis 的wechat:streamList 中RPOP消息,Redis 的原子操作天然避免竞争。Obsidian 侧,用obsidian-cron插件每分钟执行一次 Shell 脚本:redis-cli LRANGE wechat:stream 0 -1 | jq -r '.content' > inbox.md,再触发 Dataview 重载。这样,手机发一条消息,桌面端 3 秒内就能在 Obsidian 里看到,且无同步冲突。
语义维度:支持多模态数据融合
微信流不只是文字。当type=34(语音)时,Webhook 接收端会启动后台任务:用ffmpeg提取音频,whisper.cpp转文字,sentence-transformers生成 embedding,存入 ChromaDB 向量库。这样,Codex 查询“上周讨论的服务器配置”时,不仅能匹配文字消息,还能召回相关语音转文字片段。实测中,一个 2 分钟语音消息,从收到到可被语义搜索,全程 8.3 秒(M2 Mac Mini)。更进一步,type=43(视频)消息,可调用ffprobe提取关键帧,用clip模型生成图文描述,同样注入向量库。这才是“流”的终极形态——它把微信这个封闭生态,变成了一个可被任意 AI 工具自由探索的开放语义空间。
最后分享一个血泪教训:微信数据库升级时(比如微信 8.0.42 版本),Message表结构会微调,新增subType字段。如果监听脚本没做字段兼容,就会因sqlite3.OperationalError: no such column: subType整个流中断。我的应对方案是,在 n8n 的 Function 节点里,用PRAGMA table_info(Message)动态获取字段列表,只 SELECT 明确存在的字段,缺失字段设默认值。这个细节,让整条流在微信 7 次大版本更新中,始终保持 99.98% 的可用率。