Files
chill_notes/wiki/AI工程/ClaudeCode对话导出Obsidian.md
2026-05-04 10:39:03 +08:00

7.1 KiB
Executable File
Raw Blame History

created, type, tags
created type tags
2026-05-04 guide
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. 导出脚本

#!/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. 使用

# 安装依赖(通常不需要)
pip install sqlite3  # Python 内置

# 运行导出
python3 claude_to_obsidian.py

🔧 方案二:直接查看数据库结构

# 查看数据库表
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 为每个项目创建独立的数据库:

# 查找所有项目数据库
find ~/.claude -name "*.db" -type f

# 输出示例:
# ~/.claude/projects/project-uuid-1/conversations.db
# ~/.claude/projects/project-uuid-2/conversations.db

按项目导出脚本

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 执行)元数据可能需要特殊处理

🔄 自动同步方案

# 加入 cron每天凌晨自动导出
0 2 * * * cd /path/to/scripts && python3 claude_to_obsidian.py >> /tmp/claude_export.log 2>&1

研究归档2026-05-04 | Claude Code 对话导出到 Obsidian 完整指南