首页
/ graphify 增量数据接入与监听指南:用 /graphify add 抓取 URL 与 --watch 守护知识图谱

graphify 增量数据接入与监听指南:用 /graphify add 抓取 URL 与 --watch 守护知识图谱

2026-09-07 10:41:47作者:明树来

本篇是 graphify 项目(说明文档)面向 Agent / 助手(如 Kiro、Claude Code、Cursor 等)的实操参考文档解读,聚焦两个不属于默认构建流程的扩展能力:通过 /graphify add <url> 把外部 URL(推文、论文、视频、PDF、网页等)抓取入库,以及通过 --watch 在后台监听目录、让代码变更自动触发图谱重建。读完本文,你将掌握两套命令的完整参数与执行语义、底层抓取管线与"代码变更免 LLM 重建 / 文档变更标记需语义重提"的分流策略,并能在多 Agent 写代码的工作流里用 --watch 保持图谱常新。

原始参考文档位于 tools/skillgen/expected/graphify__skills__kiro__references__add-watch.md,与 graphify/skills/kiro/references/add-watch.md 内容一致,是 graphify 为不同编码 Agent 平台(Kiro、Claude、Codex、Gemini 等)生成的 skill 参考页之一(生成器见 tools/skillgen),主 skill 定义可参见 graphify/skill-kiro.md

一、背景:为什么需要"加料"与"守护"两条路径

graphify 的默认构建流程是:把 raw/ 语料目录中的代码、文档、论文、图片等一次性抽取成可查询的知识图谱(产出 graph.jsonGRAPH_REPORT.mdgraph.html 等,见 docs/how-it-works.md)。但真实工作流是持续演进的:

  • 用户丢来一个链接(博客、推文、arXiv 论文、一段视频或一份 PDF),希望它进入语料并被后续查询命中——对应 /graphify add <url>
  • 多 Agent 波次并发写代码,每次提交都希望图谱自动跟上,但又不希望为无关文件的读事件反复重建——对应 --watch

addwatch 都不是默认构建的一部分,因此参考文档要求:只有当用户运行了 /graphify add <url> 或传入了 --watch 时才加载本参考。它们共享同一套产物目录与增量合并逻辑,属于图谱的"持续喂养"层。

二、/graphify add:抓取 URL 并入语料

2.1 调用方式与"正确解释器"约定

文档给出的执行片段以 $(cat graphify-out/.graphify_python) 开头。该文件是 graphify skill 在安装时写入的一个解释器探针(见 graphify/skill-kiro.md):把实际装有 graphify 的 Python 可执行文件路径保存在 graphify-out/.graphify_python 中,后续所有 bash 块都通过 $(cat graphify-out/.graphify_python) 引用它,从而避免 python3 指向了未安装 graphify 的解释器。

$(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 抓取者名字,写入节点元数据 用户提供姓名时填写
CONTRIBUTOR 贡献者名(团队图谱场景) 同上,缺省回退为 author

对应的函数签名在 graphify/ingest.py

def ingest(url: str, target_dir: Path, author: str | None = None, contributor: str | None = None) -> Path:
    """Fetch a URL and save it into target_dir as a graphify-ready file.
    Returns the path of the saved file."""

ingest 会先 mkdir(parents=True, exist_ok=True) 确保目标目录存在,然后返回保存文件的路径。CLI 层面等价命令是 graphify add <url> [--author Name] [--contributor Name] [--dir ./raw](见 graphify/cli.py),可直接在 shell 中使用。

2.2 错误处理契约:两种异常不能静默吞掉

文档强调:命令出错时必须把原因告知用户,不能静默继续。源码把失败明确分成两类(graphify/ingest.py):

  • ValueError:URL 校验失败。ingest 在抓取前先调用 validate_url(url),任何非法输入都会以 ValueError(f"ingest: {exc}") 形式抛出。典型场景见 graphify/security.py:只允许 http / https scheme,拒绝 file://ftp://data: 等;拒绝已知云元数据主机名;解析 DNS 后拒绝回环、私网、保留网段(防止 SSRF)。
  • RuntimeError:抓取阶段失败。urllib.error.HTTPError(非 2xx 状态)、urllib.error.URLError(DNS/连接失败)、OSError(超限)都会转成 RuntimeError(f"ingest: failed to fetch ...") 抛出。

因此参考片段里两个 except 分支分别捕获这两类错误并 sys.exit(1)。真正部署在 skill 中时,还应当在调用 ingest 前先检查 graphify-out/.graphify_python 是否存在——若用户删除了 graphify-out/,应先重新解析解释器再继续(参考各 skill 文件中 ".graphify_python 缺失时重建" 的步骤)。

2.3 抓取成功后的收尾:自动跑 --update

ingest 只是把文件保存进语料目录,尚未产生任何图谱节点。文档明确要求:保存成功后,自动对 ./raw 执行 --update 管线,把新文件合并进现有图谱(该管线最终落到 graphify/watch.py_rebuild_code 全量/增量重建)。CLI 提示语 Run /graphify --update in your AI assistant to update the graph.graphify/cli.py)即是给 Agent 的指令锚点。

2.4 支持抓取的 URL 类型:自动探测分发

参考文档列出的类型并非手写分支,而是由 _detect_url_type 依据 URL 特征自动判别(graphify/ingest.py):

URL 特征 判定类型 实际处理 落盘格式
twitter.com / x.com tweet 通过 publish.twitter.com/oembed 拉取(x.com 会先归一化为 twitter.com),解析 author_name 与正文;oEmbed 失败则存 URL 存根 .md,frontmatter 含 type: tweetsource_urlcaptured_at
arxiv.org arxiv 正则提取 \d{4}\.\d{4,5} 论文号,转成 export.arxiv.org/abs/<id> 抓摘要、标题、作者 arxiv_<id>.md(小数点替换为下划线),frontmatter 含 type: paperarxiv_idpaper_authors
github.com github 目前未特化(源码中只是先判出类型,未进入专门抓取分支) 走通用网页路径
youtube.com / youtu.be youtube 交给 graphify/transcribe.pydownload_audio,用 yt-dlp 只下音频流(bestaudio[ext=m4a]/bestaudio/best),文件名基于 URL 的 SHA-1 前 12 位,如 yt_<hash>.m4a,已下载过则直接命中缓存 音频文件;下一次运行转写为 .txt
路径以 .pdf 结尾 pdf 二进制直下 .pdf
.png/.jpg/.jpeg/.webp/.gif image 二进制直下,扩展名缺失时回退 .jpg 原图;Claude vision 在下一次运行抽取
以上皆非 webpage 抓 HTML → 提取 <title> → 先剥离 <script>/<style> 再经 markdownify(heading_style="ATX", bullets="-", strip=["img"])转 Markdown;未装 markdownify 时退化为基本标签剥离并截断 8000 字符;正文截断至 12000 字符 .md,frontmatter 含 type: webpagetitle

所有文本类内容都带有 YAML frontmatter,且经由 _yaml_str 安全转义(graphify/ingest.py),防止抓取到的标题/正文通过未转义的控制字符(\t\0、Unicode U+2028/U+2029 等)逃逸出双引号标量、注入兄弟 YAML 键。文件命名由 _safe_filenamenetloc+path 清洗为安全文件名并截断到 80 字符;重名文件通过 _<counter> 后缀避免覆盖(最多尝试到 999)。

需要注意两个可选依赖

  • 视频类需要 pip install 'graphifyy[video]'(含 yt-dlp 与 faster-whisper,见 graphify/transcribe.py 的 ImportError 提示;转写模型可用环境变量 GRAPHIFY_WHISPER_MODEL 覆盖,默认 base);
  • 通用网页若想获得干净的 Markdown 而非降级文本,需要 markdownify

2.5 底层安全栅栏:add 不是裸爬虫

ingest 的每一次网络访问都经过 graphify/security.py 的 SSRF 防护体系,这也是"把任意 URL 交给 Agent 执行"时的关键安全边界:

  • validate_urlsecurity.py):scheme 白名单 + 云元数据主机黑名单 + DNS 解析后对每个地址做私网/保留网段拦截;
  • _SSRFGuardedHTTP(S)Connectionsecurity.py):连接前只解析一次 DNS 并校验 IP,校验与建连用同一个 IP,杜绝 DNS-rebind 的 TOCTOU 竞态;
  • _NoFileRedirectHandlersecurity.py):每个重定向目标都会重新 validate_url,防开放重定向把 http:// 引到 file:// 或内网;
  • safe_fetchsecurity.py):流式读取并硬性限制响应体积(超过即抛 OSError 中止),超时默认 30s;文本抓取走 safe_fetch_text(默认 15s 超时,UTF-8 容错解码)。

三、--watch:后台监听目录,按需自动重建

3.1 启动方式与参数

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

INPUT_PATH 换成要监听的目录。模块入口解析逻辑在 graphify/watch.py:路径缺省为当前目录 .--debounce 缺省 3.0 秒。CLI 等价入口是 graphify watch [path]graphify/cli.py),路径不存在时报错退出,缺少 watchdog 依赖时给出 pip install watchdog 的安装提示。

启动后终端会打印三行状态(watch.py):正在监听哪个目录、代码变更会自动重建而文档/图片变更需 /graphify --update、当前 debounce 值。按 Ctrl+C 停止

3.2 双通道分流:AST 快通道与 LLM 慢通道

watch 的核心是按变更文件类型分流(决策逻辑见 _batch_triggers_rebuild_batch_needs_llm_flagwatch.py):

变更内容 立即行为 产物 是否需要 LLM
纯代码文件.py/.ts/.go 等 AST 可抽取扩展名),或任何被删除的文件 立即重跑 AST 抽取 + 重建 +(必要时)聚类 graph.jsonGRAPH_REPORT.md 自动更新,缺失/过期的 graph.html 也会被重生成(_reconcile_graph_html
文档 / 论文 / 图片等非代码文件仍在磁盘上 写入 graphify-out/needs_update 标记文件并打印提示 等待用户手动运行 /graphify --update 是(LLM 语义重抽取)

代码文件走 _rebuild_codewatch.py),内部链条是:detect() 扫描 → 对变更文件做增量 extract() → 与既有 graph.json 做 reconcile(保留未变更文件的历史节点、驱逐已删除源的陈旧节点)→ cluster() 社区检测 → report.generate() → 原子写回。即使检测到拓扑无变化,也会智能地"不动产物"或只补渲染缺失的 HTML(watch.py)。

非代码文件之所以要手动触发,是因为文档/论文/图片需要 LLM 语义抽取(视频转写后的 .txt、视觉图片理解同理)——AST 快通道无法生成这些语义节点。文档要求:标记文件存在时打印 [graphify check-update] Pending non-code changes ... 并提示运行 /graphify --update(见 watch.pycheck_update)。

3.3 Debounce:把"一波并发写"折叠成一次重建

默认 3 秒的防抖(watch.py--debounce 默认值)语义是:等到文件活动停止满 N 秒才触发。监听循环每 0.5s 检查一次 time.monotonic() - last_trigger >= debouncewatch.py),因此一个 Agent 波次内并发的几十次写入只会聚合成一个批次触发一次重建,而不是每写一个文件就重建一遍。watch() 还会加载一次 .graphifyignore 模式缓存,并用 _is_read_only_event 过滤掉 opened/closed_no_write 这类纯读事件,避免 watcher 自己读文件触发死循环(watch.py)。

被监听扩展名集合来自 CODE_EXTENSIONS | DOC_EXTENSIONS | PAPER_EXTENSIONS | IMAGE_EXTENSIONSwatch.py,常量定义在 graphify/detect.py),点开头路径、graphify 自身输出目录 graphify-out 内的事件会被排除。

3.4 并发安全与可靠性设计(源码级细节)

watch 之所以适合多 Agent 并行场景,底层有几层防护(这些不在参考文档中列出,但决定了它的生产可用性):

  • 每仓库重建锁_rebuild_lockwatch.py)基于 fcntl.flock,非阻塞获取,持锁进程把 PID 写入 graphify-out/.rebuild.lock;未抢到锁的增量提交先把自己的变更集写入 graphify-out/.pending_changes 队列,由持锁进程在重建前后排空合并,避免变更集被静默丢弃;
  • 原子写入:新图先写 .graph.tmp.jsonreplacegraph.json,防止中途崩溃留下截断文件;
  • 收缩防护_check_shrinkwatch.py)拒绝"节点数异常减少"的写入(除非显式删除或 --force),防止半截的语义抽取 chunk 静默清空图谱;
  • 平台差异:macOS 上使用轮询观察器(PollingObserver)以避免 FSEvents 漏掉编辑器的快速保存;Windows 无 fcntl 时锁自动降级为 no-op。

3.5 Agentic 工作流的推荐用法

参考文档给出的落地建议:

在多 Agent 工作流中,在后台终端运行 --watch。Agent 波次之间的代码改动会被自动拾取;如果 Agent 还写了文档或笔记,则在这些波次之后需要手动执行一次 /graphify --update

即:--watch 负责代码主线的"免打扰自动更新",/graphify --update 负责需要 LLM 的语义材料的"按需补齐"。两者共享同一套增量合并(_reconcile_existing_graphwatch.py),所以交替使用不会互相破坏既有节点。

四、工作目录约定小结

两类能力都围绕以下目录布局运转(输出目录名默认 graphify-out,可用环境变量 GRAPHIFY_OUT 覆盖,见 graphify/paths.py):

路径 角色
raw/(或 ./raw /graphify add 默认落盘目录,也是默认语料根目录
graphify-out/.graphify_python 解释器探针文件,add/watch 命令都通过它选择 Python
graphify-out/graph.json 图谱数据,watch 重建的目标产物
graphify-out/GRAPH_REPORT.md 人读报告,随代码重建自动刷新
graphify-out/needs_update 非代码变更待处理的标记文件(LLM 语义重抽取信号)
graphify-out/.rebuild.lock / .pending_changes 并发重建锁与待办变更队列(内部机制)

五、验证与测试入口

若想了解该行为在仓库中的验证方式,相关测试集中在 tests/test_watch.pytests/test_incremental.pytests/test_incremental_mtime_collision.pytests/test_office_incremental.py 等文件中;add 的抓取分派可在 graphify/ingest.py 直接阅读,转写链路见 graphify/transcribe.py

六、一句话总结

/graphify add <url> 把外部世界(推文、论文、视频、PDF、网页)安全地变成语料文件并立即触发 --update 并入图谱;--watch 则让代码库的持续演进"零 LLM 成本"地自动反映到 graph.json 中,而对文档/图片这类需要语义理解的材料,用 needs_update 标记把决策权交还给 /graphify --update。两者相加,就构成了一条"抓取即入库、改动即重建、语义材料按需补齐"的图谱持续喂养闭环。

登录后查看全文
热门项目推荐
相关项目推荐