首页
/ graphify 数据增补实战:用 `add` 抓取任意 URL、用 `--watch` 监视文件夹并自动更新知识图谱

graphify 数据增补实战:用 `add` 抓取任意 URL、用 `--watch` 监视文件夹并自动更新知识图谱

2026-09-07 20:08:49作者:侯霆垣

graphify 的核心默认流程是扫描本地 ./raw 语料并构建可查询的知识图谱(graph.jsonGRAPH_REPORT.md)。但真实项目中,语料往往来自外部:一篇文章、一篇论文、一条推文、一段视频或一个网页。本文围绕 skill 参考文档 add-watch(其在 Droid 等各平台 skill 中的同名副本见 tools/skillgen/expected/graphify__skills__droid__references__add-watch.md),讲解两套非默认的"数据增补"能力:/graphify add <url> 负责把远程 URL 抓取进语料库,--watch 负责让文件夹变更自动触发增量重建。读完你会掌握这两种能力的触发条件、完整命令、URL 自动识别规则、代码级行为差异,以及在多 Agent 工作流中的正确用法。

两种能力概览与触发时机

这两个功能不属于默认构建流程,只有在用户显式触发时才需要加载对应技能逻辑:

  • /graphify add <url>:抓取一个远程 URL,将内容保存进语料库(默认写入 ./raw),再触发后续更新管线把新文件合并进已有图谱。
  • --watch:以后台进程监视一个文件夹,文件变化时自动更新图谱。

从源码结构看,二者分别对应 graphify/ingest.py(URL 抓取与落盘)和 graphify/watch.py(目录监视与增量重建)。CLI 入口均在 graphify/cli.pyadd 子命令见 cli.py 的 add 分支watch 子命令见 cli.py 的 watch 分支)。

一、/graphify add:把 URL 变成可建图的语料

1. 完整调用命令

skill 中通过 graphify-out/.graphify_python 文件记录"真正安装了 graphify 的解释器路径",因此所有 Python 调用都经 $(cat graphify-out/.graphify_python) 间接执行,避免用错解释器。参考文档给出的完整抓取命令如下:

$(cat graphify-out/.graphify_python) -c "
import sys
from graphify.ingest import ingest
from pathlib import Path

try:
    out = ingest('URL', Path('./raw'), author='AUTHOR', contributor='CONTRIBUTOR')
    print(f'Saved to {out}')
except ValueError as e:
    print(f'error: {e}', file=sys.stderr)
    sys.exit(1)
except RuntimeError as e:
    print(f'error: {e}', file=sys.stderr)
    sys.exit(1)
"

使用前需要做三处替换:

占位符 含义 说明
URL 要抓取的地址 支持多种类型,自动识别,见下文
AUTHOR 用户名 用户提供了才填写,会写入文件 frontmatter
CONTRIBUTOR 贡献者名 同理;团队图谱场景用于溯源

错误处理策略是硬性要求ingest 抛出 ValueError(典型如 URL 校验不通过)或 RuntimeError(典型如网络拉取失败)时,命令以非零码退出并向用户说明失败原因,不得静默继续

2. 命令行等价写法

同样的能力在 cli.py 的 add 分支 有原生 CLI 入口,参数解析逻辑(--author/--contributor/--dir)与 Python API 一一对应:

graphify add <url> [--author Name] [--contributor Name] [--dir ./raw]

其中默认 --dir./raw;成功后终端会提示 "Saved to …" 并提醒 "Run /graphify --update in your AI assistant to update the graph."——这与参考文档中"保存成功后自动对 ./raw 执行 --update 管线,把新文件合并进既有图谱"的要求一致。

3. 底层实现:ingest() 的五步流程

核心函数为 ingest(),其流程可拆为:

  1. URL 类型自动检测:调用 _detect_url_type() 按域名与路径后缀分类。
  2. 安全校验:调用 graphify/security.py 暴露的 validate_url(校验失败抛 ValueError),实际网络请求通过 safe_fetch/safe_fetch_text 完成。
  3. 按类型抓取:不同类型走不同抓取函数(见下表),网络/IO 异常统一包装为 RuntimeError 抛出。
  4. 安全命名落盘_safe_filename() 把 URL 转成磁盘安全文件名(截断至 80 字符);若文件已存在则自动追加 _1_2 计数器,不覆盖已有文件。
  5. 返回保存路径:调用方拿到 Path 后打印结果,再进入 --update 管线。

4. 支持的 URL 类型(自动识别)

参考文档列出 6 类来源,其底层判定逻辑与落盘格式如下:

URL 类型 判定依据 抓取方式与产物 关键实现
YouTube / 任意视频 域名含 youtube.com/youtu.be 用 yt-dlp 下载音频,下次运行时转写成 .txt;需先 pip install 'graphifyy[video]' download_audio()
Twitter / X 域名含 twitter.com/x.com 经 oEmbed 拉取,存为 .md(含推文正文与作者) _fetch_tweet()
arXiv 域名含 arxiv.org 抓取摘要+元数据,存为 .md _fetch_arxiv()
PDF 路径以 .pdf 结尾 直接二进制下载为 .pdf _download_binary()
图片 .png/.jpg/.jpeg/.webp/.gif 直接下载原图,下次运行由 Claude vision 提取语义 同上
任意网页 兜底 HTML 转 Markdown 后存为 .md _fetch_webpage()

几点值得展开的细节:

  • 文本类来源统一产出带 YAML frontmatter 的 .md。例如 tweet 会写入 source_urltype: tweetauthorcaptured_atcontributor 元数据;arXiv 额外带 arxiv_idtype: paperpaper_authors。这些 frontmatter 会被后续语义提取读作节点元数据。
  • YouTube 视频是"分步"而非"一步到位"ingest 只负责下载音频到语料目录,真正的语音转写发生在下一次运行(转成 .txt 后再进图谱)。对应的可选依赖声明在 pyproject.tomlvideo extra(faster-whisper + yt-dlp>=2026.6.9),因此文档明确要求先安装 graphifyy[video](注意包名是双 y 的 graphifyy,见 pyproject.toml)。
  • 网页转 Markdown 有容错:参考文档提到 html2text,而实际源码 _html_to_markdown() 优先使用 markdownify(属于 pdf extra 依赖),并总是先剥除 <script>/<style> 内容防止脚本文本泄漏;若两者都不可用则退化为基础标签剥离并截断到 8000 字符。
  • frontmatter 做了 YAML 注入防护_yaml_str() 会转义换行、制表符、\0、U+2028/U+2029 等所有 YAML 视作换行的字符,防止恶意网页标题等外部内容逃逸出双引号标量、注入兄弟 YAML 键。
  • GitHub 链接也在识别范围内_detect_url_type()github.com 有独立分类,但因不属于上述专门分支,最终落入网页抓取逻辑(保存为 .md)。

二、--watch:让图谱跟上文件夹的变化

1. 完整命令与防抖参数

$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3

INPUT_PATH 是要监视的文件夹;--debounce 默认 3 秒,表示文件活动停止后的等待窗口——目的是让"一波并行的 Agent 写入"只触发一次重建,而不是每个文件各自触发一次。等价 CLI 写法为 graphify watch [path](默认监视当前目录,见 cli.py 的 watch 分支)。

启动后会输出监视路径与防抖值,按 Ctrl+C 停止。监视的扩展名集合 _WATCHED_EXTENSIONSCODE_EXTENSIONS ∪ DOC_EXTENSIONS ∪ PAPER_EXTENSIONS ∪ IMAGE_EXTENSIONS 合并而来(见 graphify/watch.pygraphify/detect.py 中定义,代码/文档/论文/图片四大类的来源)。

2. 变更分流:两种截然不同的响应

监视器发现变化后按"变了什么"决定动作,这是 --watch 最核心的行为契约:

  • 纯代码文件变化(.py.ts.go 等):立即重跑 AST 提取 + 重建 + 聚类,全程不需要 LLMgraph.jsonGRAPH_REPORT.md 自动更新。
  • 文档、论文或图片变化:写入 graphify-out/needs_update 标记文件并打印通知,提示运行 /graphify --update(这类文件需要 LLM 语义重提取,AST 快速通道覆盖不了)。

对应实现位于 watch() 主循环,用两个谓词做分流:

  • _batch_triggers_rebuild():批次含代码扩展名或含删除事件即为 True。代码重建无需 LLM;任何类型文件的删除也会触发重建,因为语料对账(reconcile)需要把磁盘上已消失的源节点逐出图谱,无需 LLM 也能做。
  • _batch_needs_llm_flag():批次里存在仍存活的非代码文件才写 needs_update 标记——纯删除批次不该留下一个过期的标记。

写标记与提示的落点在 _notify_only():在 watch_path/graphify-out/needs_update 写入内容并打印"Run /graphify --update in Claude Code to update the graph"。配套的 _check_update() 则用于 cron 等场景安全地探测该标记是否存在(始终返回 True,不会让 cron 误报警)。

3. 增量重建的底层保障

--watch 的"快"来自多层工程设计(源码均在 graphify/watch.py):

  • 防抖循环 + watchdog:主循环每 0.5 秒轮询一次 pending 状态,距离最后事件超过 debounce 才触发。macOS 上改用轮询式观察器(PollingObserver),规避 FSEvents 在某些编辑器快速保存时的漏事件问题。
  • 只读事件过滤:Linux 的 inotify 会对每次文件 open/close 上报 opened/closed_no_write 事件,若不过滤,监视器会把自己重建时的读取行为当成变更,陷入自我触发死循环。_is_read_only_event() 专门排除这类只读事件。
  • 忽略规则一次加载:启动时一次性解析 .graphifyignore(并尊重持久化的 gitignore 设置),避免每个文件系统事件都重新解析规则文件;dot 目录与 graphify-out/ 自身的写入也会被过滤,防止监视器重建产物再次触发自身。
  • 锁与防塌缩:重建通过 _rebuild_lock()flock 互斥;对既有图的合并走 _reconcile_existing_graph(),采用"fail-closed"策略——文件仍存在但离开扫描语料 ≠ 删除,只有活的忽略规则命中或磁盘上确实消失才做逐出,避免过滤规则误伤导致节点被静默批量清空。
  • 变化集不丢失:无法取得重建锁的 post-commit 钩子进程会把变更路径追加写入 .pending_changes,持锁进程重建前后各排空一次并合并(见 _queue_pending())。

相关行为有测试覆盖,例如 tests/test_watch.pytests/test_watch_manifest_location.py

4. Agent 工作流中的正确姿势

参考文档给出的最佳实践是:

--watch 跑在一个后台终端里。Agent 各轮(waves)产生的代码变更会在波次之间被自动拾取;但如果 Agent 同时也在写文档或笔记,你需要在这些波次之后手动执行一次 /graphify --update

原因正在于第二节的分流契约:代码走免 LLM 的即时重建,文档/论文/图片必须经 LLM 语义重提取。因此理想节奏是——Agent 写代码时让 --watch 兜底增量更新;当一波文档类产出结束后,人(或编排层)补一次 --update,让 needs_update 标记清空、语料语义节点完整合并。

三、依赖、适用范围与限制

  • --watch 依赖第三方库 watchdog(未安装时会明确抛出 ImportError: pip install watchdog),对应 pyproject.tomlwatch extra。
  • 视频类 URL 需额外安装 graphifyy[video](yt-dlp + faster-whisper);网页转 Markdown 的推荐路径依赖 markdownify
  • add 写入的新文件默认落在 ./raw,但实际目标目录可用 --dir 调整;保存成功后仍需显式(或由 skill 自动)触发 --update 才能真正进入图谱——add 只负责"入料",不负责"建图"
  • 以上能力面向 Claude Code、Cursor、Codex、Gemini CLI、Droid 等各平台 skill,完整装配流程见对应平台主 skill 文档,例如 graphify/skill-droid.mdgraphify/skill.md
登录后查看全文
热门项目推荐
相关项目推荐