尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Jupyter 笔记本新鲜度检查:用 freeze-check 守住 _quarto.yml 与 ipynb 的同步

发布时间:2026/9/26 10:35:20

资讯中心
01
ARTICLE

Jupyter 笔记本新鲜度检查:用 freeze-check 守住 _quarto.yml 与 ipynb 的同步

Jupyter 笔记本新鲜度检查:用 freeze-check 守住 _quarto.yml 与 ipynb 的同步
1. 当 Quarto 渲染出来的图还是上周的你有没有遇到过这种情况明明昨天刚在 Jupyter 里改完数据清洗逻辑重新跑了一遍 notebook结果quarto render出来的 HTML 里图表还是上周那版。翻回.ipynb一看输出确实更新了但_quarto.yml里注册的 notebook 路径和实际执行状态对不上Quarto 直接用了_freeze/里的旧缓存。这不是 Quarto 的 bug而是「笔记本新鲜度」这件事本身没有自动守门人。_quarto.yml只负责声明哪些 notebook 参与渲染它不关心你的.ipynb最后一次执行是什么时候、输出是不是比源码旧、_freeze缓存是不是已经过期。于是就有了一个很典型的脱节场景源码改了、输出没重跑、缓存还在渲染结果看起来正常实际是旧数据。freeze-check就是用来堵这个口子的。它读取_quarto.yml里manuscript.notebooks注册的路径逐个检查.ipynb是否有输出、最后修改时间、输出时效性、以及_freeze/notebooks/name/缓存状态最后给出一张新鲜度汇总表。适合用 Jupyter 做分析、用 Quarto 发布报告或论文的开发者尤其是那种「渲染前心里没底、不知道哪个 notebook 该重跑」的日常流程。下面我会先讲清楚它检查的四个维度再给一份可复制的配置骨架和检查命令然后完整演示一次「改 ipynb → 发现 stale → 重执行 → 验证同步」的动作。全程不需要你手动比对时间戳。2. 把 freeze-check 接进 Quarto 项目2.1 它到底检查什么freeze-check的核心不是简单比文件修改时间而是四个维度一起看维度检查内容判定意义Has outputs打开.ipynbJSON看 code cell 的outputs数组是否非空没输出说明根本没执行过Last modified.ipynb文件本身的修改时间戳源码是否被改过Outputs agecell metadata 里的执行时间戳如果有输出是什么时候生成的Freeze cache_freeze/notebooks/name/是否存在且含缓存Quarto 会不会直接用缓存四个维度组合出四种状态Current有输出且源码没比输出新、Stale有输出但源码改得比输出晚、Unexecuted任何 code cell 都没输出、Freeze only没 cell 输出但有_freeze缓存Quarto 会走缓存。关键点在于Stale和Freeze only这两种最容易被忽略。前者渲染出来是旧结果后者渲染出来可能连执行都跳过。2.2 前置准备项目结构与依赖假设你的 Quarto 项目长这样my-report/ ├── _quarto.yml ├── _freeze/ │ └── notebooks/ │ ├── notebook-01/ │ └── notebook-02/ ├── notebooks/ │ ├── notebook-01.ipynb │ └── notebook-02.ipynb └── index.qmd_quarto.yml里注册 notebook 的部分通常是这样project: type: book manuscript: notebooks: - notebooks/notebook-01.ipynb - notebooks/notebook-02.ipynbfreeze-check只认manuscript.notebooks这个列表路径写错或漏注册它就会报No notebooks found in _quarto.yml或者把文件标成 missing。所以第一步永远是确认这个列表和磁盘上的.ipynb一一对应。如果你还没配好模型侧的调用环境可以先把 API Key 准备好后面做自动化检查脚本时会用到。入口在 API Keys接入方式看 接入文档。这一步不是必须的纯本地检查也能跑但如果你想把检查结果自动汇总成报告有个稳定的模型接口会省事很多。3. 可复制的配置骨架与检查命令3.1 配置骨架freeze-check本身是一个检查技能落地到项目里我建议放一个scripts/freeze_check.py把读取_quarto.yml、遍历 notebook、判定状态这三步写死。骨架如下import json import os import time from pathlib import Path import yaml QUARTO_YML Path(_quarto.yml) FREEZE_DIR Path(_freeze/notebooks) def load_notebooks(): with open(QUARTO_YML, r, encodingutf-8) as f: cfg yaml.safe_load(f) return cfg.get(manuscript, {}).get(notebooks, []) def has_outputs(nb_path: Path) - bool: with open(nb_path, r, encodingutf-8) as f: nb json.load(f) for cell in nb.get(cells, []): if cell.get(cell_type) code and cell.get(outputs): return True return False def outputs_age(nb_path: Path): with open(nb_path, r, encodingutf-8) as f: nb json.load(f) stamps [] for cell in nb.get(cells, []): meta cell.get(metadata, {}) ts meta.get(execution, {}).get(iopub.execute_input) if ts: stamps.append(ts) return max(stamps) if stamps else None def freeze_exists(nb_path: Path) - bool: name nb_path.stem cache FREEZE_DIR / name return cache.exists() and any(cache.iterdir()) def check_one(nb_path: Path): if not nb_path.exists(): return Missing, None mtime nb_path.stat().st_mtime has_out has_outputs(nb_path) age outputs_age(nb_path) frozen freeze_exists(nb_path) if not has_out and frozen: return Freeze only, mtime if not has_out: return Unexecuted, mtime if age and mtime age: return Stale, mtime return Current, mtime def main(): notebooks load_notebooks() if not notebooks: print(No notebooks found in _quarto.yml) return print(f{Notebook:28}{Has Outputs:14}{Last Modified:22}{Status}) print(- * 78) for rel in notebooks: nb Path(rel) status, mtime check_one(nb) has_out Yes if nb.exists() and has_outputs(nb) else No ts time.strftime(%Y-%m-%d %H:%M, time.localtime(mtime)) if mtime else - print(f{nb.name:28}{has_out:14}{ts:22}{status}) if __name__ __main__: main()这段代码就是freeze-check的本地实现逻辑和前面表格里的四维度完全对应。outputs_age读的是 cell metadata 里的执行时间戳不是所有内核都会写所以判定Stale时如果拿不到 age会退化成只看mtime和是否有输出。3.2 运行检查在项目根目录执行python scripts/freeze_check.py输出会是一张表Notebook Has Outputs Last Modified Status ------------------------------------------------------------------------------ notebook-01.ipynb Yes 2026-02-28 14:30 Current notebook-02.ipynb Yes 2026-03-01 09:15 Stale notebook-03.ipynb No 2026-02-25 11:00 Unexecuted如果出现Stale或Unexecuted脚本会提示你重执行。Quarto 项目里对应的动作是quarto render --execute或者只重跑 notebookjupyter nbconvert --to notebook --execute notebooks/notebook-02.ipynb \ --output notebooks/notebook-02.ipynb注意--execute会重新生成输出并更新 cell metadata 里的时间戳这样下一次freeze-check才会把它判成Current。4. 一次完整的同步验证4.1 改一个 notebook打开notebooks/notebook-02.ipynb随便改一个 cell比如把df.groupby(region).sum()改成df.groupby(region).mean()保存。此时.ipynb的mtime更新了但 cell 输出还是旧的。跑一次检查python scripts/freeze_check.py你会看到notebook-02.ipynb的状态从Current变成Stale因为mtime outputs_age。这就是脱节的信号源码新、输出旧。4.2 重执行并验证执行jupyter nbconvert --to notebook --execute notebooks/notebook-02.ipynb \ --output notebooks/notebook-02.ipynb再跑一次freeze_check.pynotebook-02.ipynb Yes 2026-03-01 09:40 Current状态回到Current说明输出已经跟上源码。这时候再quarto render渲染结果就是新的。如果你在检查过程中想确认某个 notebook 的执行逻辑对不对可以把关键 cell 贴到 模型对话 里让模型帮你核对尤其是涉及数据聚合、时间窗口这类容易写错的地方。4.3 把检查接进日常流程最省事的做法是在quarto render之前挂一个 pre-render 钩子。Quarto 支持_quarto.yml里配project: pre-renderproject: type: book pre-render: python scripts/freeze_check.py这样每次渲染前都会先跑一遍新鲜度检查有Stale或Unexecuted就直接暴露出来不会等到 HTML 出来才发现图是旧的。如果你在做长期编码或 Agent 类的自动化流程可以把检查脚本和重执行命令串成一个任务交给 Coding Plan 里的工作流去跑省得每次手动敲。5. 本篇常见错排查5.1 报 No notebooks found in _quarto.yml说明manuscript.notebooks是空的或者键名写错了。检查_quarto.yml里是不是写成了notebooks:而不是manuscript: notebooks:。Quarto 的 book 项目里 notebook 注册必须在manuscript下写成顶层notebooks不会被识别。5.2 某个 notebook 被标成 Missingfreeze-check按_quarto.yml里的相对路径去找文件路径是相对项目根目录的。如果你在子目录里跑脚本Path(rel)就会解析错。解决办法是脚本里统一用项目根目录做基准ROOT Path(__file__).resolve().parent.parent nb ROOT / rel5.3 状态一直是 Stale重执行也没用大概率是 cell metadata 里没有执行时间戳outputs_age返回None判定逻辑退化成只看mtime。有些内核比如部分老版本 ipykernel不写iopub.execute_input。这时候可以改用nbconvert的--ExecutePreprocessor.record_timingTrue强制记录jupyter nbconvert --to notebook --execute notebooks/notebook-02.ipynb \ --output notebooks/notebook-02.ipynb \ --ExecutePreprocessor.record_timingTrue5.4 Freeze only 状态怎么处理Freeze only表示没有 cell 输出但有_freeze缓存Quarto 渲染时会直接用缓存。如果你希望渲染时真正执行 notebook需要先清掉缓存再重执行rm -rf _freeze/notebooks/notebook-03 jupyter nbconvert --to notebook --execute notebooks/notebook-03.ipynb \ --output notebooks/notebook-03.ipynb清缓存这一步要谨慎确认缓存不是唯一输出来源再删。5.5 检查脚本报 YAML 解析错_quarto.yml里如果有 tab 缩进或者中文冒号yaml.safe_load会直接抛异常。Quarto 的 YAML 必须用空格缩进冒号用英文。可以用python -c import yaml; yaml.safe_load(open(_quarto.yml))单独验证一下配置文件本身能不能解析。6. 把新鲜度检查变成渲染前的默认动作freeze-check的价值不在于它多复杂而在于它把「哪个 notebook 该重跑」这件事从人脑记忆变成了可执行的检查。四维度判定里Stale和Freeze only是最容易骗过眼睛的两种状态前者渲染出旧结果后者直接跳过执行。把检查脚本挂到pre-render每次渲染前自动跑一遍基本就能告别「图是上周的」这类问题。如果你想把检查结果自动汇总、或者在多项目之间统一管理可以走 控制台 配一套 API 调用把freeze_check.py的输出结构化后发给模型做摘要。接入细节在 接入文档 里有完整说明Key 在 API Keys 页面生成。纯本地检查不需要这些但一旦你想把新鲜度检查纳入 CI 或者多人协作流程有个稳定的接口会方便很多。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。