<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://litaolin.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://litaolin.com/" rel="alternate" type="text/html" hreflang="zh" /><updated>2026-09-13T17:56:43-04:00</updated><id>https://litaolin.com/feed.xml</id><title type="html">Litao</title><subtitle>在美国的生活观察，以及数据集建设、程序开发的规范方法与工具推荐，以 CBDB 为例。</subtitle><entry xml:lang="zh"><title type="html">给站点接上自己的域名：一天的 Cloudflare 配置记录</title><link href="https://litaolin.com/2026/cloudflare-setup/" rel="alternate" type="text/html" title="给站点接上自己的域名：一天的 Cloudflare 配置记录" /><published>2026-09-13T00:00:00-04:00</published><updated>2026-09-13T00:00:00-04:00</updated><id>https://litaolin.com/2026/cloudflare-setup</id><content type="html" xml:base="https://litaolin.com/2026/cloudflare-setup/"><![CDATA[<p>站点本身怎么搭的，写在《<a href="/2026/colophon/">这个站点是怎么搭的</a>》里，这里不重复。它一直挂在 <code class="language-plaintext highlighter-rouge">用户名.github.io</code> 这个地址上；今天把自己的域名接上了，顺带配了 Cloudflare 的一整套东西。操作本身不复杂，值得记的是几个不照着文档走就会卡住的地方。</p>

<h2 id="为什么值得买一个域名">为什么值得买一个域名</h2>

<p>这是今天唯一花钱的部分，一年十来美元。</p>

<p>理由不是速度，也不是好看。个人站点相对平台的核心优势是复利——三年前的文章今天仍然在被搜到、被链接、被引用，而一条社交媒体动态四十八小时后就死了。但这条复利完全建立在 <strong>URL 稳定</strong>的前提上。</p>

<p><code class="language-plaintext highlighter-rouge">用户名.github.io</code> 把你绑在了 GitHub 上。有了自己的域名之后，托管商、CDN、生成器将来都可以换，而那个地址可以跟着走。买的其实是<strong>可携带性</strong>，不是域名本身。</p>

<p>后缀我最后选了 <code class="language-plaintext highlighter-rouge">.com</code>。考虑过 <code class="language-plaintext highlighter-rouge">.io</code>，因为它在技术圈观感好，但查清楚之后放弃了：它原本是英属印度洋领地的国家代码域名，而该领地的主权状态正在变动。为一个要持有二十年的地址，去选一个后缀存续性存疑的域名，和买它的初衷相悖。</p>

<h2 id="反直觉之一签证书必须先把-cdn-关掉">反直觉之一：签证书必须先把 CDN 关掉</h2>

<p>这是今天卡得最久的一步。</p>

<p>Cloudflare 的 DNS 记录有个代理开关，橙云表示流量经过 Cloudflare，灰云表示它只做解析、不碰流量。直觉上橙云功能更多，应该一开始就打开。</p>

<p>但打开之后，GitHub Pages 的 <strong>Enforce HTTPS</strong> 这个选项一直是灰的，提示域名未正确配置以支持 HTTPS。</p>

<p>原因是<strong>签发证书和使用证书是两回事</strong>。GitHub 要向 Let's Encrypt 申请证书，而 Let's Encrypt 不能随便发——否则任何人都能申请别人域名的证书。它要求申请者先证明控制权：在服务器上放一个特定文件，然后自己去访问 <code class="language-plaintext highlighter-rouge">http://你的域名/某个路径</code>，看能不能读到。</p>

<p>开着橙云时，这个验证请求到达的是 Cloudflare 的边缘节点，那里没有那个文件，验证失败。</p>

<p>所以正确顺序是：<strong>灰云 → 等证书签发 → 再改橙云</strong>。证书一旦到手就是一份存在服务器上的文件，使用它不需要任何外部验证，这时候 Cloudflare 插在中间毫无问题。</p>

<p>这个模式值得记住，它不只出现在这里：<strong>验证所有权需要直连，提供服务不需要</strong>。以后配邮箱域名验证、申请泛域名证书、接入某些第三方服务，大概率还会遇到"先把代理临时关掉"这种要求。</p>

<p>顺带两个相关的坑：</p>

<ul>
  <li>改回橙云之后，SSL/TLS 加密模式必须设成 <strong>Full (strict)</strong>。默认可能是 Flexible，那个模式下 Cloudflare 用 HTTP 回源，而 GitHub Pages 会强制跳转到 HTTPS，两边互踢，形成无限重定向循环。</li>
  <li>HSTS 我没开。它的特性是<strong>不可撤销</strong>——浏览器记住之后，在有效期内即使你关掉它也无法回退。对一个没有登录、没有表单、内容本来就公开的站点，它防的那点风险和它带来的故障可能性不成比例。</li>
</ul>

<h2 id="反直觉之二统计脚本可以不经过仓库">反直觉之二：统计脚本可以不经过仓库</h2>

<p>接 Cloudflare Web Analytics 时，我原本以为必须手动把那段 JS 写进模板，因为站点托管在 GitHub 而不是 Cloudflare 自己的产品上。实际上只要开了橙云代理就能自动注入，源站在哪不重要。</p>

<p>原理比结论有意思。橙云状态下 Cloudflare 不只是转发，它<strong>可以修改经过的响应内容</strong>：从 GitHub 拿到原始 HTML 之后，在 <code class="language-plaintext highlighter-rouge">&lt;/body&gt;</code> 前插入信标脚本，再发给访客。</p>

<p>所以仓库里的文件从头到尾没变。<code class="language-plaintext highlighter-rouge">git clone</code> 下来搜不到那段脚本，浏览器查看源码却能看到——这两个"源码"根本不是同一份东西。</p>

<p>这也顺带解释了另一件事：如果响应头里有 <code class="language-plaintext highlighter-rouge">Cache-Control: public, no-transform</code>，注入会失败。<code class="language-plaintext highlighter-rouge">no-transform</code> 的字面意思就是禁止中间代理改写内容，Cloudflare 遵守它。</p>

<p>值得意识到的一点是：这个能力意味着 Cloudflare 能读到并修改所有经过的内容。对公开博客无所谓，但它不是一个被动的管道，而是一个有完全访问权的中间人。灰云时它是电话簿，橙云时它是代理人。</p>

<h2 id="同一个错误一天出现三次">同一个错误，一天出现三次</h2>

<p>今天最有价值的收获不是某个具体配置，而是一个反复出现的模式。</p>

<p>试着把站点迁到 Cloudflare Pages 做备选方案时，构建失败了：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Invalid US-ASCII character "\xE2" on line 5
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">\xE2</code> 是 UTF-8 里通用标点的首字节——破折号、弯引号这类字符。文件本身没问题，是构建容器的 locale 不是 UTF-8，Ruby 默认按 ASCII 读文件，遇到多字节字符就崩了。</p>

<p>加两条环境变量解决：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LANG=C.UTF-8
LC_ALL=C.UTF-8
</code></pre></div></div>

<p>用 <code class="language-plaintext highlighter-rouge">C.UTF-8</code> 而不是 <code class="language-plaintext highlighter-rouge">en_US.UTF-8</code>，因为前者在任何 glibc 系统上都存在，后者需要镜像里预先生成过。</p>

<p>关键在于，<strong>同一类问题今天已经是第三次出现</strong>：</p>

<ol>
  <li>在云服务器上装环境时，最小化镜像默认 locale 是 POSIX，Jekyll 处理中文正文直接抛异常</li>
  <li>在 VS Code 集成终端里，环境变量因为非交互式 shell 提前 return 而没被读到</li>
  <li>现在是云端构建容器</li>
</ol>

<p>三次之后它就不是偶然了。对写中文的人来说，<strong>任何新的执行环境都要先确认 locale 是 UTF-8</strong>，这应该是默认动作，不是出了问题再排查的事项。</p>

<h2 id="一个小教训入口点错了">一个小教训：入口点错了</h2>

<p>配 Pages 时最开始进错了界面。Cloudflare 把 Workers 和 Pages 放在同一个菜单下，两者入口挨得很近。</p>

<p>识别方法：如果界面上出现「部署命令」并预填了 <code class="language-plaintext highlighter-rouge">npx wrangler deploy</code>，那是 Workers——用来部署 JavaScript 函数的。Pages 的界面上应该是「框架预设」「构建命令」「构建输出目录」这三栏。</p>

<p>静态站点要走 Pages。Jekyll 那几项填构建命令 <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code>、输出目录 <code class="language-plaintext highlighter-rouge">_site</code>，再加一条 <code class="language-plaintext highlighter-rouge">RUBY_VERSION</code>——这些和本地构建是一回事，《<a href="/2026/colophon/">这个站点是怎么搭的</a>》里写过。</p>

<h2 id="现在的架构">现在的架构</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>域名所有权    Cloudflare Registrar
DNS 解析      Cloudflare
源码和版本    GitHub
构建和托管    GitHub Pages
CDN / HTTPS   Cloudflare
访问统计      Cloudflare Web Analytics
</code></pre></div></div>

<p>三家产品拼在一起，各管一段。这也是为什么配置要在两个后台来回切——不是哪里搞错了，是架构本来如此。</p>

<p>值得说的是这个结构的<strong>可替换性</strong>：域名在自己名下、源码在 Git 历史里、内容是纯 Markdown。任何一层想换，改几处配置就能走，不会被锁死在谁手里。</p>

<p>这正是今天那十几美元买到的东西。</p>]]></content><author><name></name></author><summary type="html"><![CDATA[站点本身怎么搭的，写在《这个站点是怎么搭的》里，这里不重复。它一直挂在 用户名.github.io 这个地址上；今天把自己的域名接上了，顺带配了 Cloudflare 的一整套东西。操作本身不复杂，值得记的是几个不照着文档走就会卡住的地方。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://litaolin.com/assets/img/og-card.png" /><media:content medium="image" url="https://litaolin.com/assets/img/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">从守望先锋走进纽约中央车站</title><link href="https://litaolin.com/2026/overwatch-grand-central/" rel="alternate" type="text/html" title="从守望先锋走进纽约中央车站" /><published>2026-09-11T00:00:00-04:00</published><updated>2026-09-11T00:00:00-04:00</updated><id>https://litaolin.com/2026/overwatch-grand-central</id><content type="html" xml:base="https://litaolin.com/2026/overwatch-grand-central/"><![CDATA[<p>这篇只说两件事。</p>

<p>第一件：我玩守望先锋。</p>

<p>第二件：游戏里那张以纽约为背景的地图，车站大厅那一段，一眼就能认出原型是中央车站（Grand Central Terminal）。我去实地看了一次，录了一段。</p>

<div class="embed">
<iframe src="https://player.bilibili.com/player.html?bvid=BV1Sxk3BpEQA&amp;autoplay=0&amp;high_quality=1&amp;danmaku=0" title="实地探访纽约中央车站" loading="lazy" scrolling="no" frameborder="no" allowfullscreen=""></iframe>
</div>

<p>播放器加载不出来的话，<a href="https://www.bilibili.com/video/BV1Sxk3BpEQA/">直接去 B 站看</a>。</p>]]></content><author><name></name></author><summary type="html"><![CDATA[这篇只说两件事。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://litaolin.com/assets/img/og-card.png" /><media:content medium="image" url="https://litaolin.com/assets/img/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">为什么要建这个站点</title><link href="https://litaolin.com/2026/why-this-site/" rel="alternate" type="text/html" title="为什么要建这个站点" /><published>2026-08-26T00:00:00-04:00</published><updated>2026-08-26T00:00:00-04:00</updated><id>https://litaolin.com/2026/why-this-site</id><content type="html" xml:base="https://litaolin.com/2026/why-this-site/"><![CDATA[<p>这个站有一半是写方法的：程序开发和数据集制作的一般规范，以及好用的工具，多半结合 CBDB 的工作实践。说明一下为什么写这些。</p>

<p>我个人非常在意工作流程的规范性，力求找到所有环节的最佳实践。</p>

<p>硕士期间我长期从事基于机器学习、神经网络模型的古文自动化处理工具的开发与评测，以及中国古代文献的知识组织和知识挖掘研究。过往的研究经历让我意识到，一个不规范的开始，往往使得后续的大量时间浪费在修补和纠正前期工作上。</p>

<p>所以我希望在这里分享大家好用的方法。这里也有我的一点私心：作为合作者，大家使用这些方法，省下的是自己的时间，也会间接地为我提供便利。</p>

<p>建这个站点还有另一方面的动机：想借它探索 AI 的创造力，以及美国互联网生态基础设施的可用性和可靠性。</p>]]></content><author><name></name></author><summary type="html"><![CDATA[这个站有一半是写方法的：程序开发和数据集制作的一般规范，以及好用的工具，多半结合 CBDB 的工作实践。说明一下为什么写这些。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://litaolin.com/assets/img/og-card.png" /><media:content medium="image" url="https://litaolin.com/assets/img/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">这个站点是怎么搭的</title><link href="https://litaolin.com/2026/colophon/" rel="alternate" type="text/html" title="这个站点是怎么搭的" /><published>2026-08-22T00:00:00-04:00</published><updated>2026-08-22T00:00:00-04:00</updated><id>https://litaolin.com/2026/colophon</id><content type="html" xml:base="https://litaolin.com/2026/colophon/"><![CDATA[<p>这个站点是 Jekyll 生成的静态页面，托管在 GitHub Pages 上，用的是 Pages 的原生构建，没有配 GitHub Actions。整站没有任何服务端逻辑。域名走 Cloudflare，访问统计用的是 Cloudflare Web Analytics——它由边缘节点注入，仓库里没有相关代码，也不使用 cookie，只记录页面维度的访问量。写作流程是在编辑器里写 Markdown，<code class="language-plaintext highlighter-rouge">git push</code>，GitHub 自动构建并发布。</p>

<p>暂时不做评论区。理由不是技术上做不到，而是我想先把写作本身跑顺。多媒体内容一律外链——视频挂在别的平台，大文件放 Releases，站点本身只有文字和少量压缩过的图片。这样加载快，也不会因为某个第三方服务停摆而坏掉。</p>

<p>正文用 Source Serif 4 配思源宋体系统，两者本是同一设计体系，中英混排时横竖笔画的粗细能对上。中文段落两端对齐，西文段落左对齐——汉字等宽，两端对齐不会出现西文那种断裂的空隙。</p>

<p>想订阅的话，页脚有 RSS。</p>

<p>下面是完整的搭建和日常维护记录。原先散在几个文档里，现在归到一处。</p>

<h2 id="从零建一个">从零建一个</h2>

<p><strong>仓库命名。</strong> 新建一个<strong>公开</strong>仓库，名字必须严格是 <code class="language-plaintext highlighter-rouge">你的用户名.github.io</code>——用户名是 <code class="language-plaintext highlighter-rouge">litao</code>，仓库名就得是 <code class="language-plaintext highlighter-rouge">litao.github.io</code>。这是 GitHub 识别「个人主站」的唯一命名方式，差一个字符都不行。</p>

<p><strong>上传文件。</strong> 把项目全部文件放进仓库根目录。网页上传走「Add file → Upload files」；命令行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"初始站点"</span>
git branch <span class="nt">-M</span> main
git remote add origin https://github.com/你的用户名/你的用户名.github.io.git
git push <span class="nt">-u</span> origin main
</code></pre></div></div>

<p><strong>打开 Pages。</strong> 仓库 → Settings → Pages，三项设成：Source 选 <strong>Deploy from a branch</strong>，Branch 选 <code class="language-plaintext highlighter-rouge">main</code>，Folder 选 <code class="language-plaintext highlighter-rouge">/ (root)</code>。保存后等一两分钟，<code class="language-plaintext highlighter-rouge">https://你的用户名.github.io</code> 就能访问了。</p>

<h2 id="配置">配置</h2>

<p><code class="language-plaintext highlighter-rouge">_config.yml</code> 顶部那几个字段是唯一需要动的地方：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">title</code> / <code class="language-plaintext highlighter-rouge">title_en</code>——你的名字。两者相同时，英文那行会自动隐藏。</li>
  <li><code class="language-plaintext highlighter-rouge">tagline</code> / <code class="language-plaintext highlighter-rouge">tagline_en</code>——首页正中的定位语，中英各一行。</li>
  <li><code class="language-plaintext highlighter-rouge">email</code>——页脚的联系邮箱。</li>
  <li><code class="language-plaintext highlighter-rouge">url</code>——<code class="language-plaintext highlighter-rouge">https://你的用户名.github.io</code>。</li>
</ul>

<p>然后把 <code class="language-plaintext highlighter-rouge">about.md</code> 里的占位内容换成自己的介绍。改完 <code class="language-plaintext highlighter-rouge">_config.yml</code> 要重新推送才生效。</p>

<h2 id="日常写作">日常写作</h2>

<p><strong>文件命名。</strong> 在 <code class="language-plaintext highlighter-rouge">_posts/</code> 下新建文件，名字必须是 <code class="language-plaintext highlighter-rouge">年-月-日-英文短名.md</code>。命名或日期格式不对，文章不会显示——这是最常见的「我明明推上去了怎么没有」的原因。</p>

<p><strong>Front matter 模板：</strong></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">中文标题"</span>
<span class="na">title_en</span><span class="pi">:</span> <span class="s2">"</span><span class="s">English</span><span class="nv"> </span><span class="s">Title"</span>
<span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">首页列表里显示的一两句话。可以不写。"</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2026-09-15</span>
<span class="na">lang</span><span class="pi">:</span> <span class="s">zh</span>
<span class="nn">---</span>

正文从这里开始。
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">lang</code> 填 <code class="language-plaintext highlighter-rouge">zh</code> 或 <code class="language-plaintext highlighter-rouge">en</code>，填 <code class="language-plaintext highlighter-rouge">zh</code> 会自动两端对齐。</p>

<p><strong>常用元素。</strong> 脚注是正文里写 <code class="language-plaintext highlighter-rouge">[^标签]</code>、文末写 <code class="language-plaintext highlighter-rouge">[^标签]: 内容</code>，序号和位置自动排；引用块行首加 <code class="language-plaintext highlighter-rouge">&gt;</code>。</p>

<p>图片压成 WebP 放进 <code class="language-plaintext highlighter-rouge">assets/img/</code>，全宽图控制在 100KB 以内，压缩用 <a href="https://squoosh.app">Squoosh</a> 就够。引用方式：</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">![</span><span class="nv">说明</span><span class="p">](</span><span class="sx">/assets/img/文件名.webp</span><span class="p">)</span>
</code></pre></div></div>

<p>视频不要上传进仓库，嵌一个播放器：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"embed"</span><span class="nt">&gt;</span>
<span class="nt">&lt;iframe</span> <span class="na">src=</span><span class="s">"https://www.youtube-nocookie.com/embed/视频ID"</span> <span class="na">title=</span><span class="s">"标题"</span> <span class="na">loading=</span><span class="s">"lazy"</span> <span class="na">allowfullscreen</span><span class="nt">&gt;&lt;/iframe&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">.embed</code> 会保持 16:9 比例，<code class="language-plaintext highlighter-rouge">loading="lazy"</code> 减少首屏压力。讲稿 PDF、数据集这类大文件放 GitHub Releases 或 Zenodo，文章里外链引用。</p>

<h2 id="字体与本地预览">字体与本地预览</h2>

<p>西文正文是 Source Serif 4，中文用系统宋体（Mac 上通常是 Songti SC）。Windows 下宋体屏显偏细，把 <code class="language-plaintext highlighter-rouge">_config.yml</code> 里的</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">typeface</span><span class="pi">:</span> <span class="s">serif</span>
</code></pre></div></div>

<p>改成 <code class="language-plaintext highlighter-rouge">sans</code>，中文就切到黑体（苹方 / 思源黑体 / 微软雅黑）。</p>

<p>本地预览是可选的，不预览直接推上去看效果也行。要在本地跑：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>bundler
bundle <span class="nb">install
</span>bundle <span class="nb">exec </span>jekyll serve
</code></pre></div></div>

<p>然后访问 <code class="language-plaintext highlighter-rouge">http://localhost:4000</code>。</p>

<h2 id="几件需要记住的事">几件需要记住的事</h2>

<p>仓库是公开的，源文件对所有人可见，别提交私人内容。更要紧的是：Git 删除文件不会抹掉历史记录，敏感内容一旦提交过，就可能长期可见——发现提交错了，改文件是不够的。</p>

<p>容量上，站点上限约 1GB，月流量软限制约 100GB，所以媒体一律外链。</p>

<p>插件目前只用 GitHub Pages 白名单内的三个（feed、sitemap、seo-tag），所以能原生构建；哪天需要白名单外的插件，就得改成 GitHub Actions 构建流程，这是个不小的切换。想加邮件订阅的话，在 <code class="language-plaintext highlighter-rouge">_includes/footer.html</code> 里放个表单就行，不影响现有结构。</p>]]></content><author><name></name></author><summary type="html"><![CDATA[这个站点是 Jekyll 生成的静态页面，托管在 GitHub Pages 上，用的是 Pages 的原生构建，没有配 GitHub Actions。整站没有任何服务端逻辑。域名走 Cloudflare，访问统计用的是 Cloudflare Web Analytics——它由边缘节点注入，仓库里没有相关代码，也不使用 cookie，只记录页面维度的访问量。写作流程是在编辑器里写 Markdown，git push，GitHub 自动构建并发布。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://litaolin.com/assets/img/og-card.png" /><media:content medium="image" url="https://litaolin.com/assets/img/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>