<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>OCR on iwmei的博客</title><link>https://iwmei.top/tags/ocr/</link><description>Recent content in OCR on iwmei的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>iwmei</copyright><lastBuildDate>Tue, 05 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://iwmei.top/tags/ocr/index.xml" rel="self" type="application/rss+xml"/><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>