# Theo Confluence Reader

> 读取Confluence需求文档并整理成指定格式。采集原则是"忠实记录"，而非"需求分析"。输出包括：{序号}_{标题}.md（每个页面一个Markdown文件）、requirement-meta.md（元信息）、images/（所有图片，文件中包含图片引用）。

- Skill: `leoyeai-openclaw-master-skills/theo-confluence-reader` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add leoyeai-openclaw-master-skills/theo-confluence-reader`
- Raw SKILL.md: https://api.skillmd.com/api/skills/leoyeai-openclaw-master-skills/theo-confluence-reader/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: LeoYeAI (https://skillmd.com/u/leoyeai-openclaw-master-skills)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/leoyeai-openclaw-master-skills/theo-confluence-reader

---


# Theo Confluence Reader

## 全局配置

```powershell
# Confluence 配置（根据实际环境修改）
$confluenceBaseUrl = "https://confluence.xxx.com"
$outputDir = "C:\Users\xxx\.openclaw\workspace\output"
$workspaceDir = "C:\Users\xxx\.openclaw\workspace"
$maxSize = 1GB
$warnThreshold = 0.8
```

## 核心原则

**这个skill的职责是"忠实采集"，不是"需求分析"**
- 只做格式转换（HTML → MD），不做内容删减、概括或精简
- 保留原文的所有细节，包括表格、列表、备注、批注等
- 不要对原文做任何主观判断、删除或重组

## 执行规则（强制）

**⚠️ 必须抓取所有页面，不允许部分抓取或偷懒**

### 默认行为
1. **自动抓取全部**：给定一个Confluence页面链接后，必须抓取该页面**及其所有子页面**的全部内容
2. **不允许只抓索引**：不能只抓父页面或索引页就停下来，必须深入到每个叶子节点
3. **不允许跳过**：不能因为"内容太多"或"时间太长"而跳过某些页面

### 唯一例外：确认模式（仅允许询问一次）
如果预估采集时间超过5分钟或页面数量超过10个，可以先整理大纲并预估资源，返回给用户确认。

**但必须遵守以下规则**：
- **只能询问一次**：确认后必须执行完整，不能再次询问
- **大纲必须包含**：
  - 所有待抓取页面的完整列表
  - 预估页面数量
  - 预估时间和资源消耗
- **确认后必须执行**：用户确认后，立即开始抓取所有页面，中途不能停止或询问
- **禁止多次询问**：一旦用户确认后，不能以任何理由再次询问是否继续

### 示例

**错误做法（禁止）**：
- 只抓父页面索引，给用户后说"详细内容需要再抓"
- 抓了一部分后说"太多了要不要继续"
- 用户确认后 又问"这个子页面要不要抓"

**正确做法**：
- 要么直接全部抓完
- 要么先给大纲确认，**确认后全部抓完**

## 存储上限

**output/ 目录总大小上限：1GB**

- 每次创建新目录前检查总大小
- 超过 80%（800MB）时：自动删除最早的目录
- 超过 100%（1GB）时：强制删除最早的目录直到低于 80%

**检查并清理脚本**：
```powershell
$outputDir = "C:\Users\xxx\.openclaw\workspace\output"
$workspaceDir = "C:\Users\xxx\.openclaw\workspace"
$maxSize = 1GB
$warnThreshold = 0.8

if (!(Test-Path $outputDir)) { 
    New-Item -ItemType Directory -Path $outputDir -Force
}

# 计算 output 目录大小 + workspace 根目录下的 zip 文件大小
$outputSize = 0
$zipSize = 0

$outputItems = Get-ChildItem $outputDir -Recurse -ErrorAction SilentlyContinue
if ($outputItems) {
    $outputSize = ($outputItems | Measure-Object -Property Length -Sum).Sum
}

$zipFiles = Get-ChildItem $workspaceDir -Filter "*.zip" -ErrorAction SilentlyContinue
if ($zipFiles) {
    $zipSize = ($zipFiles | Measure-Object -Property Length -Sum).Sum
}

$totalSize = $outputSize + $zipSize

if ($totalSize -gt ($maxSize * $warnThreshold)) {
    Write-Host "存储超过 80%，开始清理..."
    
    # 1. 删除最早的 zip 文件
    if ($zipSize -gt 0) {
        $zipFilesSorted = $zipFiles | Sort-Object LastWriteTime
        foreach ($zip in $zipFilesSorted) {
            if ($totalSize -lt ($maxSize * $warnThreshold)) { break }
            $z = $zip.Length
            Remove-Item $zip.FullName -Force
            $totalSize -= $z
            Write-Host "已删除压缩包: $($zip.Name)"
        }
    }
    
    # 2. 删除最早的 output 子目录
    $dirs = Get-ChildItem $outputDir -Directory -ErrorAction SilentlyContinue | Sort-Object LastWriteTime
    foreach ($dir in $dirs) {
        if ($totalSize -lt ($maxSize * $warnThreshold)) { break }
        $dirSize = (Get-ChildItem $dir.FullName -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
        if ($dirSize -gt 0) {
            Remove-Item $dir.FullName -Recurse -Force
            $totalSize -= $dirSize
            Write-Host "已删除: $($dir.Name)"
        }
    }
}
```

## 输出目录结构

**重要：每次运行都会在 output/ 下创建一个以"页面标题_时间戳"命名的独立目录，避免文件混淆。**

**pageId获取方式**：
- 从Confluence URL中提取：`/pages/viewpage.action?pageId=272188760` → `pageId=272188760`
- 或从页面源码中获取

```
output/
├── 页面标题_2026-03-13_2030/
│   ├── 01_需求概述.md          # 页面1的MD转换版
│   ├── 02_登录注册.md          # 页面2的MD转换版
│   ├── 03_系统首页.md          # 页面3的MD转换版
│   ├── ...                     # 其他页面
│   ├── requirement-meta.md    # 元信息（整个采集任务的元信息）
│   └── images/                 # 关联图片
│       ├── 01_需求概述_功能架构图.png
│       ├── 02_登录注册.png
│       └── ...
```

**时间戳格式**：`YYYY-MM-DD_HHmm`（年月日_时分）

## 前置要求

**必须先登录 Confluence**：
1. 在运行 OpenClaw 的机器上打开浏览器
2. 访问 Confluence 页面并完成登录
3. 保持登录状态，后续的图片下载和内容获取都依赖这个已登录的 session

## 操作流程

### Step 1: 分析页面结构 + 创建输出目录 + 检查存储上限

**⚠️ 重要：必须抓取所有子页面**
- 这一步必须展开页面树，列出**所有**子页面（包括二级、三级、四级等全部层级）
- 不能只抓父页面就停止
- 记录每个子页面的：pageId、标题、层级

**输入验证**：
```powershell
param(
    [Parameter(Mandatory=$true)]
    [string]$confluenceUrl
)

# 验证URL格式
if ($confluenceUrl -notmatch 'https?://[^/]+/pages/(viewpage\.action\?pageId=|viewpage\.action\?title=)') {
    throw "无效的Confluence URL，请提供形如 https://confluence.xxx.com/pages/viewpage.action?pageId=123456 的URL"
}

# 提取pageId（如果URL中包含）
if ($confluenceUrl -match 'pageId=(\d+)') {
    $rootPageId = $matches[1]
    Write-Host "根页面ID: $rootPageId"
}
```

**获取完整页面树的两种方法：**

#### 方法1：通过浏览器页面树获取（默认推荐）

1. 用浏览器打开用户提供的 Confluence URL
2. 在页面左侧找到"页面树结构"
3. 点击"展开全部"或手动展开**所有层级**（包括二级、三级、四级...直到叶子节点）
4. 使用 browser evaluate 获取页面树的完整结构：
   ```javascript
   // 在浏览器控制台执行，获取所有页面链接
   const links = Array.from(document.querySelectorAll('a[href*="pageId="]')).map(a => ({
       title: a.textContent.trim(),
       pageId: a.href.match(/pageId=(\d+)/)?.[1],
       href: a.href
   }));
   console.log(JSON.stringify(links, null, 2));
   ```
5. 解析获取的数据，提取所有 pageId 和标题
6. **⚠️ 关键**：必须确保页面树完全展开，所有层级的子页面都被展开
7. 确认总共有多少个页面需要获取

#### 方法2：通过 Confluence REST API 获取（备选）

```powershell
# 获取页面及其所有子页面
function Get-ConfluencePageTree {
    param(
        [string]$baseUrl = "https://confluence.xxx.com",
        [string]$rootPageId,
        [string]$cookie  # 登录 cookie
    )
    
    $pages = @()
    
    # 获取当前页面的直接子页面
    $apiUrl = "$baseUrl/rest/api/content/$rootPageId/child/page"
    $headers = @{
        "Cookie" = $cookie
        "Content-Type" = "application/json"
    }
    
    $response = Invoke-RestMethod -Uri $apiUrl -Headers $headers -Method Get
    foreach ($page in $response.results) {
        $pages += @{
            pageId = $page.id
            title = $page.title
            type = $page.type
        }
        
        # 递归获取子页面的子页面
        $childPages = Get-ConfluencePageTree -baseUrl $baseUrl -rootPageId $page.id -cookie $cookie
        $pages += $childPages
    }
    
    return $pages
}

# 调用
$allPages = Get-ConfluencePageTree -rootPageId $rootPageId -cookie $cookie
Write-Host "共获取 $($allPages.Count) 个页面"
```

**检查并清理存储（如果超过80%上限）**：
```powershell
# 复用全局配置的变量，或直接使用以下默认值
$outputDir = "C:\Users\xxx\.openclaw\workspace\output"
$workspaceDir = "C:\Users\xxx\.openclaw\workspace"
$maxSize = 1GB
$warnThreshold = 0.8

if (!(Test-Path $outputDir)) { 
    New-Item -ItemType Directory -Path $outputDir -Force
}

# 计算 output 目录大小 + workspace 根目录下的 zip 文件大小
$outputSize = 0
$zipSize = 0

$outputItems = Get-ChildItem $outputDir -Recurse -ErrorAction SilentlyContinue
if ($outputItems) {
    $outputSize = ($outputItems | Measure-Object -Property Length -Sum).Sum
}

$zipFiles = Get-ChildItem $workspaceDir -Filter "*.zip" -ErrorAction SilentlyContinue
if ($zipFiles) {
    $zipSize = ($zipFiles | Measure-Object -Property Length -Sum).Sum
}

$totalSize = $outputSize + $zipSize

if ($totalSize -gt ($maxSize * $warnThreshold)) {
    Write-Host "存储超过 80%，开始清理..."
    
    # 1. 删除最早的 zip 文件
    if ($zipSize -gt 0) {
        $zipFilesSorted = $zipFiles | Sort-Object LastWriteTime
        foreach ($zip in $zipFilesSorted) {
            if ($totalSize -lt ($maxSize * $warnThreshold)) { break }
            $z = $zip.Length
            Remove-Item $zip.FullName -Force
            $totalSize -= $z
            Write-Host "已删除压缩包: $($zip.Name)"
        }
    }
    
    # 2. 删除最早的 output 子目录
    $dirs = Get-ChildItem $outputDir -Directory -ErrorAction SilentlyContinue | Sort-Object LastWriteTime
    foreach ($dir in $dirs) {
        if ($totalSize -lt ($maxSize * $warnThreshold)) { break }
        $dirSize = (Get-ChildItem $dir.FullName -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
        if ($dirSize -gt 0) {
            Remove-Item $dir.FullName -Recurse -Force
            $totalSize -= $dirSize
            Write-Host "已删除: $($dir.Name)"
        }
    }
}
```

**创建带时间戳的输出目录**：
```powershell
# 获取当前时间戳
$timestamp = Get-Date -Format "yyyy-MM-dd_HHmm"
# 页面标题（去除非法字符）
$safeTitle = "页面标题" -replace '[\\/:*?"<>|]', '_'
# 创建目录
$outputDir = "C:\Users\root\.openclaw\workspace\output\${safeTitle}_${timestamp}"
New-Item -ItemType Directory -Path "$outputDir\images" -Force
```

**后续所有文件都写入这个新创建的目录**，而不是直接写进 `output/` 根目录。

### Step 2: 获取每个页面的内容（使用 sessions_spawn）

**⚠️ 必须抓取所有页面，不允许选择性抓取**

**重要**：由于需要获取多个页面，必须使用 sessions_spawn 派发子任务并行处理。

**步骤**：
1. 分析所有pageId列表（从页面URL或页面树中提取）
2. 派发子任务获取所有页面的文字内容
3. 派发子任务下载所有页面的图片
4. 监控子任务直到完成

**获取页面内容的方法**：
1. 使用 browser 访问 Confluence 页面
2. 获取页面完整HTML内容（browser evaluate 或 snapshot）
3. 将HTML转换为Markdown格式

**HTML转Markdown推荐方法**：
- 方法1：使用 Python `markdownify` 库
  ```powershell
  python -m pip install markdownify
  python -c "from markdownify import markdownify; print(markdownify(html_content))"
  ```
- 方法2：使用 Node.js `turndown` 库
  ```powershell
  npm install -g turndown
  echo $htmlContent | turndown
  ```
- 方法3：使用在线API（如 html2md.com 的API）

**关键要求**：转换过程中不得删除任何内容，包括：
- 表格（保持表格结构）
- 图片引用（转换为 `![](images/xxx.png)` 格式）
- 代码块（保持原始格式）
- 特殊字符和格式

### Step 3: 获取页面图片（使用 curl 直接下载）

**重要**：使用 curl 直接下载图片原始文件，不再使用 browser 截图方式。

**⚠️ 必须为每个页面下载图片，不管页面层级**

**不管是一级页面、二级页面、三级页面还是更深层次的子页面，都必须下载该页面的所有图片附件！**

> **关键原则**：每个页面独立获取图片 — 页面层级不影响图片下载，父页面的图片 ≠ 子页面的图片，子页面需要单独获取自己的图片！

**⚠️ 前置要求：必须先获取登录 Cookie**

由于 Confluence 私有图片需要登录才能下载，**需要用户手动提供 Cookie**：

1. 用户在浏览器中登录 Confluence
2. 按 F12 打开开发者工具 → Application/Storage → Cookies
3. 复制以下 cookie 的值：
   - `JSESSIONID`
   - `CONFLAuth`
   - 其他可能需要的 cookie（如 `_ga`、`tgw_l7_route` 等）

4. 将完整 cookie 字符串提供给模型，格式如：
   ```
   _ga=GA1.1.xxx; pt_66be4504=xxx; JSESSIONID=xxx; tgw_l7_route=xxx
   ```

**获取认证Cookie**：
```powershell
# 用户提供的 cookie（直接使用，无需额外处理）
$cookie = "用户提供的完整cookie字符串"
```

**图片获取步骤**：
1. **遍历所有页面**：对 Step 1 中获取的**每一个**页面（包括所有层级的一级、二级、三级...子页面）
2. 对每个pageId，访问附件页面：`https://confluence.xxx.com/pages/viewpageattachments.action?pageId={pageId}`
3. 使用 curl 获取附件列表页面源码
4. 解析 `data-attachment-filename` 属性，找到该页面**所有**图片附件
5. 使用 curl 下载每个图片
6. **重复步骤1-5直到所有页面的图片都下载完成**

**图片命名规则**：
- 格式：`{页面序号}_{页面名称}.{扩展名}`
- 例如：`01_需求概述_功能架构图.png`、`02_登录注册.png`

**curl 下载命令示例**：
```powershell
$cookie = "用户提供的cookie"
$imageUrl = "https://confluence.xxx.com/download/attachments/272188760/image-2026-3-3_14-10-42.png?api=v2"
$outputFile = "C:\Users\root\.openclaw\workspace\output\images\01_需求概述_功能架构图.png"

curl.exe -L -o $outputFile $imageUrl -H "Cookie: $cookie"
```

**注意事项**：
- 使用 `curl.exe` 而非 PowerShell 内置 `curl` 别名
- 添加 `-L` 参数跟随重定向
- 必须携带完整登录 Cookie 才能下载私有图片
- Cookie 可能有有效期限制，如失效需让用户重新提供

**curl 下载脚本（含错误处理和重试）**：
```powershell
# 配置
$cookie = "JSESSIONID=xxx; CONFLAuth=xxx"  # 替换为实际 cookie
$imagesDir = "$outputDir\images"
$maxRetries = 3

# 创建 images 目录
if (!(Test-Path $imagesDir)) {
    New-Item -ItemType Directory -Path $imagesDir -Force
}

# 图片列表（从附件页面解析得到）
$imageList = @(
    @{pageId="272191284"; filename="image.png"; ext="png"},
    @{pageId="272191284"; filename="photo.jpg"; ext="jpg"}
)

$index = 1
foreach ($img in $imageList) {
    $imageUrl = "https://confluence.xxx.com/download/attachments/$($img.pageId)/$($img.filename)"
    $outputFile = "$imagesDir\$($img.pageId)_$index.$($img.ext)"
    
    # 重试逻辑
    $success = $false
    for ($retry = 1; $retry -le $maxRetries; $retry++) {
        Write-Host "尝试下载 ($retry/$maxRetries): $imageUrl"
        
        $result = curl.exe -L -o $outputFile $imageUrl `
            -H "Cookie: $cookie" `
            -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" `
            --silent --show-error `
            2>&1
        
        # 检查文件是否成功下载
        if ((Test-Path $outputFile) -and (Get-Item $outputFile).Length -gt 0) {
            $success = $true
            Write-Host "已下载: $outputFile"
            break
        } else {
            Write-Host "下载失败，等待重试..."
            Start-Sleep -Seconds 2
        }
    }
    
    if (-not $success) {
        Write-Host "警告: 图片下载失败 - $imageUrl" -ForegroundColor Yellow
    }
    
    $index++
}

Write-Host "图片下载完成，共 $($imageList.Count) 张"
```

**注意事项**：
- 使用 `curl.exe` 而非 PowerShell 内置 `curl` 别名
- 添加 `-L` 参数跟随重定向
- 保留原始文件格式（png/jpg/svg等）
- 必须携带登录 Cookie 才能下载私有图片
- 添加重试逻辑应对网络不稳定
- 下载后检查文件大小确保下载成功

### Step 4: 生成输出文件

**所有文件都写入 Step 1 创建的 `$outputDir` 目录**。

#### 4.1 {序号}_{标题}.md（每个页面一个）

将Confluence页面的HTML内容忠实转换为Markdown格式。

**关键：必须插入图片引用！**

在生成 md 文件时，对于该页面关联的每张图片，必须在**适当位置**插入图片引用：

```powershell
# 假设当前页面是 "02_登录注册"，该页面有 2 张图片
# images/ 目录中的文件：
#   - 02_登录注册.png
#   - 02_登录注册_2.png

# 在 md 文件内容中插入图片引用（建议放在页面标题下方或相关章节后）

$markdownContent = @"
# 2.1 登录注册（一期交付）

![](images/02_登录注册.png)

## 1、功能设计

...

## 2、原型图

![](images/02_登录注册_2.png)

...
"@

# 写入文件
$mdFileName = "02_登录注册.md"
$mdFilePath = Join-Path $outputDir $mdFileName
Set-Content -Path $mdFilePath -Value $markdownContent -Encoding UTF8
```

**图片引用规则**：
- 格式：`![](images/{图片文件名})`
- 位置：
  - 如果图片是功能架构图/流程图，放在页面标题下方或相应章节后
  - 如果是原型图，放在对应功能描述之后
  - 如果无法确定位置，放在页面末尾
- 如果一个页面有多张图片，按文件名排序后依次插入

#### 4.2 requirement-meta.md

```markdown
# 采集元信息

- 来源页面: [Confluence 页面 URL]
- 采集根页面: [根页面标题和 URL]
- 采集时间: [ISO 8601 格式，如 2026-03-15T18:00:00+08:00]
- 采集环境: [Confluence URL 域名]
- 总页面数: [页面总数]
- 页面列表:
  - 01_需求概述 (pageId: 272188760)
  - 02_登录注册 (pageId: 272189290)
  - ...
```

写入 `$outputDir\requirement-meta.md`。

#### 4.3 images/ 目录

- 保存所有下载的图片到 `$outputDir\images\`
- 文件名格式：`{序号}_{页面标题}_{图片描述}.{扩展名}`
- 例如：`01_需求概述_功能架构图.png`、`02_登录注册.png`、`02_登录注册_2.png`

### Step 5: 打包输出

将整个output目录打包成zip文件，发送给用户。

**打包命令**：
```powershell
# 打包整个 output 目录
$timestamp = Get-Date -Format "yyyy-MM-dd_HHmm"
$zipName = "confluence_export_$timestamp.zip"
$zipPath = "C:\Users\root\.openclaw\workspace\$zipName"

# 使用 PowerShell Compress-Archive
Compress-Archive -Path "C:\Users\root\.openclaw\workspace\output\*" -DestinationPath $zipPath -Force

# 验证打包结果
if (Test-Path $zipPath) {
    $zipSize = (Get-Item $zipPath).Length
    Write-Host "打包完成: $zipName (大小: $([math]::Round($zipSize/1MB, 2)) MB)"
} else {
    Write-Host "打包失败!" -ForegroundColor Red
}
```

**或者使用 7z（如果安装了中国）**：
```powershell
7z a -r "$zipPath" "C:\Users\root\.openclaw\workspace\output\*"
```

**输出文件后**：
- 将 zip 文件路径返回给用户
- 用户下载后可解压查看

## 注意事项

1. **使用sessions_spawn**：长时间任务必须使用sessions_spawn派发子任务并行处理

2. **图片直接下载**：
   - 访问 `viewpageattachments.action?pageId={pageId}` 获取附件列表
   - 使用 curl 携带登录 Cookie 直接下载图片原始文件
   - 保留原始格式（png/jpg/svg等）
   - 添加重试逻辑（建议3次）确保稳定性

3. **认证Cookie获取**：
   - 主session已登录，可以从browser获取Cookie
   - Cookie格式：`JSESSIONID=xxx; CONFLAuth=xxx`
   - 必须携带Cookie才能下载私有图片

4. **HTML转Markdown**：
   - 推荐使用 `markdownify` (Python) 或 `turndown` (Node.js)
   - 转换过程不得删除任何内容
   - 表格、代码块、图片引用需保持完整

5. **存储管理**：
   - 每次创建新目录前检查存储上限
   - 超过80%自动清理最早的目录和zip文件
   - 注意处理空目录的边界情况

6. **错误处理**：
   - 所有网络请求添加重试逻辑
   - 下载后验证文件大小
   - 记录错误日志以便排查

7. **不删减内容**：原文的每一个字都要保留
8. **保留格式**：表格、列表等都要完整保留
9. **层级准确**：严格按照Confluence的页面树层级整理

