<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Obsidian on iwmei的博客</title><link>https://iwmei.top/tags/obsidian/</link><description>Recent content in Obsidian on iwmei的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>iwmei</copyright><lastBuildDate>Wed, 06 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://iwmei.top/tags/obsidian/index.xml" rel="self" type="application/rss+xml"/><item><title>BilibiliContentFetcher 项目复盘：从单条演示到批处理闭环</title><link>https://iwmei.top/p/bilibilicontentfetcher-from-single-demo-to-batch-workflow/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://iwmei.top/p/bilibilicontentfetcher-from-single-demo-to-batch-workflow/</guid><description>&lt;img src="https://iwmei.top/p/bilibilicontentfetcher-from-single-demo-to-batch-workflow/cover.png" alt="Featured image of post BilibiliContentFetcher 项目复盘：从单条演示到批处理闭环" />&lt;p>这次复盘记录的是 &lt;code>BilibiliContentFetcher&lt;/code> 一次完整落地过程。&lt;/p>
&lt;p>我这次的目标不是做一条能看的演示链路，而是把批量生产链路做稳定，把输出质量做可控，把项目结构做可维护，把最终产物直接落到 Obsidian。&lt;/p>
&lt;p>我按真实时间线展开，尽量保留全过程细节。&lt;/p>
&lt;h2 id="1-起点目标">1. 起点目标
&lt;/h2>&lt;p>我在会话开始阶段先把目标重新收敛，避免中途跑偏。&lt;/p>
&lt;p>当时我给自己定了六个硬条件。&lt;/p>
&lt;ol>
&lt;li>主目标是批量稳定执行，不是单条效果展示。&lt;/li>
&lt;li>输出格式必须严格贴合根目录 &lt;code>Tempate.md&lt;/code>。&lt;/li>
&lt;li>必须使用 DeepSeek 思考模式，不接受本地替代。&lt;/li>
&lt;li>模块结构与链路逻辑要对齐 &lt;code>XHSContentFetcher&lt;/code>。&lt;/li>
&lt;li>最终笔记必须可直接出现在 Obsidian。&lt;/li>
&lt;li>最终代码要推送远程，并补全 CLI 命令文档。&lt;/li>
&lt;/ol>
&lt;p>这一组条件决定了后续所有取舍。&lt;/p>
&lt;h2 id="2-阶段-a">2. 阶段 A
&lt;/h2>&lt;p>这个阶段是质量回归和模板约束加固，时间段是 18 点 20 到 18 点 33。&lt;/p>
&lt;p>我先执行回归测试，确认基线可跑。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">pytest&lt;/span> &lt;span class="n">-q&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果是 &lt;code>5 passed&lt;/code>。&lt;/p>
&lt;p>这一步给了我一个清晰信号，当前代码有可用基础，但输出质量不够。&lt;/p>
&lt;p>我随后集中处理了四类问题。&lt;/p>
&lt;ol>
&lt;li>文本里有泛化表达，观点和结论过于笼统。&lt;/li>
&lt;li>有占位风格残留，章节质感不稳定。&lt;/li>
&lt;li>评论区覆盖不足，子评论利用率偏低。&lt;/li>
&lt;li>视频类笔记有明显模板痕迹。&lt;/li>
&lt;/ol>
&lt;h3 id="21-质量约束改造">2.1 质量约束改造
&lt;/h3>&lt;p>我在 &lt;code>src/bili_content_fetcher/ai_writer.py&lt;/code> 里加了完整质量栅栏。&lt;/p>
&lt;p>关键函数是：&lt;/p>
&lt;ul>
&lt;li>&lt;code>_validate_markdown_output&lt;/code>&lt;/li>
&lt;li>&lt;code>_repair_markdown_once&lt;/code>&lt;/li>
&lt;li>&lt;code>_ensure_quality&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>这三步形成了质量闭环。&lt;/p>
&lt;ol>
&lt;li>先检查结构完整性。&lt;/li>
&lt;li>再执行自动修订。&lt;/li>
&lt;li>最后做二轮兜底，不通过就回退。&lt;/li>
&lt;/ol>
&lt;p>我把检查项写得很细，包含章节顺序、占位词、概念数量、问题数量、评论数量、图片内联要求。&lt;/p>
&lt;h3 id="22-评论区典型性改造">2.2 评论区典型性改造
&lt;/h3>&lt;p>我同时补了评论评分和代表性筛选。&lt;/p>
&lt;p>对应新增逻辑：&lt;/p>
&lt;ul>
&lt;li>&lt;code>_comment_score&lt;/code>&lt;/li>
&lt;li>&lt;code>_select_representative_comments&lt;/code>&lt;/li>
&lt;li>&lt;code>_comment_insight&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>这组改动让评论区从堆数据变成可读观点层。&lt;/p>
&lt;p>我要求每条代表评论都要有我的提炼句，不再只贴原文。&lt;/p>
&lt;h3 id="23-阶段-a-实跑">2.3 阶段 A 实跑
&lt;/h3>&lt;p>我用单条链接做了一次实跑。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">bili-note&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">//&lt;/span>&lt;span class="n">www&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">bilibili&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">opus&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="mf">1195483269038080034&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果是新笔记目录生成成功，结构可用，基础质量明显提升。&lt;/p>
&lt;h2 id="3-阶段-b">3. 阶段 B
&lt;/h2>&lt;p>这个阶段是切到 DeepSeek 真链路，时间段是 18 点 33 到 18 点 36。&lt;/p>
&lt;p>问题不是代码逻辑，而是配置状态。&lt;/p>
&lt;h3 id="31-deepseek-key-缺失">3.1 DeepSeek Key 缺失
&lt;/h3>&lt;p>我检查后确认当前项目没有可用 Key。&lt;/p>
&lt;ol>
&lt;li>项目 &lt;code>.env&lt;/code> 缺失。&lt;/li>
&lt;li>环境变量 &lt;code>DEEPSEEK_API_KEY&lt;/code> 为空。&lt;/li>
&lt;/ol>
&lt;p>我的处理方式是从 &lt;code>XHSContentFetcher&lt;/code> 读取可用配置，再写入当前项目 &lt;code>.env&lt;/code>。&lt;/p>
&lt;h3 id="32-vault-路径空值误解析">3.2 Vault 路径空值误解析
&lt;/h3>&lt;p>我还遇到了一个配置细节问题。&lt;/p>
&lt;p>&lt;code>OBSIDIAN_VAULT_PATH=&lt;/code> 空值被解析为当前目录，&lt;code>bili-note check&lt;/code> 显示默认 Vault 指向点目录。&lt;/p>
&lt;p>我删除了空值行，恢复为未配置状态，避免误写路径。&lt;/p>
&lt;h3 id="33-deepseek-真链路验证">3.3 DeepSeek 真链路验证
&lt;/h3>&lt;p>我重新执行带 DeepSeek 配置的运行命令。&lt;/p>
&lt;p>结果是模型链路跑通，真实生成成功。&lt;/p>
&lt;p>这个节点很关键，说明我已经从本地降级模式切回目标模式。&lt;/p>
&lt;h2 id="4-阶段-c">4. 阶段 C
&lt;/h2>&lt;p>这个阶段是方向纠偏，时间段是 18 点 36 到 18 点 56。&lt;/p>
&lt;p>核心变化只有一句话，批量优先替代单条打磨。&lt;/p>
&lt;p>我在这个阶段做了三件大事。&lt;/p>
&lt;ol>
&lt;li>新增批处理子命令。&lt;/li>
&lt;li>处理 Windows 编码崩溃。&lt;/li>
&lt;li>落地批处理汇总产物。&lt;/li>
&lt;/ol>
&lt;h3 id="41-批处理子命令落地">4.1 批处理子命令落地
&lt;/h3>&lt;p>我在 &lt;code>src/bili_content_fetcher/cli.py&lt;/code> 加了 &lt;code>batch&lt;/code> 子命令体系。&lt;/p>
&lt;p>支持参数包含：&lt;/p>
&lt;ul>
&lt;li>&lt;code>--max-items&lt;/code>&lt;/li>
&lt;li>&lt;code>--fail-fast&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>并新增批处理汇总文件机制，输出 &lt;code>batch_summary_*.json&lt;/code>。&lt;/p>
&lt;p>我同时同步改了 &lt;code>README.md&lt;/code>，保证命令文档可直接执行。&lt;/p>
&lt;h3 id="42-windows-编码崩溃修复">4.2 Windows 编码崩溃修复
&lt;/h3>&lt;p>这一步是阶段 C 的关键故障。&lt;/p>
&lt;p>我当时遇到错误：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">UnicodeEncodeError: &amp;#39;gbk&amp;#39; codec can&amp;#39;t encode character &amp;#39;\ufeff&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>触发点是批量输入文件首行 BOM 加上控制台输出编码冲突。&lt;/p>
&lt;p>我按三步修复。&lt;/p>
&lt;ol>
&lt;li>&lt;code>_load_batch_inputs&lt;/code> 改成 &lt;code>utf-8-sig&lt;/code> 读取。&lt;/li>
&lt;li>输入行统一剥离 &lt;code>\ufeff&lt;/code> 前缀。&lt;/li>
&lt;li>控制台输出前做安全预览过滤，剔除不可编码字符。&lt;/li>
&lt;/ol>
&lt;p>修完后我马上复跑批量命令。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">bili-note&lt;/span> &lt;span class="n">batch&lt;/span> &lt;span class="n">batch_links&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">txt&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-max-items&lt;/span> &lt;span class="mf">2&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果是 &lt;code>2/2&lt;/code> 成功。&lt;/p>
&lt;p>并产出汇总文件：&lt;/p>
&lt;p>&lt;code>output/batch_summary_20260506_185559.json&lt;/code>&lt;/p>
&lt;h2 id="5-阶段-d">5. 阶段 D
&lt;/h2>&lt;p>这个阶段是视频笔记质量二次修复，时间段是 18 点 56 到 19 点 02。&lt;/p>
&lt;h3 id="51-英文噪声词进入概念区">5.1 英文噪声词进入概念区
&lt;/h3>&lt;p>我发现视频类笔记的概念区混入了 &lt;code>and&lt;/code> &lt;code>the&lt;/code> &lt;code>add&lt;/code> &lt;code>no&lt;/code> 这类词。&lt;/p>
&lt;p>这是明显质量缺陷。&lt;/p>
&lt;p>我做了三项补丁。&lt;/p>
&lt;ol>
&lt;li>增加英文停用词 &lt;code>EN_STOPWORDS&lt;/code>。&lt;/li>
&lt;li>增加英文术语白名单 &lt;code>EN_TERM_ALLOWLIST&lt;/code>。&lt;/li>
&lt;li>非股票场景的问题标题改成稳定框架，避免弱标题漂移。&lt;/li>
&lt;/ol>
&lt;h3 id="52-模型结果被回退过度覆盖">5.2 模型结果被回退过度覆盖
&lt;/h3>&lt;p>我还发现质量修订链有副作用。&lt;/p>
&lt;p>多轮修订后，较好的模型版本有时会被过度回退。&lt;/p>
&lt;p>我在 &lt;code>_ensure_quality&lt;/code> 里加了保留策略。&lt;/p>
&lt;p>当结构可用且占位残留已清空时，优先保留最后模型版本。&lt;/p>
&lt;p>这一步直接改善了成文自然度。&lt;/p>
&lt;h3 id="53-阶段-d-复跑">5.3 阶段 D 复跑
&lt;/h3>&lt;p>我用视频批处理单条做复验。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">bili-note&lt;/span> &lt;span class="n">batch&lt;/span> &lt;span class="n">batch_video_only&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">txt&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-max-items&lt;/span> &lt;span class="mf">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果是视频学习报告稳定生成，英文噪声问题消失。&lt;/p>
&lt;h2 id="6-阶段-e">6. 阶段 E
&lt;/h2>&lt;p>这个阶段是大改造，时间段是 19 点 02 到 19 点 28。&lt;/p>
&lt;p>目标很明确，把 Bili 项目和 XHS 项目做同构对齐。&lt;/p>
&lt;h3 id="61-同构基线">6.1 同构基线
&lt;/h3>&lt;p>我先对齐目标模块清单：&lt;/p>
&lt;ul>
&lt;li>cli&lt;/li>
&lt;li>config&lt;/li>
&lt;li>workflow&lt;/li>
&lt;li>fetcher&lt;/li>
&lt;li>media&lt;/li>
&lt;li>video_tools&lt;/li>
&lt;li>deepseek&lt;/li>
&lt;li>markdown_writer&lt;/li>
&lt;li>obsidian&lt;/li>
&lt;li>url_parser&lt;/li>
&lt;li>ocr_engine&lt;/li>
&lt;/ul>
&lt;h3 id="62-新增模块">6.2 新增模块
&lt;/h3>&lt;p>我新增了以下文件。&lt;/p>
&lt;ol>
&lt;li>&lt;code>src/bili_content_fetcher/url_parser.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/fetcher.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/media.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/video_tools.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/ocr_engine.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/deepseek.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/markdown_writer.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/obsidian.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/bili_content_fetcher/workflow.py&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="63-重构模块">6.3 重构模块
&lt;/h3>&lt;p>我重点重构了两个文件。&lt;/p>
&lt;ol>
&lt;li>&lt;code>config.py&lt;/code>
&lt;ul>
&lt;li>增加本地配置文件 &lt;code>~/.bili-content-fetcher/config.json&lt;/code>&lt;/li>
&lt;li>保留 &lt;code>.env&lt;/code> 兼容链路，兼容历史配置&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>cli.py&lt;/code>
&lt;ul>
&lt;li>从 Typer 切到 &lt;code>argparse&lt;/code>&lt;/li>
&lt;li>统一成与 XHS 对齐的 &lt;code>run&lt;/code> &lt;code>config&lt;/code> &lt;code>batch&lt;/code> 命令体系&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="64-文档和入口">6.4 文档和入口
&lt;/h3>&lt;p>我同步重写：&lt;/p>
&lt;ol>
&lt;li>&lt;code>README.md&lt;/code>&lt;/li>
&lt;li>&lt;code>pyproject.toml&lt;/code> 脚本入口，改为 &lt;code>bili_content_fetcher.cli:main&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="65-阶段-e-验收">6.5 阶段 E 验收
&lt;/h3>&lt;p>我执行整套验收。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">pytest&lt;/span> &lt;span class="n">-q&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="n">-help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">batch&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-help&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果：&lt;/p>
&lt;ol>
&lt;li>&lt;code>pytest -q&lt;/code> 变为 &lt;code>8 passed&lt;/code>&lt;/li>
&lt;li>CLI 帮助命令全部可用&lt;/li>
&lt;/ol>
&lt;p>随后我提交并推送。&lt;/p>
&lt;ul>
&lt;li>commit &lt;code>03ac851&lt;/code>&lt;/li>
&lt;li>message &lt;code>feat: align bili workflow with xhs architecture and add full batch CLI&lt;/code>&lt;/li>
&lt;li>push &lt;code>origin/main&lt;/code> 成功&lt;/li>
&lt;/ul>
&lt;h2 id="7-阶段-f">7. 阶段 F
&lt;/h2>&lt;p>这个阶段是最终验收，时间点约 20 点 02。&lt;/p>
&lt;p>目标只有一个，Obsidian 可见。&lt;/p>
&lt;p>我执行命令：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;opus链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-vault-dir&lt;/span> &lt;span class="s2">&amp;#34;C:\Users\yourp\Documents\Obsidian Vault&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-notes-folder&lt;/span> &lt;span class="s2">&amp;#34;BiliNotes&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-open-obsidian&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>验收结果：&lt;/p>
&lt;ol>
&lt;li>DeepSeek 生成成功&lt;/li>
&lt;li>图片 7&lt;/li>
&lt;li>评论 18&lt;/li>
&lt;li>OCR 7&lt;/li>
&lt;li>笔记写入 Vault 成功&lt;/li>
&lt;li>Obsidian 可直接查看&lt;/li>
&lt;/ol>
&lt;p>本次验收产物路径：&lt;/p>
&lt;p>&lt;code>C:\Users\yourp\Documents\Obsidian Vault\BiliNotes\ru-he-zai-5000jia-gu-piao-zhong-shai-xuan-chu-long-tou-gu-bi-li-bi-li\ru-he-zai-5000jia-gu-piao-zhong-shai-xuan-chu-long-tou-gu-bi-li-bi-li.md&lt;/code>&lt;/p>
&lt;h2 id="8-问题清单汇总">8. 问题清单汇总
&lt;/h2>&lt;p>我把全过程问题收敛成七类。&lt;/p>
&lt;ol>
&lt;li>笔记里有占位和模板痕迹。&lt;/li>
&lt;li>评论区代表性不足，回复利用不足。&lt;/li>
&lt;li>项目缺 DeepSeek 配置。&lt;/li>
&lt;li>批处理不是主链路。&lt;/li>
&lt;li>Windows BOM 和编码导致批处理崩溃。&lt;/li>
&lt;li>视频笔记出现英文噪声概念。&lt;/li>
&lt;li>项目结构和 XHS 不一致。&lt;/li>
&lt;/ol>
&lt;p>对应修复策略分别是：&lt;/p>
&lt;ol>
&lt;li>质量校验加自动重写加密度约束。&lt;/li>
&lt;li>评论评分筛选加逐条提炼。&lt;/li>
&lt;li>&lt;code>.env&lt;/code> 补齐加 CLI 缺 Key 明确报错。&lt;/li>
&lt;li>&lt;code>batch&lt;/code> 子命令加失败续跑和汇总。&lt;/li>
&lt;li>&lt;code>utf-8-sig&lt;/code> 加 BOM 剥离加安全输出。&lt;/li>
&lt;li>停用词加白名单加标题稳态框架。&lt;/li>
&lt;li>同构重构模块加统一 CLI 和配置体系。&lt;/li>
&lt;/ol>
&lt;h2 id="9-关键命令流水">9. 关键命令流水
&lt;/h2>&lt;h3 id="91-质量回归">9.1 质量回归
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">pytest&lt;/span> &lt;span class="n">-q&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="92-单条链路">9.2 单条链路
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">bili-note&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;链接&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="93-批量链路">9.3 批量链路
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">bili-note&lt;/span> &lt;span class="n">batch&lt;/span> &lt;span class="n">links&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">txt&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-max-items&lt;/span> &lt;span class="mf">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">batch&lt;/span> &lt;span class="n">links&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">txt&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-max-items&lt;/span> &lt;span class="mf">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="94-配置检查">9.4 配置检查
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">bili-note&lt;/span> &lt;span class="n">check&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">bili_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">config&lt;/span> &lt;span class="n">show&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="95-版本管理">9.5 版本管理
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">commit&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">push&lt;/span> &lt;span class="n">origin&lt;/span> &lt;span class="n">main&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="10-最终状态">10. 最终状态
&lt;/h2>&lt;p>我在导出时间点确认的状态如下。&lt;/p>
&lt;ol>
&lt;li>代码已提交并推送到远程主分支。&lt;/li>
&lt;li>CLI 的 &lt;code>run&lt;/code> &lt;code>batch&lt;/code> &lt;code>config&lt;/code> 全量可用。&lt;/li>
&lt;li>DeepSeek 真链路实跑通过。&lt;/li>
&lt;li>Obsidian 实跑可见笔记。&lt;/li>
&lt;li>测试状态 &lt;code>8 passed&lt;/code>。&lt;/li>
&lt;/ol>
&lt;h2 id="11-我这次最关键的决策">11. 我这次最关键的决策
&lt;/h2>&lt;p>第一，先把批处理链路立成主线，单条只做回归样本。&lt;/p>
&lt;p>第二，把输出质量做成程序约束，不靠模型临场发挥。&lt;/p>
&lt;p>第三，把项目结构和 XHS 同构，换来后续维护和迁移的确定性。&lt;/p>
&lt;h2 id="12-下一步计划">12. 下一步计划
&lt;/h2>&lt;p>下一步我会继续补三组能力。&lt;/p>
&lt;ol>
&lt;li>断点续跑，降低长任务重跑成本。&lt;/li>
&lt;li>OCR 和 LLM 缓存，降低重复计算成本。&lt;/li>
&lt;li>结构化日志，细化阶段耗时和错误分类。&lt;/li>
&lt;/ol>
&lt;p>这次项目的核心结果不是某一条笔记写得好看。&lt;/p>
&lt;p>真正的结果是我把一条从抓取到笔记落地的链路做成了可批量执行、可定位故障、可重复验收的工程系统。&lt;/p></description></item><item><title>XHSContentFetcher 项目复盘：从空仓到可发布 CLI</title><link>https://iwmei.top/p/xhscontentfetcher-from-empty-repo-to-publishable-cli/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid>https://iwmei.top/p/xhscontentfetcher-from-empty-repo-to-publishable-cli/</guid><description>&lt;img src="https://iwmei.top/p/xhscontentfetcher-from-empty-repo-to-publishable-cli/cover.png" alt="Featured image of post XHSContentFetcher 项目复盘：从空仓到可发布 CLI" />&lt;p>这篇是我的全过程复盘，不是摘要版。重点是把“从空仓到可发布版本”的真实路径写清楚。&lt;/p>
&lt;p>我会尽量保留操作细节，包括：&lt;/p>
&lt;ul>
&lt;li>我执行过的关键命令&lt;/li>
&lt;li>我遇到的报错&lt;/li>
&lt;li>我如何定位&lt;/li>
&lt;li>我具体改了什么&lt;/li>
&lt;li>我怎么复测&lt;/li>
&lt;/ul>
&lt;p>文中所有 Cookie / API Key 都是脱敏表达，不写明文。&lt;/p>
&lt;hr>
&lt;h2 id="0-起点与约束">0. 起点与约束
&lt;/h2>&lt;h3 id="01-初始仓库状态">0.1 初始仓库状态
&lt;/h3>&lt;p>项目路径是：&lt;/p>
&lt;p>&lt;code>F:\College\Activ_CodeProjects\PycharmProjects\XHSContentFetcher&lt;/code>&lt;/p>
&lt;p>我一开始先看目录：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Recurse&lt;/span> &lt;span class="o">-File&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当时可见文件基本只有：&lt;/p>
&lt;ul>
&lt;li>&lt;code>计划.md&lt;/code>&lt;/li>
&lt;li>&lt;code>LICENSE&lt;/code>&lt;/li>
&lt;li>&lt;code>venv/&lt;/code>&lt;/li>
&lt;li>&lt;code>.idea/&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>也就是说，业务代码几乎是 0。&lt;/p>
&lt;h3 id="02-我给自己写死的目标来自-计划md">0.2 我给自己写死的目标，来自 &lt;code>计划.md&lt;/code>
&lt;/h3>&lt;ol>
&lt;li>输入小红书分享链接，支持图文和视频。&lt;/li>
&lt;li>抓取正文、评论、图片 / 视频。&lt;/li>
&lt;li>图片 OCR。&lt;/li>
&lt;li>视频关键帧抽取。&lt;/li>
&lt;li>调 DeepSeek 生成可用于 Obsidian 的学习笔记。&lt;/li>
&lt;li>全部做成 CLI，不做 GUI。&lt;/li>
&lt;li>最终产物是“能直接进 Obsidian”的笔记和素材。&lt;/li>
&lt;/ol>
&lt;h3 id="03-现实条件">0.3 现实条件
&lt;/h3>&lt;ol>
&lt;li>我手里有一个参考仓库，但不确定有没有可复用代码。&lt;/li>
&lt;li>我有图文和视频两条测试链接。&lt;/li>
&lt;li>我有开发阶段所需 Cookie 和 DeepSeek Key，后续代码和文档全部脱敏。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="1-预研阶段先确认抓取链路可行">1. 预研阶段：先确认抓取链路可行
&lt;/h2>&lt;p>我没有上来就写框架。我先做了 5 个预研动作。&lt;/p>
&lt;h3 id="11-动作一确认项目现状">1.1 动作一：确认项目现状
&lt;/h3>&lt;p>执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Recurse&lt;/span> &lt;span class="o">-File&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结论：&lt;/p>
&lt;ol>
&lt;li>仓库本体几乎没有业务代码。&lt;/li>
&lt;li>这次必须从零搭。&lt;/li>
&lt;/ol>
&lt;h3 id="12-动作二完整读取计划">1.2 动作二：完整读取计划
&lt;/h3>&lt;p>执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-Content&lt;/span> &lt;span class="n">-Raw&lt;/span> &lt;span class="n">-Encoding&lt;/span> &lt;span class="n">UTF8&lt;/span> &lt;span class="n">计划&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">md
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结论：&lt;/p>
&lt;ol>
&lt;li>技术目标很清楚：抓取 + OCR + 大模型 + Obsidian。&lt;/li>
&lt;li>文风要求也清楚：去 AI 味、逻辑分点、自然表达。&lt;/li>
&lt;/ol>
&lt;h3 id="13-动作三核查参考仓库">1.3 动作三：核查参考仓库
&lt;/h3>&lt;p>执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="nb">ls-remote&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">//&lt;/span>&lt;span class="n">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">yourpapayouknow&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">XHSContentFetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">git&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">clone&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">//&lt;/span>&lt;span class="n">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">yourpapayouknow&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">XHSContentFetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">git&lt;/span> &lt;span class="n">_ref_repo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Force&lt;/span> &lt;span class="n">_ref_repo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Recurse&lt;/span> &lt;span class="o">-File&lt;/span> &lt;span class="n">_ref_repo&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>发现：&lt;/p>
&lt;ol>
&lt;li>仓库只有 &lt;code>LICENSE&lt;/code>，无可复用实现。&lt;/li>
&lt;/ol>
&lt;p>决策：&lt;/p>
&lt;ol>
&lt;li>我不再依赖这个仓库，直接独立实现。&lt;/li>
&lt;/ol>
&lt;h3 id="14-动作四抓取库选型">1.4 动作四：抓取库选型
&lt;/h3>&lt;p>执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">index&lt;/span> &lt;span class="n">versions&lt;/span> &lt;span class="nb">xiaohongshu-cli&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">download&lt;/span> &lt;span class="nb">xiaohongshu-cli&lt;/span>&lt;span class="p">==&lt;/span>&lt;span class="mf">0.6&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">4&lt;/span> &lt;span class="n">-d&lt;/span> &lt;span class="n">_vendor&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我后续做了 wheel 解包和源码检查，重点看：&lt;/p>
&lt;ul>
&lt;li>&lt;code>xhs_cli/client.py&lt;/code>&lt;/li>
&lt;li>&lt;code>xhs_cli/client_mixins.py&lt;/code>&lt;/li>
&lt;li>&lt;code>xhs_cli/html_parser.py&lt;/code>&lt;/li>
&lt;li>&lt;code>xhs_cli/formatter.py&lt;/code>&lt;/li>
&lt;li>&lt;code>xhs_cli/cookies.py&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>我确认到的能力：&lt;/p>
&lt;ol>
&lt;li>&lt;code>get_note_detail&lt;/code>&lt;/li>
&lt;li>&lt;code>get_comments / get_sub_comments&lt;/code>&lt;/li>
&lt;li>&lt;code>get_note_from_html&lt;/code>，用于 HTML 回退&lt;/li>
&lt;li>现成签名流程，我不用自己逆向签名&lt;/li>
&lt;/ol>
&lt;h3 id="15-动作五用真实-cookie--真实链接做最小验证">1.5 动作五：用真实 Cookie + 真实链接做最小验证
&lt;/h3>&lt;p>我写了临时脚本，直接调 &lt;code>XhsClient&lt;/code>，分别验证图文和视频。&lt;/p>
&lt;p>关键验证结果：&lt;/p>
&lt;ol>
&lt;li>图文 &lt;code>note_card&lt;/code> 里有 &lt;code>image_list&lt;/code>、&lt;code>desc&lt;/code>、&lt;code>interact_info&lt;/code>。&lt;/li>
&lt;li>视频 &lt;code>note_card.video.media.stream.h264&lt;/code> 能取到可下载流地址。&lt;/li>
&lt;li>评论接口能正常返回。&lt;/li>
&lt;/ol>
&lt;p>到这里我才进入工程实现阶段。&lt;/p>
&lt;hr>
&lt;h2 id="2-工程搭建阶段从-0-到可执行-cli">2. 工程搭建阶段：从 0 到可执行 CLI
&lt;/h2>&lt;h3 id="21-基础文件落地">2.1 基础文件落地
&lt;/h3>&lt;p>我先建了最小工程骨架：&lt;/p>
&lt;ol>
&lt;li>&lt;code>pyproject.toml&lt;/code>&lt;/li>
&lt;li>&lt;code>.gitignore&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/__init__.py&lt;/code>&lt;/li>
&lt;li>&lt;code>README.md&lt;/code>，初版&lt;/li>
&lt;/ol>
&lt;h3 id="22-模块化拆分第一版">2.2 模块化拆分，第一版
&lt;/h3>&lt;p>我按职责拆成 12 个模块：&lt;/p>
&lt;ol>
&lt;li>&lt;code>config.py&lt;/code>：配置持久化 + Cookie 解析&lt;/li>
&lt;li>&lt;code>utils.py&lt;/code>：通用工具函数&lt;/li>
&lt;li>&lt;code>url_parser.py&lt;/code>：链接解析，包含重定向处理&lt;/li>
&lt;li>&lt;code>fetcher.py&lt;/code>：详情 / 评论抓取 + 评论排序&lt;/li>
&lt;li>&lt;code>media.py&lt;/code>：图片和视频下载&lt;/li>
&lt;li>&lt;code>video_tools.py&lt;/code>：ffmpeg 调用、降采样、抽帧&lt;/li>
&lt;li>&lt;code>ocr_engine.py&lt;/code>：PaddleOCR 封装&lt;/li>
&lt;li>&lt;code>deepseek.py&lt;/code>：模型输入构造与调用&lt;/li>
&lt;li>&lt;code>markdown_writer.py&lt;/code>：Markdown 产物写入&lt;/li>
&lt;li>&lt;code>obsidian.py&lt;/code>：打开 Obsidian&lt;/li>
&lt;li>&lt;code>workflow.py&lt;/code>：端到端编排&lt;/li>
&lt;li>&lt;code>cli.py&lt;/code>：命令入口&lt;/li>
&lt;/ol>
&lt;h3 id="23-初始测试">2.3 初始测试
&lt;/h3>&lt;p>我建了 3 个单测：&lt;/p>
&lt;ol>
&lt;li>URL 解析测试&lt;/li>
&lt;li>Cookie 解析测试&lt;/li>
&lt;li>评论排序测试&lt;/li>
&lt;/ol>
&lt;p>执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pytest&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">6 passed
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="3-首轮端到端联调先跑通图文--视频主链路">3. 首轮端到端联调：先跑通图文 / 视频主链路
&lt;/h2>&lt;p>我先把变量收敛，避免 LLM 和 OCR 抢占排错注意力。&lt;/p>
&lt;p>执行示意：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">xhs_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;图文链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-cookie&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;脱敏&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-llm&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-ocr&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-output-root&lt;/span> &lt;span class="n">output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">xhs_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;视频链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-cookie&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;脱敏&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-llm&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-ocr&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-output-root&lt;/span> &lt;span class="n">output&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="31-第一个关键故障api-error-code--1">3.1 第一个关键故障：&lt;code>API error: {&amp;quot;code&amp;quot;: -1}&lt;/code>
&lt;/h3>&lt;p>图文链路首次运行报错：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[ERROR] API error: {&amp;#34;code&amp;#34;: -1, &amp;#34;success&amp;#34;: false}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="32-我的定位路径">3.2 我的定位路径
&lt;/h3>&lt;p>我没有直接改代码，而是先拆开验证：&lt;/p>
&lt;ol>
&lt;li>&lt;code>get_note_detail&lt;/code> 单独调，成功。&lt;/li>
&lt;li>&lt;code>get_comments&lt;/code> 单独调，成功。&lt;/li>
&lt;li>失败集中在“抓子评论”阶段，部分 &lt;code>get_sub_comments&lt;/code> 偶发异常。&lt;/li>
&lt;/ol>
&lt;h3 id="33-修复动作">3.3 修复动作
&lt;/h3>&lt;p>我在 &lt;code>fetcher.py&lt;/code> 里改 &lt;code>_fetch_sub_comments&lt;/code>：&lt;/p>
&lt;ol>
&lt;li>改成单条容错。&lt;/li>
&lt;li>单条失败就跳过。&lt;/li>
&lt;li>不让局部失败中断主流程。&lt;/li>
&lt;/ol>
&lt;p>修复后复测：&lt;/p>
&lt;ul>
&lt;li>图文链路跑通。&lt;/li>
&lt;li>视频链路跑通。&lt;/li>
&lt;/ul>
&lt;p>关键产物验证：&lt;/p>
&lt;ol>
&lt;li>视频下载成功，文件是 &lt;code>video_raw.mp4&lt;/code>。&lt;/li>
&lt;li>关键帧抽取成功，默认抽取 8 帧。&lt;/li>
&lt;li>评论抓取成功。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-媒体与-ocr-深度修复这部分最耗时">4. 媒体与 OCR 深度修复：这部分最耗时
&lt;/h2>&lt;h3 id="41-图片输出成-bin">4.1 图片输出成 &lt;code>.bin&lt;/code>
&lt;/h3>&lt;p>现象：&lt;/p>
&lt;ol>
&lt;li>图片文件变成 &lt;code>image_01.bin&lt;/code>。&lt;/li>
&lt;li>Obsidian 预览和 OCR 都受到影响。&lt;/li>
&lt;/ol>
&lt;p>根因：&lt;/p>
&lt;ol>
&lt;li>部分 CDN 响应缺失可靠 &lt;code>content-type&lt;/code>。&lt;/li>
&lt;/ol>
&lt;p>修复：&lt;/p>
&lt;p>我在 &lt;code>media.py&lt;/code> 加了魔数检测和扩展名回退顺序：&lt;/p>
&lt;ol>
&lt;li>先看内容头，覆盖 JPEG / PNG / GIF / BMP / WEBP / AVIF&lt;/li>
&lt;li>再看 &lt;code>Content-Type&lt;/code>&lt;/li>
&lt;li>再看 URL 后缀&lt;/li>
&lt;li>最后才用 &lt;code>.bin&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>结果：&lt;/p>
&lt;ol>
&lt;li>图片稳定输出为 &lt;code>.webp/.jpg/...&lt;/code>。&lt;/li>
&lt;/ol>
&lt;h3 id="42-ocr-初始化失败缺底层依赖">4.2 OCR 初始化失败：缺底层依赖
&lt;/h3>&lt;p>现象：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">dependency &amp;#39;paddlepaddle&amp;#39; is not installed
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>处理：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">paddleocr&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">paddlepaddle&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="43-paddleocr-参数兼容问题">4.3 PaddleOCR 参数兼容问题
&lt;/h3>&lt;p>我遇到两个典型兼容问题：&lt;/p>
&lt;ol>
&lt;li>某些版本不接受 &lt;code>show_log&lt;/code>。&lt;/li>
&lt;li>新版在 OCR 参数里带 &lt;code>cls=True&lt;/code> 时会抛 &lt;code>unexpected keyword&lt;/code>。&lt;/li>
&lt;/ol>
&lt;p>修复策略：&lt;/p>
&lt;ol>
&lt;li>初始化参数做多套回退。&lt;/li>
&lt;li>OCR 调用失败后回退到不带 &lt;code>cls&lt;/code> 的方式。&lt;/li>
&lt;/ol>
&lt;h3 id="44-onednn--mkldnn-兼容报错">4.4 oneDNN / MKLDNN 兼容报错
&lt;/h3>&lt;p>现象：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ConvertPirAttribute2RuntimeAttribute not support ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>修复：&lt;/p>
&lt;ol>
&lt;li>在 &lt;code>ocr_engine.py&lt;/code> 设置兼容环境变量，禁用问题路径。&lt;/li>
&lt;/ol>
&lt;h3 id="45-中文路径导致-invalid-argument">4.5 中文路径导致 &lt;code>Invalid argument&lt;/code>
&lt;/h3>&lt;p>现象：&lt;/p>
&lt;ol>
&lt;li>Paddle 在部分中文路径下读取文件失败。&lt;/li>
&lt;/ol>
&lt;p>修复：&lt;/p>
&lt;ol>
&lt;li>OCR 前先复制到临时 ASCII 路径。&lt;/li>
&lt;li>识别完成后映射回原文件名。&lt;/li>
&lt;/ol>
&lt;h3 id="46-ocr-文本污染">4.6 OCR 文本污染
&lt;/h3>&lt;p>现象：&lt;/p>
&lt;ol>
&lt;li>OCR 结果混入临时路径字符串。&lt;/li>
&lt;/ol>
&lt;p>修复：&lt;/p>
&lt;ol>
&lt;li>过滤 &lt;code>xhs_ocr_&lt;/code>、盘符路径、tmp 路径行。&lt;/li>
&lt;/ol>
&lt;p>结果：&lt;/p>
&lt;ol>
&lt;li>OCR 文本可直接进入笔记。&lt;/li>
&lt;/ol>
&lt;h3 id="47-ocr-超时不是功能失败">4.7 OCR 超时不是功能失败
&lt;/h3>&lt;p>我实际还遇到过一次“超时中断”。&lt;/p>
&lt;p>这次不是逻辑错误，而是首次全量 OCR 跑 8 张图时加载和推理时间偏长。&lt;/p>
&lt;p>我采取的动作：&lt;/p>
&lt;ol>
&lt;li>放宽命令超时窗口。&lt;/li>
&lt;li>完整跑到底，再判断是否存在真实异常。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="5-提示词迭代从复述改到可读笔记">5. 提示词迭代：从“复述”改到“可读笔记”
&lt;/h2>&lt;p>链路跑通后，我回看产物，发现三个硬问题：&lt;/p>
&lt;ol>
&lt;li>文本复述多，知识点拆解不够。&lt;/li>
&lt;li>图片堆在文末，不是图文并茂。&lt;/li>
&lt;li>口吻不像我的笔记。&lt;/li>
&lt;/ol>
&lt;h3 id="51-第一次改造强化提示词约束">5.1 第一次改造：强化提示词约束
&lt;/h3>&lt;p>我在 &lt;code>deepseek.py&lt;/code> 做了三层调整：&lt;/p>
&lt;ol>
&lt;li>固定第一人称口吻。&lt;/li>
&lt;li>解释优先，要求按“是什么 / 为什么 / 怎么用 / 边界误区”拆解。&lt;/li>
&lt;li>禁止教学腔、禁止空泛总结。&lt;/li>
&lt;/ol>
&lt;p>同时在 &lt;code>workflow.py&lt;/code> 把素材相对路径直接喂模型：&lt;/p>
&lt;ul>
&lt;li>&lt;code>assets/images/...&lt;/code>&lt;/li>
&lt;li>&lt;code>assets/frames/...&lt;/code>&lt;/li>
&lt;li>&lt;code>assets/video/...&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>目的很明确：模型必须内联引用，不允许文末堆图。&lt;/p>
&lt;h3 id="52-第二次改造模板优先禁用硬编码骨架">5.2 第二次改造：模板优先，禁用硬编码骨架
&lt;/h3>&lt;p>我后续把策略改成“模板优先”：&lt;/p>
&lt;ol>
&lt;li>优先读取根目录 &lt;code>Tempate.md&lt;/code>。&lt;/li>
&lt;li>兼容 &lt;code>Template.md&lt;/code> / &lt;code>template.md&lt;/code>。&lt;/li>
&lt;li>找不到模板就报错，不再自动生成章节骨架。&lt;/li>
&lt;li>占位符替换真实路径：
&lt;ul>
&lt;li>&lt;code>assets.images[*].path&lt;/code>&lt;/li>
&lt;li>&lt;code>assets.video_frames[*].path&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>这一步的意义是：我把结构控制权从模型拿回到模板。&lt;/p>
&lt;hr>
&lt;h2 id="6-url-解析事故discoveryitemid">6. URL 解析事故：&lt;code>discovery/item/&amp;lt;id&amp;gt;&lt;/code>
&lt;/h2>&lt;h3 id="61-故障现象">6.1 故障现象
&lt;/h3>&lt;p>新链接进入后，全链路失败：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[ERROR] 帖子详情为空，无法提取 note_card。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-我的定位过程">6.2 我的定位过程
&lt;/h3>&lt;p>我先用独立脚本验证，确认：&lt;/p>
&lt;ol>
&lt;li>&lt;code>XhsClient.get_note_detail&lt;/code> 能拿到 &lt;code>items&lt;/code>。&lt;/li>
&lt;li>我自己的 &lt;code>url_parser&lt;/code> 把 &lt;code>note_id&lt;/code> 解析错了，取成了 &lt;code>item&lt;/code>。&lt;/li>
&lt;/ol>
&lt;h3 id="63-修复方案">6.3 修复方案
&lt;/h3>&lt;p>我在 &lt;code>url_parser.py&lt;/code> 做三点修复：&lt;/p>
&lt;ol>
&lt;li>优先匹配 24 位 ID-like 片段。&lt;/li>
&lt;li>特判 &lt;code>discovery/item/&amp;lt;id&amp;gt;&lt;/code>。&lt;/li>
&lt;li>保留 &lt;code>explore/&amp;lt;id&amp;gt;&lt;/code> 和 &lt;code>item/&amp;lt;id&amp;gt;&lt;/code> 兼容逻辑。&lt;/li>
&lt;/ol>
&lt;p>复测结果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">7 passed
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="7-obsidian-对接联调命令可用但编码有坑">7. Obsidian 对接联调：命令可用，但编码有坑
&lt;/h2>&lt;h3 id="71-cli-可用性验证">7.1 CLI 可用性验证
&lt;/h3>&lt;p>执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">obsidian&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">obsidian&lt;/span> &lt;span class="n">vaults&lt;/span> &lt;span class="n">verbose&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我确认：&lt;/p>
&lt;ol>
&lt;li>Obsidian CLI 可用。&lt;/li>
&lt;li>Vault 路径能识别到，例如 &lt;code>C:\Users\yourp\Documents\Obsidian Vault&lt;/code>。&lt;/li>
&lt;/ol>
&lt;h3 id="72-真机写入--自动打开">7.2 真机写入 + 自动打开
&lt;/h3>&lt;p>执行示意：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">xhs-fetch&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-vault-dir&lt;/span> &lt;span class="s2">&amp;#34;C:\Users\...\Obsidian Vault&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-open-obsidian&lt;/span> &lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果：&lt;/p>
&lt;ol>
&lt;li>笔记写入成功。&lt;/li>
&lt;li>自动打开成功。&lt;/li>
&lt;/ol>
&lt;h3 id="73-unicodedecodeerror-修复">7.3 &lt;code>UnicodeDecodeError&lt;/code> 修复
&lt;/h3>&lt;p>现象：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">UnicodeDecodeError: &amp;#39;gbk&amp;#39; codec can&amp;#39;t decode ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>根因：&lt;/p>
&lt;ol>
&lt;li>&lt;code>subprocess&lt;/code> 捕获 Obsidian 输出时发生编码读流问题。&lt;/li>
&lt;/ol>
&lt;p>修复：&lt;/p>
&lt;ol>
&lt;li>&lt;code>obsidian.py&lt;/code> 把 &lt;code>stdout/stderr&lt;/code> 改成 &lt;code>DEVNULL&lt;/code>。&lt;/li>
&lt;/ol>
&lt;p>复测后恢复正常。&lt;/p>
&lt;hr>
&lt;h2 id="8-我做过的回归验证清单">8. 我做过的回归验证清单
&lt;/h2>&lt;p>我不是“跑通一次就结束”，而是按层回归：&lt;/p>
&lt;ol>
&lt;li>单测回归：&lt;code>venv\Scripts\python -m pytest&lt;/code>&lt;/li>
&lt;li>图文链路回归：&lt;code>--skip-llm --skip-ocr&lt;/code>&lt;/li>
&lt;li>视频链路回归：&lt;code>--skip-llm --skip-ocr&lt;/code>&lt;/li>
&lt;li>OCR 层验证：单图 + 全量图&lt;/li>
&lt;li>DeepSeek 全链路验证：含模型调用产出&lt;/li>
&lt;li>产物检查：笔记、图片、关键帧、视频、路径引用&lt;/li>
&lt;/ol>
&lt;p>期间我还做过环境检查：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-Command&lt;/span> &lt;span class="n">ffprobe&lt;/span> &lt;span class="n">-ErrorAction&lt;/span> &lt;span class="n">SilentlyContinue&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Select-Object&lt;/span> &lt;span class="n">-ExpandProperty&lt;/span> &lt;span class="n">Source&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-Command&lt;/span> &lt;span class="n">ffmpeg&lt;/span> &lt;span class="n">-ErrorAction&lt;/span> &lt;span class="n">SilentlyContinue&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Select-Object&lt;/span> &lt;span class="n">-ExpandProperty&lt;/span> &lt;span class="n">Source&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样可以先排除本机 ffmpeg 工具链问题。&lt;/p>
&lt;hr>
&lt;h2 id="9-文档清理与发布">9. 文档、清理与发布
&lt;/h2>&lt;h3 id="91-清理动作">9.1 清理动作
&lt;/h3>&lt;p>我按交付要求清理：&lt;/p>
&lt;ol>
&lt;li>&lt;code>output/&lt;/code>&lt;/li>
&lt;li>&lt;code>tests/&lt;/code>&lt;/li>
&lt;li>&lt;code>.pytest_cache/&lt;/code>&lt;/li>
&lt;li>全部 &lt;code>__pycache__/&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>保留：&lt;/p>
&lt;ol>
&lt;li>&lt;code>Tempate.md&lt;/code>，后续生成结构依赖它&lt;/li>
&lt;/ol>
&lt;h3 id="92-readme-重写范围">9.2 README 重写范围
&lt;/h3>&lt;p>我把 README 改成简体中文，覆盖：&lt;/p>
&lt;ol>
&lt;li>功能全览&lt;/li>
&lt;li>安装与依赖&lt;/li>
&lt;li>配置方式&lt;/li>
&lt;li>模板机制&lt;/li>
&lt;li>Obsidian 对接&lt;/li>
&lt;li>常见问题排错&lt;/li>
&lt;/ol>
&lt;h3 id="93-git-提交和推送">9.3 Git 提交和推送
&lt;/h3>&lt;p>我执行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">add&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="py">gitignore&lt;/span> &lt;span class="n">README&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">md &lt;/span>&lt;span class="n">Tempate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">md &lt;/span>&lt;span class="n">pyproject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">toml&lt;/span> &lt;span class="n">src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">commit&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="s2">&amp;#34;feat: implement end-to-end xhs fetcher with obsidian integration&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">push&lt;/span> &lt;span class="n">origin&lt;/span> &lt;span class="n">main&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果：&lt;/p>
&lt;ol>
&lt;li>提交：&lt;code>13f8545&lt;/code>&lt;/li>
&lt;li>分支：&lt;code>main&lt;/code>&lt;/li>
&lt;li>远程推送成功&lt;/li>
&lt;/ol>
&lt;p>安全处理：&lt;/p>
&lt;ol>
&lt;li>含敏感信息的 &lt;code>计划.md&lt;/code> 保持未跟踪，不进仓库。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="10-本次产出的最终代码结构">10. 本次产出的最终代码结构
&lt;/h2>&lt;p>核心目录：&lt;/p>
&lt;ol>
&lt;li>&lt;code>src/xhs_content_fetcher/cli.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/workflow.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/fetcher.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/media.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/video_tools.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/ocr_engine.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/deepseek.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/markdown_writer.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/obsidian.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/url_parser.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/config.py&lt;/code>&lt;/li>
&lt;li>&lt;code>src/xhs_content_fetcher/utils.py&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>工程文件：&lt;/p>
&lt;ol>
&lt;li>&lt;code>pyproject.toml&lt;/code>&lt;/li>
&lt;li>&lt;code>.gitignore&lt;/code>&lt;/li>
&lt;li>&lt;code>README.md&lt;/code>&lt;/li>
&lt;li>&lt;code>Tempate.md&lt;/code>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="11-关键报错与修复对照">11. 关键报错与修复对照
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>阶段&lt;/th>
&lt;th>报错 / 问题&lt;/th>
&lt;th>根因&lt;/th>
&lt;th>修复&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>抓取&lt;/td>
&lt;td>&lt;code>API error: {&amp;quot;code&amp;quot;: -1}&lt;/code>&lt;/td>
&lt;td>子评论接口偶发失败&lt;/td>
&lt;td>子评论单条容错，不阻断主流程&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>媒体&lt;/td>
&lt;td>图片输出 &lt;code>.bin&lt;/code>&lt;/td>
&lt;td>CDN 响应类型不稳定&lt;/td>
&lt;td>魔数识别 + 多级扩展名回退&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OCR&lt;/td>
&lt;td>&lt;code>paddlepaddle not installed&lt;/code>&lt;/td>
&lt;td>只装了 &lt;code>paddleocr&lt;/code>&lt;/td>
&lt;td>补装 &lt;code>paddlepaddle&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OCR&lt;/td>
&lt;td>参数不兼容&lt;/td>
&lt;td>PaddleOCR 版本差异&lt;/td>
&lt;td>初始化 / 调用参数回退&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OCR&lt;/td>
&lt;td>oneDNN 报错&lt;/td>
&lt;td>后端兼容问题&lt;/td>
&lt;td>兼容环境变量兜底&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OCR&lt;/td>
&lt;td>&lt;code>Invalid argument&lt;/code>&lt;/td>
&lt;td>中文路径读取问题&lt;/td>
&lt;td>临时 ASCII 路径识别&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>解析&lt;/td>
&lt;td>&lt;code>note_id&lt;/code> 被解析为 &lt;code>item&lt;/code>&lt;/td>
&lt;td>URL 规则覆盖不足&lt;/td>
&lt;td>修复 &lt;code>discovery/item/&amp;lt;id&amp;gt;&lt;/code> 解析&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Obsidian&lt;/td>
&lt;td>&lt;code>UnicodeDecodeError&lt;/code>&lt;/td>
&lt;td>子进程输出编码冲突&lt;/td>
&lt;td>&lt;code>stdout/stderr -&amp;gt; DEVNULL&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="12-命令流水节选">12. 命令流水节选
&lt;/h2>&lt;p>下面这组命令是我这次会话里反复执行的关键路径，按用途分组：&lt;/p>
&lt;h3 id="121-目录与文件检查">12.1 目录与文件检查
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Recurse&lt;/span> &lt;span class="o">-File&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-Content&lt;/span> &lt;span class="n">-Raw&lt;/span> &lt;span class="n">-Encoding&lt;/span> &lt;span class="n">UTF8&lt;/span> &lt;span class="n">计划&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">md
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="122-抓取依赖验证">12.2 抓取依赖验证
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="nb">ls-remote&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">//&lt;/span>&lt;span class="n">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">yourpapayouknow&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">XHSContentFetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">git&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">clone&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">//&lt;/span>&lt;span class="n">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">yourpapayouknow&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">XHSContentFetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">git&lt;/span> &lt;span class="n">_ref_repo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">index&lt;/span> &lt;span class="n">versions&lt;/span> &lt;span class="nb">xiaohongshu-cli&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">download&lt;/span> &lt;span class="nb">xiaohongshu-cli&lt;/span>&lt;span class="p">==&lt;/span>&lt;span class="mf">0.6&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">4&lt;/span> &lt;span class="n">-d&lt;/span> &lt;span class="n">_vendor&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="123-本地构建与测试">12.3 本地构建与测试
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">-e&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="n">pytest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">xhs_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="n">-help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pytest&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="124-图文--视频端到端">12.4 图文 / 视频端到端
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">xhs_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;图文链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-cookie&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;脱敏&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-llm&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-ocr&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-output-root&lt;/span> &lt;span class="n">output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">xhs_content_fetcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cli &lt;/span>&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;视频链接&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-cookie&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;脱敏&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-llm&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-skip-ocr&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-output-root&lt;/span> &lt;span class="n">output&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="125-ocr-相关">12.5 OCR 相关
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">paddleocr&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">paddlepaddle&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="126-obsidian-验证">12.6 Obsidian 验证
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">obsidian&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">obsidian&lt;/span> &lt;span class="n">vaults&lt;/span> &lt;span class="n">verbose&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="127-发布">12.7 发布
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">add&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="py">gitignore&lt;/span> &lt;span class="n">README&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">md &lt;/span>&lt;span class="n">Tempate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">md &lt;/span>&lt;span class="n">pyproject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">toml&lt;/span> &lt;span class="n">src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">commit&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="s2">&amp;#34;feat: implement end-to-end xhs fetcher with obsidian integration&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">push&lt;/span> &lt;span class="n">origin&lt;/span> &lt;span class="n">main&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="13-我这次最值的三个决策">13. 我这次最值的三个决策
&lt;/h2>&lt;ol>
&lt;li>先做可用性验证，再做架构设计。&lt;/li>
&lt;li>把长链路做成“局部失败不拖垮全流程”。&lt;/li>
&lt;li>用模板约束生成结构，把质量控制权留在我手里。&lt;/li>
&lt;/ol>
&lt;h2 id="14-下一步优化计划">14. 下一步优化计划
&lt;/h2>&lt;ol>
&lt;li>断点续跑，减少长任务重跑成本。&lt;/li>
&lt;li>OCR / LLM 缓存，按 &lt;code>note_id + 素材 hash&lt;/code>。&lt;/li>
&lt;li>结构化日志，覆盖阶段耗时和错误分类。&lt;/li>
&lt;li>模板变量校验，缺失章节自动报错。&lt;/li>
&lt;li>“仅更新正文 / 仅更新评论”子命令。&lt;/li>
&lt;li>多模型路由与失败回退策略。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="15-最终复盘一句话">15. 最终复盘一句话
&lt;/h2>&lt;p>这次我真正做成的，不是一个“能跑演示”的脚本，而是一条可定位、可修复、可复测、可发布、可复用的工程链路。&lt;/p></description></item></channel></rss>