☰
Chatlog 实战:解密微信本地库,用 MCP 与 HTTP API 接入 AI 检索
2026/10/2 1:25:17 网站建设 项目流程

简介:Chatlog 是 sjzar 基于 Go 语言开源的跨平台工具,核心定位是把散落在本地数据库中的微信聊天记录转化为可搜索、可调用的结构化数据,面向需要批量检索聊天内容、或将微信数据接入 AI 助手的开发者与进阶用户。它兼容微信 3.x 与 4.0 客户端,无需 root 或越狱即可读取并解密消息文件,支持 Windows 与 macOS。资源包共 139 个文件,以 123 个 go 源码为主体,辅以 proto 接口定义、yml/yaml 配置、Makefile 构建脚本及 md 说明文档,整体约 213KB,结构紧凑、便于二次开发。功能上覆盖本地数据自动发现与多账号切换、密钥提取与数据库解密、图片语音视频等加密附件实时解码,并提供 REST API 与基于 MCP 的 SSE 增量推送双栈输出,可无缝对接支持 MCP 的 AI 助手;同时具备 Terminal UI 与命令行两种交互方式,适配自动化脚本与 DevOps 场景。目前已有 939 人学习下载,适合研究微信数据解析、构建个人知识库或开发 AI 对话上下文的读者参考。

1. 从一堆加密数据库到可检索的聊天档案:Chatlog 到底解决了什么

微信 PC 端的聊天记录存在本地一个加密的 SQLite 数据库里,路径通常在Documents\xwechat_files\wxid_xxx\db_storage下面,文件名类似message_0.db、media_0.db。直接拿 SQLite 工具打开会报「file is not a database」,因为前 4096 字节被异或加密了。想查一句「去年双十一到底谁跟我借了钱」,靠微信自带的搜索框翻半天翻不到,导出功能又只给文本、丢了图片和语音。Chatlog 这个 Go 语言写的工具就是冲着这个痛点来的:它把本地加密库解密、合并、建索引,对外暴露 HTTP API 和 MCP 协议接口,让 AI 客户端能直接查你的聊天记录。适合两类人——想给自己做聊天归档和全文检索的普通用户,以及想把微信数据接进 AI 工作流的开发者。源码是 Go 单二进制,跨平台编译,不依赖运行时。

2. 解密与建库:Chatlog 的数据链路拆解

2.1 微信本地库的加密结构

微信 PC 端每个账号一个目录,db_storage下按消息类型分库:message存文本和系统消息,media存图片视频的元信息,contact存联系人,session存会话列表。每个库文件头部 4096 字节用账号相关的密钥做异或,密钥本身又跟登录态绑定。Chatlog 的做法不是去逆向微信进程内存,而是走「已知密钥 + 文件头解密」的路线:它需要你提供密钥,或者从运行中的微信进程里读。常见做法是用chatlog key子命令尝试自动获取,失败时手动填。

解密后的库是标准 SQLite,Chatlog 会把它们复制到自己的工作目录,避免直接操作原库导致微信写入冲突。这一步很关键——微信在运行时会锁库,直接读可能拿到不一致的快照。

2.2 建索引与数据模型

解密只是第一步,真正让查询快起来的是索引。Chatlog 在合并后的库上建了针对message表的索引,字段包括talker(会话 ID)、create_time(时间戳)、type(消息类型)、content(内容)。消息类型用整数编码:1 是文本,3 是图片,34 是语音,43 是视频,49 是引用或链接卡片。查询时按talker和时间范围过滤,再对content做 LIKE 或全文检索。

-- Chatlog 内部建索引的核心语句(简化) CREATE INDEX IF NOT EXISTS idx_message_talker_time ON message(talker, create_time); CREATE INDEX IF NOT EXISTS idx_message_type ON message(type); -- 全文检索表,用于 content 模糊匹配 CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts5(content, content='message', content_rowid='local_id');

逻辑说明:idx_message_talker_time是复合索引,先按会话再按时间,覆盖「查某个人最近的消息」这个最高频场景。message_fts用 SQLite 的 FTS5 扩展做全文检索,比LIKE '%关键词%'快一个数量级,代价是建索引时多占磁盘。参数上,content_rowid指向原表主键,保证检索结果能回表拿完整记录。

2.3 启动服务与 API 验证

编译或下载二进制后,第一步是初始化:

# 初始化工作目录,默认在 ~/.chatlog ./chatlog init # 尝试自动获取密钥并解密 ./chatlog decrypt # 启动 HTTP 服务,默认监听 127.0.0.1:5030 ./chatlog server --addr 127.0.0.1:5030

init会在用户目录下建~/.chatlog,里面放解密后的库和索引。decrypt是幂等的,重复跑会跳过已解密的库。server启动后,用 curl 验证:

# 查最近 10 条消息 curl "http://127.0.0.1:5030/api/v1/messages?limit=10" # 按联系人昵称搜索 curl "http://127.0.0.1:5030/api/v1/search?keyword=借钱&talker=张三"

返回是 JSON 数组,每条含talker、create_time、type、content。如果返回空,先确认decrypt是否成功——看~/.chatlog下有没有.db文件,以及文件大小是否正常(几 MB 到几百 MB 不等)。

3. 把聊天记录接进 AI:MCP 协议与 HTTP 接口实战

3.1 MCP 是什么,Chatlog 怎么实现

MCP(Model Context Protocol)是一套让 AI 客户端调用外部工具的协议,本质是 JSON-RPC over stdio 或 HTTP。Chatlog 实现了 MCP server,把「查消息」「搜关键词」「列联系人」包装成 tool,AI 客户端连上后就能在对话里直接调。这比让 AI 读导出的文本文件强在两点:一是实时,二是能按结构化条件过滤,不用把整个聊天记录塞进上下文。

Chatlog 的 MCP 实现走 stdio 模式,配置里指定命令和参数即可。常见客户端配置片段:

{ "mcpServers": { "chatlog": { "command": "/path/to/chatlog", "args": ["mcp", "--addr", "127.0.0.1:5030"] } } }

逻辑说明:command指向 chatlog 二进制,args里mcp是子命令,--addr告诉它去连已经跑起来的 HTTP 服务。这样 MCP 层只做协议转换,数据查询还是走本地 HTTP,职责分离。参数上,如果 HTTP 服务没启动,MCP 会报连接拒绝,所以顺序是先server再配 MCP。

3.2 用 HTTP API 做自定义集成

不想用 MCP 的话,直接调 HTTP API 更灵活。Chatlog 的 API 设计偏 RESTful,几个核心端点:

端点方法参数用途
/api/v1/messagesGETtalker,limit,offset按会话拉消息
/api/v1/searchGETkeyword,talker,start,end关键词搜索
/api/v1/contactsGET无列联系人
/api/v1/sessionsGET无列会话

用 Python 写个批量导出某人的聊天记录:

import requests BASE = "http://127.0.0.1:5030/api/v1" def export_chat(talker, start_ts, end_ts): """导出指定会话在时间范围内的消息""" params = { "talker": talker, "start": start_ts, # Unix 时间戳,秒 "end": end_ts, "limit": 500 # 单次上限,超过要翻页 } all_msgs = [] offset = 0 while True: params["offset"] = offset resp = requests.get(f"{BASE}/messages", params=params, timeout=10) resp.raise_for_status() batch = resp.json() if not batch: break all_msgs.extend(batch) offset += len(batch) return all_msgs msgs = export_chat("wxid_abc123", 1700000000, 1730000000) print(f"共 {len(msgs)} 条")

逻辑说明:limit设 500 是经验值,太大单次响应慢,太小翻页次数多。offset翻页在数据量大时会有性能问题,更好的做法是用start递增——每次拿最后一条的create_time作为下次的start。参数上,talker可以是 wxid 也可以是备注名,Chatlog 内部会做映射,但备注名有重名风险,生产环境建议用 wxid。

3.3 消息类型处理与内容清洗

拿到消息后,type字段决定怎么解析content。文本直接可用;图片和视频的content是 XML 片段,含 CDN 地址和本地路径;语音是 SILK 格式,需要转码。常见做法是只处理文本和引用,媒体单独走文件路径。

import xml.etree.ElementTree as ET def parse_content(msg): """按消息类型解析 content""" t = msg["type"] raw = msg["content"] if t == 1: return raw elif t == 49: # 引用或链接卡片,content 是 XML try: root = ET.fromstring(raw) title = root.findtext(".//title") or "" return f"[卡片] {title}" except ET.ParseError: return "[卡片解析失败]" elif t == 3: return "[图片]" elif t == 34: return "[语音]" else: return f"[类型{t}]"

逻辑说明:type 49的 XML 结构随微信版本变,findtext用.//做模糊查找,避免路径写死。ET.ParseError要捕获,因为有些卡片内容不是合法 XML。参数上,如果要做全文检索,建议把解析后的纯文本另存一列,而不是每次查询都解析。

4. 避坑与排查:密钥、锁库、编码这三道坎

4.1 密钥获取失败,decrypt 报「no key found」

现象:跑chatlog decrypt提示找不到密钥,~/.chatlog下没有 db 文件。原因通常是微信没登录,或者微信版本更新后密钥存储位置变了。Chatlog 自动获取依赖读微信进程内存或配置文件,新版本微信可能改了偏移。解决:先确认微信 PC 端已登录且保持运行;如果还不行,手动指定密钥——用chatlog key --manual按提示输入,密钥可以从社区工具或自己逆向拿到。注意密钥跟账号绑定,换账号要重新获取。

4.2 解密后的库查询报「database is locked」

现象:API 返回 500,日志里是database is locked。原因是 Chatlog 的工作库和微信原库在同一个磁盘,微信写入时产生锁竞争。解决:把~/.chatlog放到另一块盘,或者用--work-dir指定独立目录。另外,decrypt完成后尽量停掉微信再查,减少锁冲突。如果必须边用微信边查,把查询超时调大,在配置里设busy_timeout=5000。

4.3 中文搜索搜不到,英文正常

现象:/api/v1/search?keyword=借钱返回空,但搜hello有结果。原因是 FTS5 默认分词器对中文按字符切,借钱被切成借和钱两个 token,而查询时按整词匹配。解决:建 FTS 表时指定tokenize='unicode61'或装simple分词器;更简单的做法是查询时把关键词拆成单字用 OR 连接。Chatlog 较新版本已经处理了这点,如果用的是旧版,手动改 FTS 配置。

4.4 媒体文件路径失效

现象:消息里图片的content指向一个本地路径,但文件不存在。原因是微信会定期清理缓存,或者你换了设备。解决:Chatlog 只能索引元信息,文件本身要自己备份。常见做法是定期把msg\attach目录整个拷出来,跟 Chatlog 的库放一起,查询时用相对路径拼。别指望 Chatlog 帮你恢复已删文件,它不做数据恢复。

4.5 端口冲突导致 server 起不来

现象:chatlog server报bind: address already in use。原因是 5030 被占,或者上次没退干净。解决:lsof -i :5030找到进程 kill 掉,或者换端口--addr 127.0.0.1:5031。换端口后记得同步改 MCP 配置里的--addr,否则 MCP 连不上。

5. 进阶:用 Chatlog 做个人知识库的检索层

把 Chatlog 当检索层,上面接一个 RAG 流程,是我觉得最有价值的用法。具体做法:先用 API 把某个会话的全部文本消息拉下来,按天切片,每片做 embedding 存向量库;查询时先向量召回,再用 Chatlog 的精确搜索做二次过滤。这样既保留了语义检索的模糊匹配,又能用talker和时间范围收窄。

import requests, hashlib from datetime import datetime BASE = "http://127.0.0.1:5030/api/v1" def build_daily_chunks(talker): """按天聚合消息,生成待 embedding 的文本块""" msgs = [] offset = 0 while True: r = requests.get(f"{BASE}/messages", params={"talker": talker, "limit": 500, "offset": offset}) batch = r.json() if not batch: break msgs.extend(batch) offset += len(batch) days = {} for m in msgs: if m["type"] != 1: # 只处理文本 continue day = datetime.fromtimestamp(m["create_time"]).strftime("%Y-%m-%d") days.setdefault(day, []).append(m["content"]) chunks = [] for day, texts in days.items(): text = "\n".join(texts) chunks.append({ "id": hashlib.md5(f"{talker}{day}".encode()).hexdigest(), "day": day, "text": text[:2000] # 单块截断,避免超 token }) return chunks

逻辑说明:按天聚合是因为聊天记录天然按时间组织,一天一块语义相对完整。text[:2000]截断是防止单天消息过多导致 embedding 超长,2000 字符大约 1000 token,对多数模型安全。id用 md5 保证幂等,重复跑不会产生重复块。参数上,talker建议用 wxid,避免备注名变更导致块 ID 变化。

验证方法:拿一个你知道答案的问题,比如「上个月谁提过项目排期」,先走向量检索拿到候选天,再用 Chatlog 的/search在候选天范围内精确搜「排期」,对比两次结果。如果向量召回漏了,说明切片粒度太粗,改成按半天或按会话轮次切。

一个具体技巧:Chatlog 的start和end参数接受 Unix 时间戳,但微信消息的create_time是秒级,别传毫秒,否则范围全空。我踩过这个坑,查了半天以为是索引没建好。从那以后我每次调时间范围接口,都先用date +%s确认单位,再拼参数。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询