Update from Sync Service
This commit is contained in:
266
wiki/AI工程/ClaudeCode对话导出Obsidian.md
Executable file
266
wiki/AI工程/ClaudeCode对话导出Obsidian.md
Executable file
@@ -0,0 +1,266 @@
|
|||||||
|
---
|
||||||
|
created: 2026-05-04
|
||||||
|
type: guide
|
||||||
|
tags: [Claude Code, Obsidian, 数据导出, 对话存档, SQLite]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Claude Code 对话导出到 Obsidian 完整方案
|
||||||
|
|
||||||
|
> 将所有 Claude Code 对话会话存档到 Obsidian 知识库
|
||||||
|
> 归档时间:2026-05-04
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 原理
|
||||||
|
|
||||||
|
Claude Code 的对话数据存储在本地 SQLite 数据库中:
|
||||||
|
|
||||||
|
**数据库路径**:
|
||||||
|
- macOS/Linux:`~/.claude/CLAUDE.md` 同级目录下的 `~/.claude/conversations.db`
|
||||||
|
- 实际数据库文件:`~/.claude/*.db` 或 `~/.claude/PROJECT_ID/*.db`
|
||||||
|
|
||||||
|
**数据库结构**(典型):
|
||||||
|
- `conversations` 表:会话元数据(ID、标题、创建时间等)
|
||||||
|
- `messages` 表:对话消息(角色、内容、时间戳)
|
||||||
|
- `attachments` 表:附件/文件引用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 方案一:Python 脚本导出(推荐)
|
||||||
|
|
||||||
|
### 1. 导出脚本
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
claude_to_obsidian.py
|
||||||
|
将 Claude Code 所有对话导出为 Obsidian Markdown 笔记
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 配置
|
||||||
|
CLAUDE_DB_PATH = os.path.expanduser("~/.claude/conversations.db")
|
||||||
|
OBSIDIAN_PATH = "/obsidian/ClaudeCode对话/" # 你的 Obsidian vault 路径
|
||||||
|
|
||||||
|
def export_all():
|
||||||
|
if not os.path.exists(CLAUDE_DB_PATH):
|
||||||
|
print(f"❌ 数据库不存在: {CLAUDE_DB_PATH}")
|
||||||
|
print("请确认 Claude Code 已安装并运行过")
|
||||||
|
return
|
||||||
|
|
||||||
|
os.makedirs(OBSIDIAN_PATH, exist_ok=True)
|
||||||
|
|
||||||
|
conn = sqlite3.connect(CLAUDE_DB_PATH)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 获取所有会话
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT c.id, c.title, c.created_at, c.updated_at,
|
||||||
|
COUNT(m.id) as msg_count
|
||||||
|
FROM conversations c
|
||||||
|
LEFT JOIN messages m ON c.id = m.conversation_id
|
||||||
|
GROUP BY c.id
|
||||||
|
ORDER BY c.updated_at DESC
|
||||||
|
""")
|
||||||
|
|
||||||
|
conversations = cursor.fetchall()
|
||||||
|
print(f"📊 找到 {len(conversations)} 个会话")
|
||||||
|
|
||||||
|
exported = 0
|
||||||
|
for conv in conversations:
|
||||||
|
title = conv['title'] or f"untitled-{conv['id'][:8]}"
|
||||||
|
# 文件名安全处理
|
||||||
|
safe_title = "".join(c for c in title if c not in r'\/:*?"<>|')
|
||||||
|
safe_title = safe_title[:80] # 限制长度
|
||||||
|
filename = f"{safe_title}.md"
|
||||||
|
filepath = os.path.join(OBSIDIAN_PATH, filename)
|
||||||
|
|
||||||
|
# 获取该会话的所有消息
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT role, content, created_at
|
||||||
|
FROM messages
|
||||||
|
WHERE conversation_id = ?
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
""", (conv['id'],))
|
||||||
|
|
||||||
|
messages = cursor.fetchall()
|
||||||
|
|
||||||
|
# 生成 Markdown
|
||||||
|
md = generate_markdown(conv, messages)
|
||||||
|
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(md)
|
||||||
|
|
||||||
|
exported += 1
|
||||||
|
print(f" ✅ {title} ({len(messages)} 条消息)")
|
||||||
|
|
||||||
|
print(f"\n🎉 完成!导出 {exported} 个会话到 {OBSIDIAN_PATH}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def generate_markdown(conv, messages):
|
||||||
|
created = datetime.fromtimestamp(conv['created_at']).strftime('%Y-%m-%d %H:%M:%S') if conv['created_at'] else 'unknown'
|
||||||
|
updated = datetime.fromtimestamp(conv['updated_at']).strftime('%Y-%m-%d %H:%M:%S') if conv['updated_at'] else 'unknown'
|
||||||
|
|
||||||
|
md = f"""---
|
||||||
|
created: {created}
|
||||||
|
updated: {updated}
|
||||||
|
tags: [claude-code, 对话]
|
||||||
|
conversation_id: {conv['id']}
|
||||||
|
message_count: {len(messages)}
|
||||||
|
---
|
||||||
|
|
||||||
|
# {conv['title'] or 'Untitled'}
|
||||||
|
|
||||||
|
> 创建时间:{created}
|
||||||
|
> 更新时间:{updated}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对话记录
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
for msg in messages:
|
||||||
|
role = msg['role'] # 'user' or 'assistant'
|
||||||
|
content = msg['content'] or ''
|
||||||
|
ts = datetime.fromtimestamp(msg['created_at']).strftime('%H:%M:%S') if msg['created_at'] else ''
|
||||||
|
|
||||||
|
if role == 'user':
|
||||||
|
md += f"\n### 👤 用户 `{ts}`\n\n"
|
||||||
|
else:
|
||||||
|
md += f"\n### 🤖 Claude `{ts}`\n\n"
|
||||||
|
|
||||||
|
# 处理内容(可能是 JSON 字符串)
|
||||||
|
md += sanitize_content(content)
|
||||||
|
md += "\n\n---\n"
|
||||||
|
|
||||||
|
return md
|
||||||
|
|
||||||
|
def sanitize_content(content):
|
||||||
|
"""清理内容,移除可能的工具调用元数据"""
|
||||||
|
try:
|
||||||
|
# 尝试解析 JSON
|
||||||
|
data = json.loads(content)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
# 提取文本内容
|
||||||
|
if 'content' in data:
|
||||||
|
return data['content']
|
||||||
|
if 'text' in data:
|
||||||
|
return data['text']
|
||||||
|
return json.dumps(data, indent=2, ensure_ascii=False)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return str(content)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
export_all()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖(通常不需要)
|
||||||
|
pip install sqlite3 # Python 内置
|
||||||
|
|
||||||
|
# 运行导出
|
||||||
|
python3 claude_to_obsidian.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 方案二:直接查看数据库结构
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看数据库表
|
||||||
|
sqlite3 ~/.claude/conversations.db ".tables"
|
||||||
|
|
||||||
|
# 查看表结构
|
||||||
|
sqlite3 ~/.claude/conversations.db ".schema conversations"
|
||||||
|
sqlite3 ~/.claude/conversations.db ".schema messages"
|
||||||
|
|
||||||
|
# 查看会话数量
|
||||||
|
sqlite3 ~/.claude/conversations.db "SELECT COUNT(*) FROM conversations;"
|
||||||
|
|
||||||
|
# 导出单个会话为 JSON
|
||||||
|
sqlite3 -json ~/.claude/conversations.db \
|
||||||
|
"SELECT m.* FROM messages m JOIN conversations c ON m.conversation_id = c.id WHERE c.title LIKE '%关键词%';"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 方案三:按项目/目录组织
|
||||||
|
|
||||||
|
Claude Code 为每个项目创建独立的数据库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查找所有项目数据库
|
||||||
|
find ~/.claude -name "*.db" -type f
|
||||||
|
|
||||||
|
# 输出示例:
|
||||||
|
# ~/.claude/projects/project-uuid-1/conversations.db
|
||||||
|
# ~/.claude/projects/project-uuid-2/conversations.db
|
||||||
|
```
|
||||||
|
|
||||||
|
**按项目导出脚本**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
def export_by_project():
|
||||||
|
db_files = glob.glob(os.path.expanduser("~/.claude/**/conversations.db"), recursive=True)
|
||||||
|
|
||||||
|
for db_path in db_files:
|
||||||
|
# 从路径提取项目名
|
||||||
|
project_name = db_path.split(os.sep)[-2] # 倒数第二级是项目 UUID
|
||||||
|
project_dir = os.path.join(OBSIDIAN_PATH, project_name)
|
||||||
|
os.makedirs(project_dir, exist_ok=True)
|
||||||
|
|
||||||
|
print(f"📁 处理项目: {project_name}")
|
||||||
|
# ... 使用上面的导出逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Obsidian 目录结构建议
|
||||||
|
|
||||||
|
```
|
||||||
|
/obsidian/ClaudeCode对话/
|
||||||
|
├── _templates/
|
||||||
|
│ └── Claude对话模板.md # Obsidian 模板
|
||||||
|
├── 2026-05-04/
|
||||||
|
│ ├── 修复API鉴权Bug.md
|
||||||
|
│ ├── 编写单元测试.md
|
||||||
|
│ └── 重构数据库模块.md
|
||||||
|
└── INDEX.md # 索引笔记(按日期/项目分类)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **隐私安全**:对话可能包含敏感信息(API Key、代码等),导出后注意权限控制
|
||||||
|
2. **大文件**:长对话可能生成大文件,Obsidian 打开会慢
|
||||||
|
3. **编码问题**:确保以 UTF-8 编码写入
|
||||||
|
4. **增量导出**:记录上次导出的时间戳,只导出新对话
|
||||||
|
5. **工具调用**:Claude Code 的工具调用(文件读写、Shell 执行)元数据可能需要特殊处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 自动同步方案
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 加入 cron,每天凌晨自动导出
|
||||||
|
0 2 * * * cd /path/to/scripts && python3 claude_to_obsidian.py >> /tmp/claude_export.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*研究归档,2026-05-04 | Claude Code 对话导出到 Obsidian 完整指南*
|
||||||
Reference in New Issue
Block a user