<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://www.wenhao.ink/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.wenhao.ink/" rel="alternate" type="text/html" /><updated>2026-02-25T05:15:01+00:00</updated><id>https://www.wenhao.ink/feed.xml</id><title type="html">wenhao</title><subtitle>A (nearly) no-CSS, fast, minimalist Jekyll theme.
</subtitle><author><name>wenhao</name></author><entry><title type="html">用大模型翻译 EPUB：从占位符到最小干预</title><link href="https://www.wenhao.ink/epub-llm-translation-20260225/" rel="alternate" type="text/html" title="用大模型翻译 EPUB：从占位符到最小干预" /><published>2026-02-25T00:00:00+00:00</published><updated>2026-02-25T00:00:00+00:00</updated><id>https://www.wenhao.ink/epub-llm-translation-inline-tags</id><content type="html" xml:base="https://www.wenhao.ink/epub-llm-translation-20260225/">&lt;p&gt;在开发 orange-translator 的过程中，我为一个问题折腾了好几个版本：&lt;strong&gt;如何处理 EPUB 里的 HTML 内联标签&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;orange-translator 是一个将英文 EPUB 电子书翻译为双语版本的工具，译文紧跟原文段落之后，形成”原文 + 译文”交替排布的阅读体验。表面上看，翻译流程并不复杂：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;EPUB 解包 → HTML 解析 → 文本提取 → LLM 批量翻译 → 双语重组 → EPUB 重新打包
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;真正让我踩坑的，是第三步和第四步之间的那道墙。&lt;/p&gt;

&lt;h2 id=&quot;核心挑战内联标签怎么办&quot;&gt;核心挑战：内联标签怎么办&lt;/h2&gt;

&lt;p&gt;EPUB 的 XHTML 里，一段文字往往不是纯文本，而是夹杂着各种内联标签：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
  In &lt;span class=&quot;nt&quot;&gt;&amp;lt;em&amp;gt;&lt;/span&gt;The Dhammapada&lt;span class=&quot;nt&quot;&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;, verse 103 reads:
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#fn1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/sup&amp;gt;&lt;/span&gt;
  &quot;Conquer yourself&lt;span class=&quot;nt&quot;&gt;&amp;lt;br/&amp;gt;&lt;/span&gt;rather than the world.&quot;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;里面有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;em&amp;gt;&lt;/code&gt;（斜体）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;sup&amp;gt;&lt;/code&gt;（脚注序号）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a&amp;gt;&lt;/code&gt;（链接）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;br/&amp;gt;&lt;/code&gt;（换行）。&lt;/p&gt;

&lt;p&gt;如果把整个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inner_html&lt;/code&gt; 原样送给 LLM，有几个问题：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;HTML 标签大量占用 token，增加成本和延迟&lt;/li&gt;
  &lt;li&gt;LLM 不擅长精确复制任意 HTML 结构，容易错位或丢失&lt;/li&gt;
  &lt;li&gt;翻译后 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;em&amp;gt;&lt;/code&gt; 里的词可能位置变了，硬保留反而不自然&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;真正的问题&lt;/strong&gt;：哪些标签必须保留？哪些可以丢弃？哪些需要特殊处理？&lt;/p&gt;

&lt;h2 id=&quot;第一个方案占位符&quot;&gt;第一个方案：占位符&lt;/h2&gt;

&lt;p&gt;直觉上，这是个”显然”的解法：把内联标签替换为 LLM 可以透传的占位符，翻译完后再还原。&lt;/p&gt;

&lt;h3 id=&quot;数学括号-n&quot;&gt;数学括号 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦N⟧&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;第一版用 Unicode 数学白方括号 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦N⟧&lt;/code&gt;（U+27E6/U+27E7）作为占位符，透明标签用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦0⟧content⟦/0⟧&lt;/code&gt;，不透明标签用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦0⟧&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：翻译模型（translategemma:4b）把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦⟧&lt;/code&gt; 翻译成了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;《》&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;输出变成了这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;《0》《/0》托《1》奥《/1》曼尼《2》
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;根本原因：LLM 的训练数据中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦&lt;/code&gt; 极少见，模型会把它当作”奇怪的外语括号”进行翻译，而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;《》&lt;/code&gt; 是它见过的最相似的括号形式。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;教训&lt;/strong&gt;：占位符不能用模型训练数据中罕见的字符。&lt;/p&gt;

&lt;h3 id=&quot;xml-风格标签-gn&quot;&gt;XML 风格标签 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;gN&amp;gt;&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;改用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;g0&amp;gt;content&amp;lt;/g0&amp;gt;&lt;/code&gt; 表示透明标签，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;x0/&amp;gt;&lt;/code&gt; 表示不透明标签。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;g&amp;gt;&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;x&amp;gt;&lt;/code&gt; 在 HTML 中不存在，模型不会把它们当真实 HTML 处理——理论上。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：不透明的自闭合 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;x0/&amp;gt;&lt;/code&gt; 被模型扩展为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;x0&amp;gt;内容&amp;lt;/x0&amp;gt;&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;模型看到一个孤立的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;x0/&amp;gt;&lt;/code&gt; 没有内容，觉得不合理，于是自作主张给它配了内容。&lt;/p&gt;

&lt;h3 id=&quot;token-风格-otn&quot;&gt;Token 风格 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[OT:N]&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;将不透明标签改为更像”标记/代码”而非”HTML 标签”的格式 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[OT:N]&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;《》&lt;/code&gt; 问题消失了，但新问题出现了：&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[OT:N]&lt;/code&gt; 被模型概率性丢弃&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;有时候输出完整，有时候 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[OT:0]&lt;/code&gt; 凭空消失。丢弃率和 batch 大小、上下文长度、模型状态都有关，不可预测。&lt;/p&gt;

&lt;h2 id=&quot;占位符方案的根本矛盾&quot;&gt;占位符方案的根本矛盾&lt;/h2&gt;

&lt;p&gt;到这里，我意识到占位符方案存在&lt;strong&gt;根本性矛盾&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;LLM 的翻译本质是：给定源语言文本，生成目标语言的自然表达。它的整个训练目标是生成流畅自然的人类语言。&lt;/p&gt;

&lt;p&gt;而占位符要求模型做相反的事：在自然语言输出中，精确地、不遗漏地复制一些非自然语言符号。这与模型的优化目标是冲突的。&lt;/p&gt;

&lt;p&gt;小模型尤其无法可靠地完成这个”规则遵从”任务，因为它没有足够的上下文理解能力来始终遵守指令。&lt;/p&gt;

&lt;h2 id=&quot;重新审视哪些标签真的需要保留&quot;&gt;重新审视：哪些标签真的需要保留&lt;/h2&gt;

&lt;p&gt;关键洞察来自对&lt;strong&gt;双语 EPUB 使用场景&lt;/strong&gt;的重新审视：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;在双语 EPUB 中，原文就在译文正上方。读者可以直接看到原文的完整格式——斜体、粗体、超链接都在。译文的作用是”帮助理解原文”，而不是”替代原文”。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这意味着：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;em&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;strong&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&lt;/code&gt; 等装饰性格式在译文中&lt;strong&gt;可以丢弃&lt;/strong&gt;，原文已经有了&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; 链接在译文中&lt;strong&gt;意义不大&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; 是&lt;strong&gt;结构性换行&lt;/strong&gt;，必须保留（诗歌、台词等场景）&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;sup&amp;gt;/&amp;lt;sub&amp;gt;&lt;/code&gt; 通常是&lt;strong&gt;脚注序号&lt;/strong&gt;，丢失了脚注引用就断了&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a id=&quot;N&quot;/&amp;gt;&lt;/code&gt; 空锚点是页码标记，&lt;strong&gt;完全不可见&lt;/strong&gt;，直接丢弃即可&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一句话：&lt;strong&gt;只有影响内容可读性的结构才需要保留，纯装饰性格式可以丢弃。&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;最小干预方案&quot;&gt;最小干预方案&lt;/h2&gt;

&lt;p&gt;基于这个认识，放弃占位符，改为”最小干预预处理”：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;preprocess_for_translation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;soup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeautifulSoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_html&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;html.parser&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;soup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 1. 规范化文本节点中的 \n 为空格，避免后续与 &amp;lt;br/&amp;gt; 转换的 \n 混淆
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text_node&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;text_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NavigableString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 2. &amp;lt;br/&amp;gt; → \n（LLM 能自然保留换行）
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;br&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;br&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;br&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NavigableString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 3. 空锚点直接剥离
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decompose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 4. 装饰性内联标签：保留文字内容，丢弃标签本身
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tag_name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_STRIP_INLINE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# em, strong, b, i, span, a, ...
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 5. sup/sub/img/wbr 保留原始 HTML 不动
&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decode_contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;br_html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还原时，把翻译结果中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt; 替换回原始的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; 字符串（保留 calibre 生成的 class 属性）。&lt;/p&gt;

&lt;h3 id=&quot;几个值得注意的细节&quot;&gt;几个值得注意的细节&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;为什么先规范化文本节点中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;XHTML 源文件里有时会有文本节点包含换行符（排版用途）。如果不先规范化，这些 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt; 会和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; 转换来的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt; 混淆，还原时会多出多余的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;br/&amp;gt;&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;sup&amp;gt;/&amp;lt;sub&amp;gt;&lt;/code&gt; 为什么不用占位符？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;实测发现，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;&lt;/code&gt; 这类短标签 LLM 能正确透传——它足够短，不像乱码，模型见过足够多的 HTML 上下文，知道要原样保留。长段落中的复杂嵌套才是问题。&lt;/p&gt;

&lt;h3 id=&quot;效果对比&quot;&gt;效果对比&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;方案&lt;/th&gt;
      &lt;th&gt;速度&lt;/th&gt;
      &lt;th&gt;缺陷&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦N⟧&lt;/code&gt; 占位符&lt;/td&gt;
      &lt;td&gt;基准&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⟦⟧&lt;/code&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;《》&lt;/code&gt; 转译&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;gN&amp;gt;&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;xN/&amp;gt;&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;-5%&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;xN/&amp;gt;&lt;/code&gt; 被扩展为含内容标签&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;gN&amp;gt;&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[OT:N]&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;-10%&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[OT:N]&lt;/code&gt; 概率性丢弃&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;最小干预（最终）&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;+22%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;无已知缺陷&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;速度提升的原因：送给 LLM 的文本更短，prompt token 减少，批次解析失败率降为零。&lt;/p&gt;

&lt;h2 id=&quot;其他踩坑记录&quot;&gt;其他踩坑记录&lt;/h2&gt;

&lt;h3 id=&quot;批量翻译的分段解析&quot;&gt;批量翻译的分段解析&lt;/h3&gt;

&lt;p&gt;批量翻译时用编号标记让 LLM 分段返回：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1]&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[2]&lt;/code&gt;……解析时用正则 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\[(\d+)\]&lt;/code&gt; 切分。&lt;/p&gt;

&lt;p&gt;模型偶尔会把多段合并，或者跳过某个编号。解决方案是&lt;strong&gt;递归对半重试&lt;/strong&gt;：10 段失败，拆成两个 5 段重试，直到单段为止。单段永远可以直接返回，无需解析。&lt;/p&gt;

&lt;h3 id=&quot;readtimeout-与流式-api&quot;&gt;ReadTimeout 与流式 API&lt;/h3&gt;

&lt;p&gt;用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpx&lt;/code&gt; 调 Ollama 时，非流式 API 需要等待完整响应。对于长段落，生成时间可能超过 300 秒，触发 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadTimeout&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;改用流式 API（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stream: true&lt;/code&gt;），用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aiter_lines()&lt;/code&gt; 逐行消费。流式模式下，timeout 针对相邻两个 chunk 之间的等待时间（设为 60 秒），而不是整个响应时间。&lt;/p&gt;

&lt;h3 id=&quot;续翻支持&quot;&gt;续翻支持&lt;/h3&gt;

&lt;p&gt;翻译 300 章的大部头时中途崩溃，已翻译的部分不能白费。&lt;/p&gt;

&lt;p&gt;实现：每章完成后写入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ot-cache/&amp;lt;md5&amp;gt;.xhtml&lt;/code&gt;，同时更新 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;progress.json&lt;/code&gt;。&lt;strong&gt;只有全部章节无错误完成时，才清理缓存&lt;/strong&gt;。有失败章节时，保留缓存，下次运行自动重翻失败的章节。&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;回头看这次折腾，走弯路的根本原因是：&lt;strong&gt;我在用错误的方式提问&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;一开始我问的是”如何让 LLM 精确透传 HTML 标签”，这是个错误的问题。LLM 的优化目标是生成流畅的自然语言，不是规则遵从。我想让它做的事，恰好和它的本质相违背。&lt;/p&gt;

&lt;p&gt;换一个问题：&lt;strong&gt;“哪些格式信息对读者真正重要？”&lt;/strong&gt; 一旦把问题问对了，答案就清晰了——在双语阅读场景下，原文就在旁边，大量格式信息根本不需要在译文中重复。&lt;/p&gt;

&lt;p&gt;让模型做它擅长的事，自己处理规则性的事。这条原则不只适用于 LLM 翻译，适用于所有工具的使用。&lt;/p&gt;

&lt;h2 id=&quot;引用&quot;&gt;引用&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ollama/ollama/blob/main/docs/api.md&quot;&gt;Ollama API 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Leo</name></author><category term="programme" /><category term="python" /><category term="epub" /><category term="llm" /><category term="翻译" /><summary type="html">在开发 orange-translator 的过程中，我为一个问题折腾了好几个版本：如何处理 EPUB 里的 HTML 内联标签。</summary></entry><entry><title type="html">拆解 Python 对象模型</title><link href="https://www.wenhao.ink/python-pyobject-metaclass-20251210/" rel="alternate" type="text/html" title="拆解 Python 对象模型" /><published>2025-12-10T00:00:00+00:00</published><updated>2025-12-10T00:00:00+00:00</updated><id>https://www.wenhao.ink/python-pyobject-metaclass</id><content type="html" xml:base="https://www.wenhao.ink/python-pyobject-metaclass-20251210/">&lt;p&gt;很多 Python 开发者写了很多年代码，但对 Python 的底层世界依然感觉雾里看花。&lt;/p&gt;

&lt;p&gt;你是否思考过这些问题：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;为什么常说“Python 中一切皆对象”，连函数和类也是对象？&lt;/li&gt;
  &lt;li&gt;为什么 Python 的变量不需要声明类型？&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 到底是什么关系？为什么 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type(object)&lt;/code&gt; 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;，而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 又是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 的父类？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果不理解这些，你只是在用 Python 写 C 代码；理解了这些，你才能真正掌握 Python 的“动态之力”。今天，我们就深入 CPython 的源码层面，拆解 Python 的对象模型。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;一-底层解剖pyobject-是万物之源&quot;&gt;一、 底层解剖：PyObject 是万物之源&lt;/h2&gt;

&lt;p&gt;Python 的灵活性源于一个核心设计：&lt;strong&gt;所有东西在底层都是同一个结构体。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;由于 CPython 是用 C 语言写的，当你创建一个整数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a = 10&lt;/code&gt;，或者定义一个函数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def func(): pass&lt;/code&gt;，在内存中它们并没有本质区别，它们都对应着 C 语言层面的一个结构体——&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyObject&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;每一个 Python 对象，在内存头部都至少包含两个核心字段：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ob_refcnt&lt;/code&gt; (引用计数)：&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;记录有多少个变量指向这个对象。当它变为 0 时，对象会被垃圾回收机制（GC）立即销毁。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ob_type&lt;/code&gt; (类型指针)：&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;这是一个指针，指向该对象所属的&lt;strong&gt;类对象&lt;/strong&gt;（Type Object）。&lt;/li&gt;
      &lt;li&gt;比如整数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ob_type&lt;/code&gt; 指向 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; 类。这个指针告诉解释器：“我是一个整数，我支持加减乘除”。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;结论：&lt;/strong&gt; 无论外表多复杂，Python 对象的内核都是一个挂着“引用计数”和“类型标签”的 C 结构体。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;二-核心隐喻变量是便利贴不是盒子&quot;&gt;二、 核心隐喻：变量是“便利贴”，不是“盒子”&lt;/h2&gt;

&lt;p&gt;理解对象模型的关键，在于纠正对“变量”的理解。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;在 C/Java 中：&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int a = 10;&lt;/code&gt; 就像申请了一个名字叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; 的&lt;strong&gt;盒子&lt;/strong&gt;，把数字 10 放进去。赋值 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b = a&lt;/code&gt; 是把 10 复制一份放到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; 盒子里。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;在 Python 中：&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a = 10&lt;/code&gt; 就像在内存里吹起了一个&lt;strong&gt;气球&lt;/strong&gt;（对象 10），然后拿一张写着 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; 的&lt;strong&gt;便利贴&lt;/strong&gt;（变量名）贴在气球上。
    &lt;ul&gt;
      &lt;li&gt;当你执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b = a&lt;/code&gt; 时，&lt;strong&gt;不是复制气球&lt;/strong&gt;，而是拿一张写着 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; 的便利贴，贴在&lt;strong&gt;同一个&lt;/strong&gt;气球上。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这就是为什么 Python 的参数传递全是&lt;strong&gt;引用传递（Pass by Assignment）&lt;/strong&gt;。这也解释了 Python 的“三位一体”特性，任何对象都有：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Identity（身份）：&lt;/strong&gt; 内存地址（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id(obj)&lt;/code&gt;）。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Type（类型）：&lt;/strong&gt; 它的模具是哪个类（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type(obj)&lt;/code&gt;）。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Value（值）：&lt;/strong&gt; 气球里的内容。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;三-终极烧脑type-和-object-的鸡蛋悖论&quot;&gt;三、 终极烧脑：type 和 object 的“鸡蛋悖论”&lt;/h2&gt;

&lt;p&gt;Python 对象模型中最令人困惑，也最精妙的设计，莫过于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 的关系。它们构成了对象系统的时空闭环。&lt;/p&gt;

&lt;h3 id=&quot;31-两个主角&quot;&gt;3.1 两个主角&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt;（万物之祖）：&lt;/strong&gt; 它是&lt;strong&gt;继承链&lt;/strong&gt;的终点。所有的类（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;str&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyClass&lt;/code&gt;）默认都继承自它。它定义了对象最基本的行为（如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__hash__&lt;/code&gt;）。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;（万物之主）：&lt;/strong&gt; 它是&lt;strong&gt;实例化链&lt;/strong&gt;的源头。也就是所谓的“元类”（Metaclass）。所有的类（包括 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt;）本质上都是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 创建出来的实例。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;32-只有两句话是真的&quot;&gt;3.2 只有两句话是真的&lt;/h3&gt;
&lt;p&gt;如果你被绕晕了，只需要记住这两句“绝对真理”：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 的子类。&lt;/strong&gt; （继承维度：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 也是个类，所以它得认 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 做父类）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 的实例。&lt;/strong&gt; （实例化维度：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 这个类对象，是由 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 制造出来的）&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;issubclass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# True
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# True
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# True (自己造自己)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;33-源码揭秘c-语言层面的神级操作&quot;&gt;3.3 源码揭秘：C 语言层面的神级操作&lt;/h3&gt;

&lt;p&gt;你可能会问：这逻辑不通啊？如果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 造了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt;，那在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 诞生之前 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 应该不存在；但 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 又继承自 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt;，说明 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 诞生前 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 必须存在。这不就是死锁了吗？&lt;/p&gt;

&lt;p&gt;在 C 语言实现的底层（CPython 源码），开发者通过&lt;strong&gt;精妙的指针操作&lt;/strong&gt;解决了这个“先有鸡还是先有蛋”的问题。这是一个人工打破死循环的过程：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;先定义结构体：&lt;/strong&gt;
C 语言代码中，先静态定义了两个核心结构体：
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyType_Type&lt;/code&gt;（对应 Python 里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;）&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyBaseObject_Type&lt;/code&gt;（对应 Python 里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt;）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;手动连接（Bootstrap）：&lt;/strong&gt;
此时它们还只是孤立的 C 结构体，编译器无法处理这种互相依赖。于是，CPython 在初始化时进行了“手动硬连线”：
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;让 type 成为自己的实例：&lt;/strong&gt; 把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyType_Type&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ob_type&lt;/code&gt; 指针指向它自己（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;PyType_Type&lt;/code&gt;）。&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;让 type 继承 object：&lt;/strong&gt; 把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyType_Type&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tp_base&lt;/code&gt; 指针指向 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyBaseObject_Type&lt;/code&gt;。&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;让 object 成为 type 的实例：&lt;/strong&gt; 把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyBaseObject_Type&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ob_type&lt;/code&gt; 指针指向 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyType_Type&lt;/code&gt;。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这种“我指你，你指我，我自己指我自己”的操作，在 C 语言层面完美闭合了逻辑环。&lt;/p&gt;

&lt;h3 id=&quot;34-为什么这么设计&quot;&gt;3.4 为什么这么设计？&lt;/h3&gt;

&lt;p&gt;这种看似复杂的环形设计，实际上是为了保证 &lt;strong&gt;Python 对象模型的一致性&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;没有特例：&lt;/strong&gt; 在 Python 中，一切皆对象。既然 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 也是对象，它们就必须遵守对象的规则（有类型、有父类）。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;逻辑闭环：&lt;/strong&gt; 通过让两者互为依托，Python 关闭了对象系统的顶层逻辑。这确保了无论你在系统中怎么回溯，永远不会遇到一个“不是对象”的东西。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;35-形象类比&quot;&gt;3.5 形象类比&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 就像是“塑料”这种材质。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 就像是“制造模具的机器”。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;源码层面的操作：&lt;/strong&gt; 工程师先用手捏了一个“最初的机器”（静态定义的结构体），然后用这台机器造出了所有后续的模具，最后甚至给这台机器贴上了“塑料制造”的标签。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;四-动态机制类亦是对象与属性查找&quot;&gt;四、 动态机制：类亦是对象与属性查找&lt;/h2&gt;

&lt;p&gt;基于上述模型，Python 衍生出了极具动态特性的行为。&lt;/p&gt;

&lt;h3 id=&quot;1-类也是对象first-class-citizen&quot;&gt;1. 类也是对象（First-class Citizen）&lt;/h3&gt;
&lt;p&gt;在 Python 中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class Dog:&lt;/code&gt; 这行代码执行完后，内存里真真切切地产生了一个名为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dog&lt;/code&gt; 的对象。
正因为类是对象，所以：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;你可以把类赋值给变量。&lt;/li&gt;
  &lt;li&gt;你可以把类当参数传给函数。&lt;/li&gt;
  &lt;li&gt;你可以在运行时动态修改类的属性（Monkey Patching）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-属性查找attribute-lookup&quot;&gt;2. 属性查找（Attribute Lookup）&lt;/h3&gt;
&lt;p&gt;当你敲下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj.x&lt;/code&gt; 时，Python 不会像 C++ 那样去偏移内存地址，而是启动了一次&lt;strong&gt;哈希查找&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;先去 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj.__dict__&lt;/code&gt;（实例字典）里找。&lt;/li&gt;
  &lt;li&gt;没找到？去 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj.__class__.__dict__&lt;/code&gt;（类字典）里找。&lt;/li&gt;
  &lt;li&gt;还没找到？顺着 MRO（方法解析顺序）去父类字典里找。&lt;/li&gt;
  &lt;li&gt;实在没有？调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__getattr__&lt;/code&gt; 给你最后一次机会。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这种机制虽然比指针偏移慢，但它带来了无与伦比的灵活性。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;五-总结&quot;&gt;五、 总结&lt;/h2&gt;

&lt;p&gt;Python 的对象模型是一种&lt;strong&gt;用空间（内存）和时间（速度）换取极致灵活性&lt;/strong&gt;的艺术。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;统一性：&lt;/strong&gt; 无论是整数、函数还是类，众生平等，皆为对象。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;元编程：&lt;/strong&gt; 通过控制 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;（元类），你可以控制类的创建过程，这是 Django ORM 等黑魔法的基石。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;自洽性：&lt;/strong&gt; 正是 C 语言底层那一次“精妙的指针连接”，让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 互为支撑，构建了一个逻辑完美自洽的动态世界。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当你下次写下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class MyClass&lt;/code&gt; 时，希望你能意识到：你不仅仅是在写代码，你是在指挥 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; 这位造物主，用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; 这种基底材质，为你创造一个新的世界。&lt;/p&gt;</content><author><name>Gemini</name></author><category term="programme" /><category term="python" /><category term="AIGC" /><summary type="html">很多 Python 开发者写了很多年代码，但对 Python 的底层世界依然感觉雾里看花。</summary></entry><entry><title type="html">Python导入与路径</title><link href="https://www.wenhao.ink/python-package-import-20250922/" rel="alternate" type="text/html" title="Python导入与路径" /><published>2025-09-22T00:00:00+00:00</published><updated>2025-09-22T00:00:00+00:00</updated><id>https://www.wenhao.ink/python-package-import</id><content type="html" xml:base="https://www.wenhao.ink/python-package-import-20250922/">&lt;p&gt;在很长一段时间，我对于Python的导入系统以及目录操作不是很清楚，很多时候会弄错，也会觉得它很复杂。&lt;/p&gt;

&lt;p&gt;这个问题还是在于对其中的某些概念不是很熟悉。下面会分两个部分进行说明。&lt;/p&gt;

&lt;h3 id=&quot;py文件的不同&quot;&gt;Py文件的不同&lt;/h3&gt;
&lt;p&gt;在一个Python项目中，不同的py文件，他们是不同的，我觉得这个概念对于理解Python代码很重要，Java中就没有区别。这个就是Python中的入口文件和模块文件。&lt;/p&gt;

&lt;p&gt;主要的区别是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__name__&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__package__&lt;/code&gt;魔术变量的值不同。当py作为入口文件时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__name__&lt;/code&gt;的值为__main__，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__package__&lt;/code&gt;为None。而不是模块文件时，他们都是各自应该有的值。&lt;/p&gt;

&lt;p&gt;导致这个问题的原因还是在于脚本语言，当他以文本的方式存在，而不像Java最终会形成jar包，而它所有的文件路径和管理都是在jar包内部。代码以文本的形式保存，代码用目录来组织，他们就需要解决哪里是项目的根目录的问题。&lt;/p&gt;

&lt;p&gt;有两个目录是可以确定的：入口文件所在目录和Python程序所在的目录。这也是Python的策略。其他，编程语言也有其他策略，比如Node还可以通过配置文件来实现。&lt;/p&gt;

&lt;p&gt;要么显示要么隐式的指定。&lt;/p&gt;

&lt;p&gt;理解Python入口文件不能使用相对导入的方式模块也很重要。&lt;/p&gt;

&lt;h3 id=&quot;包导入&quot;&gt;包导入&lt;/h3&gt;
&lt;p&gt;包导入本质上来说是在解决代码复用的问题。如果，没有包导入的功能，我们所有东西都要重新写，那是非常可怕的。而包导入就是在解决这个问题。&lt;/p&gt;

&lt;p&gt;所谓的包导入也就是系统默认从几个不同的路径来寻找模块，如果找到就其导入，没找到就报错。默认的模块路径这里就不介绍了，常见的方式就是pip安装包时，它就会安装到默认路径。&lt;/p&gt;

&lt;p&gt;这里还有一个很重要的路径就是入口文件所在的目录，也会当做模块导入的默认路径，而且优先级最高。&lt;/p&gt;

&lt;p&gt;注意，这是里入口文件所在的目录，而不是你执行代码的目录。这也是符合预期的，也是让我们很方便的导入入口文件所在的目录下包或者模块。&lt;/p&gt;

&lt;p&gt;查看包导入路径的方式是&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;文件路径&quot;&gt;文件路径&lt;/h3&gt;
&lt;p&gt;Python中文件的读写也是很常见的方式。那么文件的路径如何指定呢？可能不同的编程语言有不同的方式。Python是通过cwd来指定，也就是当前工作目录。&lt;/p&gt;

&lt;p&gt;当前工作目录就是执行代码的目录。可以通过如下方式获取cwd路径：&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getcwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里有一点要注意，IDE和命令行的环境不一样，可能导致cwd的目录不同，这也会导致有些代码在IDE中可以执行，而在命令行里无法执行。不要认为Python不可理喻，只是我们缺少一些信息。&lt;/p&gt;

&lt;h3 id=&quot;__init__py&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__.py&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__.py&lt;/code&gt;文件可以理解为就是在为__all__服务，告诉他人当前package中哪些是可以对外使用的。&lt;/p&gt;

&lt;p&gt;我们可以理解为import xx导入都是模块。导入包也是导入模块。如果想导入模块中的特定方法、变量就需要使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;from xxx import xx&lt;/code&gt;的形式。&lt;/p&gt;</content><author><name>Leo</name></author><category term="programme" /><category term="python" /><summary type="html">在很长一段时间，我对于Python的导入系统以及目录操作不是很清楚，很多时候会弄错，也会觉得它很复杂。</summary></entry><entry><title type="html">理解Oauth协议</title><link href="https://www.wenhao.ink/oauth-20250706/" rel="alternate" type="text/html" title="理解Oauth协议" /><published>2025-07-06T00:00:00+00:00</published><updated>2025-07-06T00:00:00+00:00</updated><id>https://www.wenhao.ink/oauth</id><content type="html" xml:base="https://www.wenhao.ink/oauth-20250706/">&lt;p&gt;开始研究Oauth协议，是为了使用饭否的api写点东西，他们使用就是古早的Oauth1.0。&lt;/p&gt;

&lt;p&gt;Oauth协议为了解决第三方应用授权的问题，如何让第三方应用既能够拿到用户的数据，还能保证用户账号的安全。解决的方式是账号密码只在网站拥有者输入，然后第三方和网站拥有者之间，只是用token进行交流。&lt;/p&gt;

&lt;p&gt;Oauth1.0的授权过程比较复杂，还需要对参数排序，还有加密等相对比较繁琐。对于学习来说，了解一下还是有好处，当然，各种语言也有很多库来实现相应的功能。&lt;/p&gt;

&lt;p&gt;学习Oauth协议，可能最重要的还是看他们是如何解决第三方授权的问题，以及在Oauth协议升级的过程的演变。&lt;/p&gt;

&lt;p&gt;Oauth1.0发布与2009年，可能当时HTTPS使用的并不多，所以在解决其安全性上，是通过对请求参数按照一定规则加密来解决，而在Oauth2.0中直接实用HTTPS就简单很多。&lt;/p&gt;

&lt;p&gt;Oauth1.0是为了解决Web应用的授权问题，而并没有在设计上考虑移动端，毕竟07年才发布iPhone第一代。导致移动开发早期大家使用xauth来实现授权登录，它是一种对于Oauth1.0协议的简化，他需要用户提供账号和密码。其实，并不安全。&lt;/p&gt;

&lt;p&gt;而在Oauth2.0设计时就充分考虑到移动端的设计。还解决1.0时一个很大的安全问题，授权之后的access_token并没有过期机制。&lt;/p&gt;

&lt;p&gt;看Oauth协议的发展，也能体会到技术还是要解决现实的问题。&lt;/p&gt;

&lt;p&gt;资料&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://oauth.net/1/&quot;&gt;https://oauth.net/1/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/oauthlib/oauthlib&quot;&gt;https://github.com/oauthlib/oauthlib&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Leo</name></author><category term="programme" /><category term="network" /><summary type="html">开始研究Oauth协议，是为了使用饭否的api写点东西，他们使用就是古早的Oauth1.0。</summary></entry><entry><title type="html">理解Socks5</title><link href="https://www.wenhao.ink/socks5-20250704/" rel="alternate" type="text/html" title="理解Socks5" /><published>2025-07-04T00:00:00+00:00</published><updated>2025-07-04T00:00:00+00:00</updated><id>https://www.wenhao.ink/socks5</id><content type="html" xml:base="https://www.wenhao.ink/socks5-20250704/">&lt;p&gt;它算是一种代理协议，所谓的代理协议的主要功能是转发，将client的数据转发到另外的地方。&lt;/p&gt;

&lt;p&gt;Socks5是比较常用的代理协议，它的两个特点让它的使用范围变的很广。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;支持http、https、ftp等协议&lt;/li&gt;
  &lt;li&gt;支持授权
它是用来转发TCP、UDP，所以也就不关心应用层的到底是何协议。授权解决安全性问题，也就很完美的满足常规代理服务的需求。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通常的用法，在本地运行local服务，在远端运行server服务。本地local服务，即是Socks5的服务端也是Socks5的客户端。作为Socks5的客户端用于接收本地的数据请求。作为Socks5客户端用于与server服务建立连接，传输数据。&lt;/p&gt;

&lt;h3 id=&quot;资料&quot;&gt;资料&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;https://en.wikipedia.org/wiki/SOCKS&lt;/li&gt;
  &lt;li&gt;https://datatracker.ietf.org/doc/html/rfc1928&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Leo</name></author><category term="programme" /><category term="network" /><summary type="html">它算是一种代理协议，所谓的代理协议的主要功能是转发，将client的数据转发到另外的地方。</summary></entry><entry><title type="html">神僧有言</title><link href="https://www.wenhao.ink/divine-monk-20241207/" rel="alternate" type="text/html" title="神僧有言" /><published>2024-12-07T00:00:00+00:00</published><updated>2024-12-07T00:00:00+00:00</updated><id>https://www.wenhao.ink/divine-monk</id><content type="html" xml:base="https://www.wenhao.ink/divine-monk-20241207/">&lt;p&gt;最近北京的天气真是爱了，虽然有风，还很冷，但是胜在干净。一抬头能看到蓝天，最美的还属晚霞，醉人！&lt;/p&gt;

&lt;p&gt;在铃木大拙的《通往世界的禅》第一章“关于禅”的文章中有这样一句话，很喜欢，摘录如下。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;神僧有言：“我说的话，那是我的，不是你的，也不可能成为你的；一切必须是从你自身中发起和成就。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我理解这句话神僧应该是用来指导他人修行的，但是对于现代的我们也很有用。&lt;/p&gt;

&lt;p&gt;我们被太多的外部评价所裹挟，各种排名、收入高低、领导的期许等等。虽然不能完全避免外部评价，但更加重要的还是要从自身出发，建立自己的内核。&lt;/p&gt;

&lt;p&gt;最近，在尝试建立一个新的阅读习惯，就是一本书读两遍。其逻辑就是神僧说的，书中的知识不是你的，只有你通过思考、觉知让他与你产生关系才可能为你所用。而读两遍也是希望自己慢下来，让自己能与它有更深的交流。&lt;/p&gt;

&lt;p&gt;这个过程中肯定会有痛苦，就像文章 &lt;a href=&quot;https://note.mowen.cn/note/detail?noteUuid=vcCs-n2bsSL8cuBceOdbG&quot;&gt;做主动思考者，痛并快乐着&lt;/a&gt; 中说的“&lt;strong&gt;哪个真正的思考者是不痛苦的。&lt;/strong&gt;”&lt;/p&gt;

&lt;p&gt;希望自己能成为真正的思考者。&lt;/p&gt;</content><author><name>Leo</name></author><category term="essay" /><summary type="html">最近北京的天气真是爱了，虽然有风，还很冷，但是胜在干净。一抬头能看到蓝天，最美的还属晚霞，醉人！</summary></entry><entry><title type="html">完美的日子</title><link href="https://www.wenhao.ink/film-perfect-days-20241128/" rel="alternate" type="text/html" title="完美的日子" /><published>2024-11-28T00:00:00+00:00</published><updated>2024-11-28T00:00:00+00:00</updated><id>https://www.wenhao.ink/film-perfect-days</id><content type="html" xml:base="https://www.wenhao.ink/film-perfect-days-20241128/">&lt;p&gt;时隔半年，走进电影院是为了看役所广司主演的《完美的日子》。虽然是公映，但在北京能看到的电影院并不多，场次时间也不太好。可能宣发也知道，这类电影不太可能卖座。&lt;/p&gt;

&lt;p&gt;役所广司饰演的男主角平山是一名卫生间清洁工，大概五十岁，独自一人过着平凡而规律的生活。&lt;/p&gt;

&lt;p&gt;在片中我最喜欢的画面是平山大叔在推开门上班前，抬头看天的瞬间。面露微笑，平静而感恩。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/post-film-perfect-days/perfect_days_1.jpg&quot; alt=&quot;perfect_days_1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;电影中有一段，平山大叔的小侄女尼可离家出走来找他。尼可跟着平山大叔一起去工作，平山大叔带着手套在擦拭卫生间的镜子，这时尼克还拿出手机在录了一段，这时一位和尼克年龄相仿的女学生需要去卫生间，从她的表情里透着鄙夷。平山大叔赶紧让出卫生间，双手交叉放在身前，很端正的站在门口，这时尼克的神情有一些害羞、茫然，当平山大叔侧过脸，对着尼克露出微笑，尼克感觉突然放下了，对着舅舅也露出微笑。第二天，尼克还尝试帮舅舅在卫生间拖地，虽然她用不好拖把。&lt;/p&gt;

&lt;p&gt;每个人都是在用自己的劳动创造价值，本没有贵贱之分。但是，当你在乘坐公交时，身边有一个浑身脏兮兮的建筑工人，大部分人也会想躲开。我们还是会对工作分三六九等，认为打扫卫生间的工作更加低贱一些。倘若我 50 岁时，也只能去做清理卫生间的工作，那时，我会如何看待自己和看待这份工作呢？&lt;/p&gt;

&lt;p&gt;至少，平山大叔给了我一个参照。&lt;/p&gt;

&lt;p&gt;平山大叔有一位年轻的同事阿隆，工作时会坐在地上一边刷着马桶，一边玩手机。阿隆提到平山大叔为干活方便，会自制一些小工具。这应该是导演刻意的设计，但是从中也能看出两种完全不同的生活态度，我们需要有理想，也要活在当下。做好你能做好的事情。&lt;/p&gt;

&lt;p&gt;什么样的日子是完美的呢？我不知道。但是我知道，这样的日子里我会面带微笑。&lt;/p&gt;</content><author><name>Leo</name></author><category term="film" /><summary type="html">时隔半年，走进电影院是为了看役所广司主演的《完美的日子》。虽然是公映，但在北京能看到的电影院并不多，场次时间也不太好。可能宣发也知道，这类电影不太可能卖座。</summary></entry><entry><title type="html">使用 certbot 申请免费 SSL 证书</title><link href="https://www.wenhao.ink/certbot-for-letsencrypt-20241121/" rel="alternate" type="text/html" title="使用 certbot 申请免费 SSL 证书" /><published>2024-11-21T00:00:00+00:00</published><updated>2024-11-21T00:00:00+00:00</updated><id>https://www.wenhao.ink/certbot-for-letsencrypt</id><content type="html" xml:base="https://www.wenhao.ink/certbot-for-letsencrypt-20241121/">&lt;p&gt;现在网站使用 https 已经成为标配，但是 SSL 证书最便宜的 DV 证书也要几百块钱一年，对于个人开发者来说很不划算。好在，我们有 &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt;，它是能提供免费的 SSL 证书，应该也是市面上使用最广泛的免费 DV 证书了。&lt;/p&gt;

&lt;h1 id=&quot;原理&quot;&gt;原理&lt;/h1&gt;
&lt;p&gt;一点开 &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; 还是有点懵的，按照在其他平台申请 SSL 证书的逻辑，它尽然不用注册，那怎么管理证书呢？随着不断的了解，对它也越来越佩服。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; 贡献两个主要的东西&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8555&quot;&gt;ACME protocol&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/letsencrypt/boulder&quot;&gt;boulder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ACME 全称 Automatic Certificate Management Environment。它提供一套自动证书管理的规范，这套规范中包含客户端与服务端。而 &lt;a href=&quot;https://github.com/letsencrypt/boulder&quot;&gt;boulder&lt;/a&gt; 就是 &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; 提供的一套开源的证书颁发软件。ACME 客户端官方没提供，只要支持 ACME 协议都可以实现。目前，官方推荐的客户端是 &lt;a href=&quot;https://certbot.eff.org/&quot;&gt;certbot&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;证书颁发机构只需要确认你拥有该域名的所有权，就可以帮你生成证书（需要注意 &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; 并不支持 OV 和 EV 证书）。&lt;/p&gt;

&lt;p&gt;在服务器上运行 ACME 客户端，已自动化的方法确认用户对于域名的所有权，然后向 &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; 服务端申请证书，通过后，既可以得到所需要的 SSL 证书。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; 生成的免费证书有效期为 90 天，但是它也支持自动续签。&lt;/p&gt;

&lt;h1 id=&quot;使用&quot;&gt;使用&lt;/h1&gt;
&lt;p&gt;以下操作，基于 CentOS7.8 + Nginx 服务器。&lt;/p&gt;

&lt;h2 id=&quot;安装-certbot&quot;&gt;安装 certbot&lt;/h2&gt;
&lt;p&gt;它是一款在 Linux 上使用的现代包管理工具。&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Centos7 中安装 snapd&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;epel-release
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;snapd
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--now&lt;/span&gt; snapd.socket
&lt;span class=&quot;nb&quot;&gt;sudo ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /var/lib/snapd/snap /snap

&lt;span class=&quot;c&quot;&gt;# 查看 snapd 服务状态&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl status snapd

&lt;span class=&quot;c&quot;&gt;# 安装 certbot&lt;/span&gt;
snap &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--classic&lt;/span&gt; certbot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以参考官方文档：&lt;a href=&quot;https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap&quot;&gt;https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;申请证书&quot;&gt;申请证书&lt;/h2&gt;
&lt;p&gt;certbot 有一些傻瓜式的方式可以直接一键生成证书并安装。但是这并不是我需要的，我只希望他帮我生成证书，然后自己在 Nginx 中配置。&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot certonly &lt;span class=&quot;nt&quot;&gt;--webroot&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /path/to/webroot &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; example.com &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; www.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这条指令的作用是以 http 的方式验证域名并单独生成证书。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-w&lt;/code&gt; 指定域名所在的根目录，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt; 指定需要验证的域名。以上命令成功后，它会在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/letsencrypt/live/example.com/&lt;/code&gt; 目录中生成证书。&lt;/p&gt;

&lt;h2 id=&quot;部署证书&quot;&gt;部署证书&lt;/h2&gt;
&lt;p&gt;部署证书的操作，另行搜索即可。&lt;/p&gt;

&lt;h2 id=&quot;自动续签&quot;&gt;自动续签&lt;/h2&gt;
&lt;p&gt;在证书还有 30 天过期时，重新验证域名的所有权。验证成功重新颁发证书，并重启 Nginx 服务。
&lt;a href=&quot;https://certbot.eff.org/&quot;&gt;certbot&lt;/a&gt; 已经将这些功能实现，只需要进行少量配置即可。&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 验证是否能够续签&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot renew &lt;span class=&quot;nt&quot;&gt;--dry-run&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;验证续签功能通过，说明当前环境没问题。&lt;/p&gt;

&lt;p&gt;通过snap安装certbot时，会自动在systemctl中安装续签定时调度。可以通过如下指令查看。&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemctl list-timers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;看到如下配置，说明定时调度配置是成功的。&lt;/p&gt;
&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# systemctl list-timers
NEXT                         LEFT       LAST                         PASSED       UNIT                         ACTIVATES
Mon 2025-11-03 22:55:00 CST  8h left    Mon 2025-11-03 06:52:12 CST  7h ago       snap.certbot.renew.timer     snap.certbot.renew.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;后续，我们只需要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/letsencrypt/renewal-hooks/deploy&lt;/code&gt;在目录中添加部署脚本&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reload-nginx.sh&lt;/code&gt;，即可实现自动续签和自动重启Nginx服务。&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 日志文件&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;LOG_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/var/log/certbot-hook.log&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;+%Y-%m-%d %H:%M:%S&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 记录开始&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;========================================&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] Hook 脚本开始执行&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 测试 Nginx 配置&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] 测试 Nginx 配置...&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] Nginx 配置测试通过&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# 重载 Nginx&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] 开始重载 Nginx...&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;systemctl reload nginx &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] ✅ Nginx 重载成功&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;0
    &lt;span class=&quot;k&quot;&gt;else
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] ❌ Nginx 重载失败&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
    &lt;span class=&quot;k&quot;&gt;fi
else
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] ❌ Nginx 配置测试失败，跳过重载&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOG_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;其他&quot;&gt;其他&lt;/h2&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 强制更新证书&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot certonly &lt;span class=&quot;nt&quot;&gt;--webroot&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /path/to/webroot &lt;span class=&quot;nt&quot;&gt;--force-renewal&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--deploy-hook&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;nginx -s reload&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; example.com &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; www.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;引用&quot;&gt;引用&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;https://letsencrypt.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8555&quot;&gt;https://datatracker.ietf.org/doc/html/rfc8555&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/letsencrypt/boulder&quot;&gt;https://github.com/letsencrypt/boulder&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://certbot.eff.org/&quot;&gt;https://certbot.eff.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Leo</name></author><category term="programme" /><category term="https" /><category term="certbot" /><category term="letsencrypt" /><summary type="html">现在网站使用 https 已经成为标配，但是 SSL 证书最便宜的 DV 证书也要几百块钱一年，对于个人开发者来说很不划算。好在，我们有 Let’s Encrypt，它是能提供免费的 SSL 证书，应该也是市面上使用最广泛的免费 DV 证书了。</summary></entry><entry><title type="html">开始：我与InLong的故事</title><link href="https://www.wenhao.ink/how-to-contribute-inlong-20230819/" rel="alternate" type="text/html" title="开始：我与InLong的故事" /><published>2023-08-19T00:00:00+00:00</published><updated>2023-08-19T00:00:00+00:00</updated><id>https://www.wenhao.ink/how-to-contribute-inlong</id><content type="html" xml:base="https://www.wenhao.ink/how-to-contribute-inlong-20230819/">&lt;p&gt;从2023年5月下旬开始参与&lt;a href=&quot;https://github.com/apache/inlong&quot;&gt;InLong&lt;/a&gt;项目，已经快3个月，正好用这篇文章记录下我的这段经历。&lt;/p&gt;

&lt;p&gt;参与InLong的起因是赋闲在家，虽然平时也会关注技术相关的发展，但是也担心久不写代码会手生。而参与开源项目就成为我的一个选项，在前司主要是负责大数据接入侧工作，也是出于对腾讯公司的喜爱就将目标锁定在Apache InLong上。&lt;/p&gt;

&lt;p&gt;虽然，对于自身的技术水品并不担心，但是多少还是有一些恐惧在里面，这种恐惧可能来自于Apache的名头或者完美主义的想象。所以，在一开始我选择先在&lt;a href=&quot;https://github.com/apache/inlong-website&quot;&gt;inlong-website&lt;/a&gt;项目中提交代码，它是InLong的官方文档项目。在阅读文档的过程中，自然的会发现一些问题（没有问题是不可能的）。参考其他已经合并的issue，自己也开始写文档，提交修改。&lt;/p&gt;

&lt;p&gt;我们出生在一个好的时代，虽然我的英语很烂，但是翻译工具已经能满足我基本的需求。如果有这方面担心的朋友完全不用担心，你要相信维护项目的人一定能看懂你写的可能有错误的英语。&lt;/p&gt;

&lt;p&gt;连续在&lt;a href=&quot;https://github.com/apache/inlong-website&quot;&gt;inlong-website&lt;/a&gt;中提交3个PR并成功合并后，我也基本掌握项目的贡献流程，更为重要的打破了心里的恐惧（面对它，才能解决它），后面在主项目中提交PR也就水到渠成。&lt;/p&gt;

&lt;p&gt;我有一个习惯，在了解新东西时会先将所有公开的信息浏览一遍，包含文档、项目、公众号、公开视频等，这个过程也是在建立整体的认知，比如公众号中一些介绍InLong demo运行的文章，就比官方文档更加详细和个性化。&lt;/p&gt;

&lt;p&gt;接下来，我将精力花费在搭建开发环境和阅读代码上。这里有两个小建议：其一，不要放过任何一个在玩项目中出现的你认为有问题的地方；其二，明确自己感兴趣的模块，这能让自己的精力更加集中。InLong项目给我的第一印象是项目结构很好，CI/CD构建方面有很多是公司项目里可以借鉴的。只要愿意去深挖总能发现一些有意思和值得学习的点。这也是参与开源项目对个人的价值所在吧。&lt;/p&gt;

&lt;p&gt;我已经记不得是如何与InLong项目PMC docker建立联系的。在与他的沟通中表达自己参与InLong项目的意愿，他给我了我很多的鼓励，也会将一些issue分给我，让我能更快的融入开源团队。&lt;/p&gt;

&lt;p&gt;参与开发的过程中，让我更加确定理解业务和沟通可能比编码更重要。项目的开发者分布在天南地北，没法像在公司里一样，拉个会就能将问题对齐。而且，作为新人你对于项目整体架构和设计掌握的不够全面，那么在涉及重要功能或者全局性的修改时更要谨慎，先将方案沟通清楚再写代码会事半功倍。&lt;/p&gt;

&lt;p&gt;当然，每个程序员都是各自的审美与坚持。难免，在一些功能实现细节和风格上会产生争执。这种时候需要一些妥协。我相信这些都能解决，毕竟大家参与到InLong项目的建设都是希望它更好。&lt;/p&gt;

&lt;p&gt;这段时间断断续续的提交8个PR，目前正在开发sort中基于Flink1.15的kafka-connector。对我来说心态的变化是最大的，在使用InLong遇到问题不再是想着将其抛出去，而是去研究它为什么会如此，我能怎么解决它。感谢一路上帮助过我的docker、wake、van、healchow、goson等等。&lt;/p&gt;

&lt;p&gt;最后，如果你看到这篇文章，也愿意参与到开源项目的建设中，那么参与InLong会是你非常好的选择，因为这是一个有爱的团队。&lt;/p&gt;

&lt;p&gt;Apache InLong项目：&lt;a href=&quot;https://github.com/apache/inlong&quot;&gt;https://github.com/apache/inlong&lt;/a&gt;&lt;/p&gt;</content><author><name>Leo</name></author><category term="essay" /><category term="bigdata" /><category term="inlong" /><summary type="html">从2023年5月下旬开始参与InLong项目，已经快3个月，正好用这篇文章记录下我的这段经历。</summary></entry><entry><title type="html">华为云CentOS7中docker的安装</title><link href="https://www.wenhao.ink/huaweicloud-docker-20230627/" rel="alternate" type="text/html" title="华为云CentOS7中docker的安装" /><published>2023-06-27T00:00:00+00:00</published><updated>2023-06-27T00:00:00+00:00</updated><id>https://www.wenhao.ink/huaweicloud-docker</id><content type="html" xml:base="https://www.wenhao.ink/huaweicloud-docker-20230627/">&lt;p&gt;使用华为云耀云服务器CentOS 7.9版本。&lt;/p&gt;
&lt;h1 id=&quot;docker安装&quot;&gt;docker安装&lt;/h1&gt;
&lt;p&gt;参考华为云开源镜像中Docker-CE镜像的配置。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/post-huaweicloud-docker/huaweicloud-docker-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; yum-utils device-mapper-persistent-data lvm2

wget &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; /etc/yum.repos.d/docker-ce.repo https://repo.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo

&lt;span class=&quot;nb&quot;&gt;sudo sed&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s+download.docker.com+repo.huaweicloud.com/docker-ce+&apos;&lt;/span&gt; /etc/yum.repos.d/docker-ce.repo

&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum makecache fast
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; docker-ce

&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl start docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PS：注意以上命令在CentOS8中有问题，只推荐在CentOS7中使用。&lt;/p&gt;
&lt;h1 id=&quot;docker-compse安装&quot;&gt;docker-compse安装&lt;/h1&gt;
&lt;p&gt;直接从docker-compse在github的项目中下载指定平台和架构的二进制文件。
比如linux平台 x86_64平台。&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; https://github.com/docker/compose/releases/download/v2.19.0/docker-compose-linux-x86_64 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /usr/local/bin/docker-compose
&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;华为云docker镜像加速&quot;&gt;华为云docker镜像加速&lt;/h1&gt;
&lt;p&gt;在华为云中支持docker镜像加速。
登录华为云账号后找到SWR服务后，如下图找到镜像加速器按钮。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/post-huaweicloud-docker/huaweicloud-docker-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;弹出如下配置：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/post-huaweicloud-docker/huaweicloud-docker-3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;配置完成后，重启docker&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;资料&quot;&gt;资料&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/centos/&quot;&gt;https://docs.docker.com/engine/install/centos/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/&quot;&gt;https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Leo</name></author><category term="programme" /><category term="bigdata" /><category term="docker" /><summary type="html">使用华为云耀云服务器CentOS 7.9版本。 docker安装 参考华为云开源镜像中Docker-CE镜像的配置。</summary></entry></feed>