360 Secure SkillHub
360 安全技能中心

V1.4.8

在 Mac Mini 上以交互式向导部署本地知识库与 RAG 搜索:安装 Homebrew 包与 Ollama embedding 模型,部署文档提取/OCR 脚本,注册定时分析任务并将摘要推送到飞书;同时提供批量 OCR 修复、目录清理与迁移说明。

seairteng
seairteng
热度 87.63
最近更新 2026-10-13 02:48:23
分类:知识依赖:代码执行 / 读写文件 / 第三方 API应用场景:自动化脚本 / 工作流 / 摘要总结 / 数据处理 ...

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add macmini-knowledge-base
Metadata
name
macmini-knowledge-base
version
1.4.8
description
⚠️ **本 skill 包含以下高危能力,使用前请仔细阅读 SKILL.md 顶部「⚠️ CAPABILITIES & RISKS」章节**: - Shell 执行 + 文件读写 + 安装 Homebrew 包(版本固定)+ 下载 Ollama 模型 - 修改 OpenClaw 配置 + 注册持久 cron 任务 + 飞书 webhook 推送 在 Mac Mini (M4) 上快速搭建本地知识库 + RAG 自然语言搜索系统。 适用场景: - 新 Mac 配置知识库:从零开始安装配置 Ollama、embedding模型、定时任务、文档解析 - 遇到 PDF 提取乱码、定时任务超时、skill 加载失败等问题 - 想要建立每日自动分析文档 + 08:00发送摘要到飞书的流程 - 迁移或复现知识库:打包整个 knowledge 目录和配置到新电脑 - **v1.4 新增**:CMap 残缺度自检(不预设来源)+ 50万字完整提取 + OCR fallback 到 .doc - **v1.4.8 新增**:4 项健壮性修复 1. **MuPDF/kreuzberg/OCR 异常不再静默吞掉**——记录到 stderr,保留错误信息 2. **[:3000] 硬截断修复**——使用 MAX_EXTRACT_LEN (500,000 字符),长文档完整提取 3. **错误重试机制**——retry_with_backoff(2 次重试,指数退避 1s/2s) 4. **clean_noise 噪音清理**——3 阶段处理(行级 + span-based + disclaimer 公式专项),删除投行研报的"in whole or in part, to train or finetune"等版权/免责噪音 本 skill 会引导完成:目录结构创建、依赖安装、脚本部署、定时任务注册、OpenClaw 配置。 **v1.4.8 行为变化**: - PDF 提取失败时不再静默 return,错误信息写入 stderr(可查 /var/log 或 journal) - 长文档不再被截断到 3000 字符 - 临时性网络/IO 错误会自动重试 2 次 - 摘要文件不再包含"in whole or in part, to train or finetune..."等法律噪音 ⚠️ **重要:能力范围** 本 skill 不只是「搭建」,还包含: - 批量 OCR 修复(扫描 summaries/archives 找乱码 + 重新提取) - 目录归档清理(移动重复/孤儿文件到 .trash/) - 自动定时任务(23:00 分析 + 06:00 飞书推送) 使用前请仔细评估批量修改风险。

Knowledge Base Setup

在 Mac Mini 上快速搭建本地知识库 + RAG 搜索系统。

⚠️ 阅读前必读:本 skill 的能力范围

本 skill 不只是"搭建知识库",还包含以下高危能力:

执行能力:

  • 🔧 Shell 命令执行(python3 + bash 脚本)
  • 📁 文件读写(knowledge/, summaries/, archives/, .trash/)
  • 📦 安装 Homebrew 包(antiword, tesseract, pandoc, libreoffice,版本固定)
  • ⬇️ 下载 Ollama 模型(nomic-embed-text, ~274MB)
  • ⚙️ 修改 OpenClaw 配置(~/.openclaw/openclaw.json)
  • ⏰ 注册持久 cron 任务(23:00 + 06:00,每天自动)
  • 📤 推送消息到飞书 webhook

持久化影响:

  • 知识库目录会被自动分析(每天 23:00)
  • 摘要文件会被覆盖写入(OCR 修复时)
  • cron 任务永久执行(直到手动 openclaw cron remove <id>)
  • 失败文件移到 .trash/(7 天兜底清理)

安装流程: 本 skill 的 setup.sh 是交互式安装向导:

  • 每个危险操作前会要求 y/N 确认
  • 提供 --dry-run 选项查看会做什么
  • 已安装用户重跑会进入确认模式

如果不同意上述任何一项,请不要安装本 skill。

结构化权限声明(Structured Permissions)

权限 必填 范围 用途 / 风险
exec ✅ python3 + bash scripts 文档提取 + 飞书推送
file_read ✅ ~/.openclaw/workspace/knowledge/ 读取文档 + summaries
file_write ✅ summaries/, archives/, .trash/ OCR 修复覆盖旧摘要
install_packages ✅ brew: antiword, tesseract, pandoc, libreoffice Homebrew 包安装(用户确认)
download_model ✅ ollama: nomic-embed-text (~274MB) Ollama 模型下载(用户确认)
modify_config ✅ ~/.openclaw/openclaw.json 添加 alsoAllow: [exec, process]
register_cron ✅ 23:00 分析 + 06:00 推送 持久化定时任务(用户确认)
network ✅ 飞书 webhook + Ollama 下载 外部 API 调用

warning: 本 skill 会自动修改文件、安装包、注册 cron 任务(用户每步都有 y/N 确认)

disable_command: openclaw cron remove <id>


核心功能(v2.0)

  • kreuzberg 统一提取层:PDF / DOCX / XLSX / PPTX / MD / 图片 OCR 全自动路由
  • antiword 极速专线:.doc 文件专用提取,成功率 85%,169MB 文件 0.02 秒完成
  • 智能兜底:antiword 失败自动走 soffice 转换,60 秒硬超时无误判
  • 自动分类:关键词匹配驱动,中英文双语标签
  • 定时任务:每天 23:00 分析新文档,08:00 发送摘要到飞书

🔧 权限声明

本 skill 在使用时需要以下 OpenClaw 工具能力:

{
  "tools": {
    "alsoAllow": ["exec", "process", "read", "write"]
  }
}

⚠️ 执行风险:exec + cron 自动化组合可导致持续命令执行, 请在可信环境(个人 Mac)使用,不要在共享/服务器部署。

⚠️ 安全警告:定时任务

本 skill 注册 2 个 cron 任务(23:00 分析 + 06:00 推送), 运行 shell 命令并自动推送消息到飞书。

潜在风险:

  • 脚本路径被修改 → 自动执行任意命令
  • 知识库目录被入侵 → 自动读取/外发
  • 飞书 webhook 泄漏 → 自动推送被劫持

建议:

  • 不要把 ~/.openclaw/workspace/knowledge 放在共享/多用户目录
  • 定期检查 cron 配置(openclaw cron list)
  • 飞书 webhook 使用独立群组,不要复用其他机器人的 webhook
  • 仅在个人 Mac 上运行,不要部署到服务器

快速开始

一键安装

cd ~/.openclaw/workspace/skills/knowledge-base-setup/scripts
bash setup.sh <飞书用户ID>

手动分步安装

Step 1: 系统依赖

brew install antiword tesseract pandoc

Step 2: Python 依赖

pip3 install kreuzberg pytesseract pymupdf docx openpyxl python-pptx

Step 3: Ollama + embedding 模型

# 安装 Ollama: https://ollama.com/download
ollama pull nomic-embed-text

Step 4: 创建目录结构

mkdir -p ~/.openclaw/workspace/knowledge/.analysis/summaries/archives
mkdir -p ~/.openclaw/workspace/knowledge/temp_docs
touch ~/.openclaw/workspace/knowledge/文章目录/文章目录.md

Step 5: 部署脚本

cp ~/.openclaw/workspace/skills/knowledge-base-setup/scripts/*.py \
   ~/.openclaw/workspace/knowledge/.analysis/
chmod +x ~/.openclaw/workspace/knowledge/.analysis/*.py

Step 6: 配置 OpenClaw

编辑 ~/.openclaw/openclaw.json,加入:

{
  "models": {
    "providers": {
      "ollama": {
        "baseUrl": "http://127.0.0.1:11434",
        "api": "ollama",
        "models": [
          {"id": "nomic-embed-text", "name": "Nomic Embed Text"}
        ]
      }
    }
  },
  "agents": {
    "defaults": {
      "memorySearch": {
        "provider": "ollama",
        "model": "nomic-embed-text"
      }
    }
  }
}

确保 tools 区块有:

"tools": {
    "alsoAllow": ["exec", "process"]
}

然后重启:openclaw gateway restart

Step 7: 注册定时任务

# 23:00 分析新文档
openclaw cron add \
  --name "23:00分析新文档" \
  --cron "0 23 * * *" \
  --tz "Asia/Shanghai" \
  --session isolated \
  --timeout-seconds 600 \
  --message "cd ~/.openclaw/workspace/knowledge/.analysis && python3 run_analysis.py && python3 generate_catalog.py" \
  --announce --channel feishu --to "user:<飞书用户ID>"

# 08:00 发送文档摘要
openclaw cron add \
  --name "08:00发送文档摘要" \
  --cron "0 8 * * *" \
  --tz "Asia/Shanghai" \
  --session isolated \
  --timeout-seconds 120 \
  --message "读取 summaries/ 目录发送摘要到飞书" \
  --announce --channel feishu --to "user:<飞书用户ID>"

summary 文件名 sanitize(v1.4.1 新增)

防止 OSError: [Errno 63] File name too long(NAME_MAX=255 bytes):

SUMMARY_NAME_MAX = 200

def sanitize_filename(name, max_length=SUMMARY_NAME_MAX):
    """截断超长文件名,保留扩展名 + 8 位 MD5 hash 防冲突"""
    name_bytes = name.encode('utf-8')
    if len(name_bytes) <= max_length:
        return name
    
    base, ext = os.path.splitext(name)
    ext_bytes = ext.encode('utf-8')
    base_bytes = base.encode('utf-8')
    
    import hashlib
    h = hashlib.md5(name_bytes).hexdigest()[:8]
    
    reserve = len(ext_bytes) + 1 + 8  # "_" + hash + ext
    available = max_length - reserve
    
    if available > 0 and len(base_bytes) > available:
        truncated = base_bytes[:available].decode('utf-8', errors='ignore')
        return f"{truncated}_{h}{ext}"
    
    return name[:max_length]

主循环的异常捕获重试:

try:
    with open(summary_file, 'w', encoding='utf-8') as f:
        f.write(content)
except OSError as e:
    if e.errno == 63:  # ENAMETOOLONG
        short_name = sanitize_filename(filename, max_length=180)
        summary_file = os.path.join(
            SUMMARY_DIR,
            f"{timestamp}_{short_name}.summary.txt"
        )
        with open(summary_file, 'w', encoding='utf-8') as f:
            f.write(content)

触发场景: 畸形 PDF 文件名(如下载错误的 _; filename_=utf-8''... 双名拼接),原文件名 244+ bytes + 时间戳超 255 bytes 限制。

实测案例: 20260730-Nomura-Asia Insights:China:The Politburo meeting indicated a shift to _countercyclical" policies-260730.pdf_; filename_=utf-8''...pdf (原 248 bytes) → sanitize 后 200 bytes + 8位 hash → 安全创建。

temp_docs 畸形文件清理(v1.4.1 新增)

下载失败的 PDF 在文件名里重复了两次(_; filename_=utf-8'' 分隔),实际只需保留前半。一次性清理脚本:

import os, shutil
temp_docs = os.path.expanduser("~/.openclaw/workspace/knowledge/temp_docs")
trash_dir = os.path.expanduser("~/.openclaw/workspace/knowledge/.trash/temp_docs_<时间戳>")
os.makedirs(trash_dir, exist_ok=True)

for f in os.listdir(temp_docs):
    if "_; filename_=utf-8''" in f:
        full = os.path.join(temp_docs, f)
        parts = f.split("_; filename_=utf-8''")
        real_name = parts[0]
        target = os.path.join(temp_docs, real_name)
        if not os.path.exists(target):
            shutil.move(full, target)
            print(f"重命名: {real_name}")

CMap 残缺度自检(v1.4 新增)

不预设"哪个 PDF 来源会乱码"——实测 72% 的乱码来自非 lightpdf PDF(PPT 转 PDF、扫描件等), 改用自适应检测:

def is_cmap_broken(text, threshold=0.03):
    """检测文本是否含异常字符(CMap 残缺/PUA 污染/未映射 CID)"""
    if not text or len(text.strip()) < 50:
        return False
    total = len(text)
    pua_count = sum(1 for c in text if 0xE000 <= ord(c) <= 0xF8FF)
    cjk_ext = sum(1 for c in text if 0x20000 <= ord(c) <= 0x2EBEF)
    cjk_compat = sum(1 for c in text if 0xF900 <= ord(c) <= 0xFAFF)
    cid_count = text.count('(cid:')
    bad_ratio = (pua_count + cjk_ext + cjk_compat + cid_count) / total
    return bad_ratio > threshold or cid_count > 10

3 类乱码特征:

  1. PUA 私用区 (U+E000-F8FF) —— 残缺 CMap fallback
  2. CJK 扩展区 (U+20000-2EBEF) —— 字符找不到映射
  3. (cid:xxxx) 字面值 —— pdfplumber 提取失败标志

集成位置: extract_pdf_text() 在 kreuzberg / pymupdf 提取后调 is_cmap_broken(), 通过即返回,失败即触发 OCR 路径。

OCR 性能实测(2026-08-12 验证)

文件 大小 OCR 耗时 备注
lightpdf PDF 4 页 13.5 秒 CMap 残缺,自动 OCR
大型 PPT 转 PDF 90 页 0.5 秒 默认路径(无需 OCR)
65MB .doc 169MB 文件 0.1 秒 antiword 极速专线
大型 docx(475K 字) 562KB 11.2 秒 python-docx fallback
OCR 自检总开销 - < 200ms 3 页抽样 + 字符统计

批量 OCR 修复脚本(v1.4 新增)

re_ocr_corrupted.py —— 批量扫描乱码 summary,自动用新版本 utils 重新提取:

# 干跑(不写文件)
python3 re_ocr_corrupted.py --dry-run --max 10

# 实际批量(处理所有乱码)
python3 re_ocr_corrupted.py --max 100

# 只处理指定 PDF
python3 re_ocr_corrupted.py --pdf-list "path1.pdf,path2.pdf"

行为:

  1. 扫 archives/ 找出乱码 summary
  2. 按 basename 匹配源 PDF
  3. 调 extract_pdf_text() 重跑(自动 OCR fallback)
  4. 写新 summary 到 summaries/(带新时间戳)
  5. 覆盖 archives/ 里对应 basename 的所有乱码版本
  6. 输出 JSON 报告(含每份文件路径/字数/成功状态)

文档解析架构(v2.0)

架构图

                    ┌──────────────────────────────────────┐
                    │         kreuzberg 统一提取层           │
                    │  (pypdfium2 / python-calamine / pandoc) │
                    └───┬────────────────────────────────┬───┘
                        │                              │
                自动判断 │                              │
                        ▼                              ▼
              ┌─────────────────┐           ┌─────────────────────┐
              │  kreuzberg 直提  │           │  antiword 极速专线  │
              │ PDF/DOCX/XLSX/  │           │   (.doc 文件专用)    │
              │ PPTX/MD/图片OCR │           │   成功率 85%,<1秒   │
              └─────────────────┘           └─────────────────────┘
                        │                              │
                        │         ┌──────────────────────────────┐
                        │         │     soffice 兜底转换          │
                        │         │ (.doc/.xls/.ppt antiword失败) │
                        │         │  60秒硬超时(消除误判watchdog)│
                        │         └──────────────────────────────┘
                        ▼                              │
              ┌──────────────────────────────────────────────┐
              │              文本输出(content)              │
              │  → summaries/ 摘要文件 → generate_catalog.py  │
              └──────────────────────────────────────────────┘

文件类型 × 提取方式

格式 主方案 依赖 成功率 单文件速度
PDF kreuzberg (pypdfium2) 无 ~100% 0.05-0.7s
DOCX kreuzberg + pandoc pandoc 3.9+ 100% 0.12-3s
XLSX kreuzberg (python-calamine) 无 100% 0.1-0.5s
PPTX kreuzberg + pandoc pandoc 3.9+ 100% 0.02-0.2s
MD kreuzberg + pandoc pandoc 3.9+ 100% <0.01s
.doc antiword 优先 antiword 85%,<1秒 <0.02s
.doc(失败) soffice 兜底 LibreOffice ~15% 2-21s
.xls soffice → XLSX LibreOffice ~95% 2-10s
.ppt soffice → PPTX LibreOffice ~95% 2-10s
图片 kreuzberg 内置 OCR tesseract ~90% 3-10s

antiword 极速专线

# 实测数据:
# 169MB 超大文件 → 26万字符,0.02秒完成
# 正常 .doc(0.1-15MB)→ <1秒
# 成功率 85%,覆盖绝大多数 .doc 文件
result = subprocess.run(['antiword', filepath], capture_output=True, timeout=10)

kreuzberg 统一提取层

kreuzberg 是专业的非结构化文档文本提取库(支持 20+ 格式),内部自动路由:

  • PDF → pypdfium2
  • XLSX → python-calamine
  • DOCX/PPTX/MD → pandoc
  • 图片 → 内置 OCR(tesseract)

关键词库(中英双语)

中文(47个): 房产、房价、房地产、居民、消费、股市、经济、政策、利率、通胀、人民币、A股、美联储、PBOC、GDP、股票、资产、投资、债券、银行、PPI、CPI、PMI、M2、就业、失业、汽车、新能源、AI 等

英文(70+个): property、real estate、GDP、inflation、CPI、PPI、PMI、PBOC、Fed、consumer、economy、growth、housing、stock market、EV、AI 等

标签输出语言: 自动判断——英文内容匹配英文关键词输出英文标签,中文内容匹配中文关键词输出中文标签

定时任务兼容性

任务 ID 调用方式 结论
23:00分析新文档 f3536e18 绝对路径 python3 run_analysis.py ✅ 无需修改
07:00生成财经早报 b741c6d5 Node.js 脚本 ❌ 不相关
08:00发送财经早报 a7cbaacc 读取文件发送 ❌ 不相关
09:00发送文档摘要 89b4cf75 读取 summaries 目录 ❌ 不相关

迁移到新电脑

⚠️ 迁移前必读:~/.openclaw/workspace/knowledge/ 目录可能包含:

  • 私人合同/财务文档的 OCR 摘要
  • 个人分析报告
  • 飞书推送缓存

建议:

  1. 先 du -sh ~/.openclaw/workspace/knowledge/ 看大小

  2. 排除 .trash/、.analysis/cache/ 后再迁移

  3. 用 rsync -av --exclude='.trash' ... 而不是 scp -r

  4. 复制目录(推荐 rsync):

    rsync -av --exclude='.trash' --exclude='.analysis/cache' \
      ~/.openclaw/workspace/knowledge user@new-mac:~/.openclaw/workspace/
    
  5. 在新电脑运行 bash setup.sh <飞书用户ID>

  6. 重新注册定时任务(Job ID 会变)

避坑指南

问题 原因 解决
LibreOffice 超时 watchdog 误判大文件为卡死 v2.0 移除 watchdog,60秒硬超时
.doc 提取慢 统一走 LibreOffice antiword 专线,169MB 文件 0.02秒
DOCX/PPTX 处理失败 pandoc 未安装 brew install pandoc
PDF 提取乱码(OCR 不工作) ocrmypdf --skip-text 跳过乱码页 v1.4 改为 --force-ocr 强制 OCR
PDF 漏检 CMap 残缺 没主动判断是否乱码 v1.4 is_cmap_broken() 自检(阈值 0.03)
文本被截断到 8000 字 硬编码 [:8000] 太短 v1.4 MAX_EXTRACT_LEN = 500_000
.doc 提取失败 lightpdf 处理过的 .doc 乱码 v1.4 ocr_office_via_ocr() 兜底
summary 文件名过长失败 畸形 PDF 名 244+ bytes + 时间戳超 NAME_MAX v1.4.1 sanitize_filename() + Errno 63 重试
静默失败(不知道哪个文件) 不抛异常 v1.4 PDFExtractError / ExtractError 含路径
飞书无 exec 工具 tools 策略限制 添加 alsoAllow: [exec, process]
BGE-M3 卡顿 16GB 内存不足 继续用 nomic-embed-text

关键路径

内容 路径
Skill 目录 ~/.openclaw/workspace/skills/knowledge-base-setup/
知识库 ~/.openclaw/workspace/knowledge/
分析脚本 ~/.openclaw/workspace/knowledge/.analysis/
目录缓存 ~/.openclaw/workspace/knowledge/.analysis/.catalog_cache.json
摘要输出 ~/.openclaw/workspace/knowledge/.analysis/summaries/
文章目录 ~/.openclaw/workspace/knowledge/文章目录/文章目录.md
OpenClaw 配置 ~/.openclaw/openclaw.json

⚠️ 临时文件处理(v1.4.3 修复)

OCR 流程会把 PDF 复制到临时目录处理。

v1.4.3 之前:使用 /tmp/ocrmypdf_work、/tmp/office_ocr_work、 /tmp/office_convert 共享路径,存在以下风险:

  • 多进程并发可能冲突
  • 多用户系统下其他用户可访问(权限默认 755)
  • 处理失败时临时文件残留

v1.4.3 修复:

  • 用 tempfile.mkdtemp(prefix="...") 创建 per-run 私有目录(权限 0o700)
  • 处理完成后立即 shutil.rmtree 清理
  • tempfile.mkstemp 创建稳定输出文件(避免被 finally 误删)

剩余风险:极端情况下(机器突然断电)可能残留临时目录。 建议定期清理 /Users/home/.openclaw/tmp/ 下 ocrmypdf_*、office_* 前缀目录。

版本历史

版本 日期 更新内容
1.0.0 2026-05-10 初始版本,PyMuPDF + LibreOffice 链路
1.1.0 2026-05-13 三步 PDF 处理,关键词库,双语标签
1.2.0 2026-05-21 分批处理优化,280秒断点
1.2.1 2026-05-22 utils.py 共享模块重构,LibreOffice 熔断机制
1.3.0 2026-05-28 kreuzberg 统一提取层 + antiword 专线 + pandoc
1.4.0 2026-08-12 CMap 残缺度自检 + 50万字完整提取 + OCR fallback 到 .doc
1.4.1 2026-08-12 run_analysis.py: sanitize_filename + Errno 63 重试 + 清理 temp_docs 畸形文件