Install
openclaw skills install theo-confluence-reader读取Confluence需求文档并整理成指定格式。采集原则是"忠实记录",而非"需求分析"。输出包括:{序号}_{标题}.md(每个页面一个Markdown文件)、requirement-meta.md(元信息)、images/(所有图片,文件中包含图片引用)。
openclaw skills install theo-confluence-reader# 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的职责是"忠实采集",不是"需求分析"
⚠️ 必须抓取所有页面,不允许部分抓取或偷懒
如果预估采集时间超过5分钟或页面数量超过10个,可以先整理大纲并预估资源,返回给用户确认。
但必须遵守以下规则:
错误做法(禁止):
正确做法:
output/ 目录总大小上限:1GB
检查并清理脚本:
$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获取方式:
/pages/viewpage.action?pageId=272188760 → pageId=272188760output/
├── 页面标题_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:
⚠️ 重要:必须抓取所有子页面
输入验证:
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"
}
获取完整页面树的两种方法:
// 在浏览器控制台执行,获取所有页面链接
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));
# 获取页面及其所有子页面
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%上限):
# 复用全局配置的变量,或直接使用以下默认值
$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)"
}
}
}
创建带时间戳的输出目录:
# 获取当前时间戳
$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/ 根目录。
⚠️ 必须抓取所有页面,不允许选择性抓取
重要:由于需要获取多个页面,必须使用 sessions_spawn 派发子任务并行处理。
步骤:
获取页面内容的方法:
HTML转Markdown推荐方法:
markdownify 库
python -m pip install markdownify
python -c "from markdownify import markdownify; print(markdownify(html_content))"
turndown 库
npm install -g turndown
echo $htmlContent | turndown
关键要求:转换过程中不得删除任何内容,包括:
 格式)重要:使用 curl 直接下载图片原始文件,不再使用 browser 截图方式。
⚠️ 必须为每个页面下载图片,不管页面层级
不管是一级页面、二级页面、三级页面还是更深层次的子页面,都必须下载该页面的所有图片附件!
关键原则:每个页面独立获取图片 — 页面层级不影响图片下载,父页面的图片 ≠ 子页面的图片,子页面需要单独获取自己的图片!
⚠️ 前置要求:必须先获取登录 Cookie
由于 Confluence 私有图片需要登录才能下载,需要用户手动提供 Cookie:
用户在浏览器中登录 Confluence
按 F12 打开开发者工具 → Application/Storage → Cookies
复制以下 cookie 的值:
JSESSIONIDCONFLAuth_ga、tgw_l7_route 等)将完整 cookie 字符串提供给模型,格式如:
_ga=GA1.1.xxx; pt_66be4504=xxx; JSESSIONID=xxx; tgw_l7_route=xxx
获取认证Cookie:
# 用户提供的 cookie(直接使用,无需额外处理)
$cookie = "用户提供的完整cookie字符串"
图片获取步骤:
https://confluence.xxx.com/pages/viewpageattachments.action?pageId={pageId}data-attachment-filename 属性,找到该页面所有图片附件图片命名规则:
{页面序号}_{页面名称}.{扩展名}01_需求概述_功能架构图.png、02_登录注册.pngcurl 下载命令示例:
$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 参数跟随重定向curl 下载脚本(含错误处理和重试):
# 配置
$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 参数跟随重定向所有文件都写入 Step 1 创建的 $outputDir 目录。
将Confluence页面的HTML内容忠实转换为Markdown格式。
关键:必须插入图片引用!
在生成 md 文件时,对于该页面关联的每张图片,必须在适当位置插入图片引用:
# 假设当前页面是 "02_登录注册",该页面有 2 张图片
# images/ 目录中的文件:
# - 02_登录注册.png
# - 02_登录注册_2.png
# 在 md 文件内容中插入图片引用(建议放在页面标题下方或相关章节后)
$markdownContent = @"
# 2.1 登录注册(一期交付)

## 1、功能设计
...
## 2、原型图

...
"@
# 写入文件
$mdFileName = "02_登录注册.md"
$mdFilePath = Join-Path $outputDir $mdFileName
Set-Content -Path $mdFilePath -Value $markdownContent -Encoding UTF8
图片引用规则:
# 采集元信息
- 来源页面: [Confluence 页面 URL]
- 采集根页面: [根页面标题和 URL]
- 采集时间: [ISO 8601 格式,如 2026-03-15T18:00:00+08:00]
- 采集环境: [Confluence URL 域名]
- 总页面数: [页面总数]
- 页面列表:
- 01_需求概述 (pageId: 272188760)
- 02_登录注册 (pageId: 272189290)
- ...
写入 $outputDir\requirement-meta.md。
$outputDir\images\{序号}_{页面标题}_{图片描述}.{扩展名}01_需求概述_功能架构图.png、02_登录注册.png、02_登录注册_2.png将整个output目录打包成zip文件,发送给用户。
打包命令:
# 打包整个 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(如果安装了中国):
7z a -r "$zipPath" "C:\Users\root\.openclaw\workspace\output\*"
输出文件后:
使用sessions_spawn:长时间任务必须使用sessions_spawn派发子任务并行处理
图片直接下载:
viewpageattachments.action?pageId={pageId} 获取附件列表认证Cookie获取:
JSESSIONID=xxx; CONFLAuth=xxxHTML转Markdown:
markdownify (Python) 或 turndown (Node.js)存储管理:
错误处理:
不删减内容:原文的每一个字都要保留
保留格式:表格、列表等都要完整保留
层级准确:严格按照Confluence的页面树层级整理