9480 字
47 分钟
通过Github Action实现自动推送英文外刊到Obsidian中
2025-04-10
无标签

因为我自己本身也有搜集高质量信源以观察市场的需求+该死的英一对词汇量和翻译能力的要求比较高,我决定开始系统性地精读外文刊物。

但……你们知道的,我是懒狗,有没有一种优雅的方法将这些外刊内容同步到我的阅读设备(比如我的 MatePad)上,并且方便我随时打开进行阅读、划词翻译和做笔记,而无需经历繁琐的文件传输和格式转换?

——目光转向Obsidian

没错,就是那个强大的、基于本地 Markdown 文件的双链笔记软件。因为Obsidian基于本地文件夹结构,我们可以利用各种第三方同步工具实现免费或自托管的同步方案,只要你配置好了多端同步(我是用的是腾讯云cos),那你近乎可以在所有平台体验到完全相同的阅读/笔记体验。

Obsidian 看起来能完美解决我的阅读和笔记需求。但最开始的问题——“懒”,还没解决。我总不能每天手动去各大外刊网站寻找、下载文章,再复制粘贴或者转换格式,最后放到 Obsidian 的库(Vault)里吧?这不仅繁琐,而且一点也不“优雅”。

好,我们来继续!

我偶然发现 awesome-english-ebooks 这个Github库会在每周五定期更新经济学人、纽约客等英语外刊杂志的PDF/Mobi/Epub文件,这个仓库解决了内容来源的问题,而且更新频率稳定。这意味着,理论上,我只需要找到一种方法,在每周五这个仓库更新后,自动抓取最新的期刊文件,然后把它们放到我的 Obsidian (对应云端的s3 cos)库里,就可以实现全自动化的外刊抓取与阅读了。

这就是 Github Actions 发挥作用的地方。

Github Actions 是 GitHub 提供的一项持续集成和持续部署(CI/CD)服务,但它的能力远不止于此。我们可以利用它来自动化几乎任何与代码仓库相关的任务,包括我们现在这个需求:定时检查外部资源、下载文件、并将这些文件提交到我们自己的仓库中

我的设想流程是这样的:

  1. 设置一个定时触发器:利用 Github Actions 的 schedule 功能,让工作流(Workflow)每周(比如周五晚上或周六早上)自动运行一次。

  2. 拉取最新外刊:工作流启动后,它会去 awesome-english-ebooks 仓库,把最新的期刊文件(比如我主要关心的《经济学人》的 PDF 文件)下载到 Action 的运行环境中。

  3. 处理和组织文件(可选):可以根据需要对下载的文件进行重命名,或者按照期刊名称、日期创建好文件夹结构,让它们在 Obsidian 中看起来更整洁。

  4. 将文件推送到“Obsidian 仓库”这是关键一步。为了让 Github Actions 能够管理我们 Obsidian 的内容,我选择直接让Action把对应的PDF文件上传到S3储存桶的指定文件夹里,由此实现了巧妙的同步。

  5. 触发同步:当 Github Actions 将新的外刊文件 push 到“Obsidian 仓库”后,本地设备上配置的同步方案(无论是基于 Git 的同步插件如 Obsidian Git,还是像腾讯云 COS + FolderSync/Syncthing 等)就会检测到云端(即 Github 仓库)的变化,并将这些新文件自动拉取到你的 MatePad 等设备上的 Obsidian Vault 中。

最终效果:

每周什么都不用做,最新的《经济学人》(或其他你选择的刊物)就会自动出现在我所有设备的 Obsidian 中,随时可以打开阅读、划词、做笔记。下载、传输、整理这些繁琐的步骤完全消失,真正实现了“懒人”的优雅阅读。

为了复现我的 Workflow,你需要准备:

  • 一个Github账号
  • 一个S3储存桶账号(腾讯云阿里云Cloudflare都有,选择太多了)
  • Obsidian笔记
  • 一点点的耐心

一、Obsidian设置#

为了让 Github Actions 下载的外刊能够自动出现在我们的 Obsidian 中,我们需要先打通 Obsidian 与云存储之间的同步通道。这里我们选用一个广受好评的第三方同步插件:Remotely Save

1. 安装 Remotely Save 插件:#

  • 打开你的 Obsidian。
  • 进入 设置 (Settings) -> 第三方插件 (Community Plugins)。
  • 重要提示: 如果你之前没有启用过社区插件,需要先关闭 安全模式 (Safe Mode)。请知晓其中的风险,只安装你信任的插件。
  • 点击 浏览 (Browse) 社区插件市场。
  • 搜索 Remotely Save。
  • 找到插件后,点击 安装 (Install),安装完成后点击 启用 (Enable)。

2. 配置 Remotely Save 连接 S3 服务:#

安装并启用后,Remotely Save 的设置选项会出现在左侧边栏的 第三方插件 区域。点击进入其设置界面。

  • 选择远程服务: 在插件设置中,找到选择远程服务提供商的选项。这里你需要选择 S3 或者专门列出的 Tencent COS (如果插件有此区分选项)。

  • 填写 S3 凭证: 这是最关键的一步。你需要填入连接到你云存储桶所需的信息。具体设置界面可能因插件版本略有不同,但核心信息是相同的。 你可以参考下图所示的配置界面: 参考

  • 获取必要的 S3 凭证信息: 你需要登录你的云服务提供商控制台(以腾讯云对象存储 COS 为例)获取以下四个关键信息。请务必妥善保管,尤其是 SecretKey!

    • COS_SECRET_ID (对应 S3 的 Access Key ID): 访问密钥 ID,用于标识你的账户。
    • COS_SECRET_KEY (对应 S3 的 Secret Access Key): 访问密钥 Key,这是高度敏感信息,请勿泄露! 它与 Secret ID 配合使用,用于验证你的请求。
    • COS_REGION (对应 S3 的 Region): 你的存储桶所在的区域。例如,北京区域通常是 ap-beijing。请确保填写插件要求的正确格式。
    • COS_BUCKET (对应 S3 的 Bucket Name): 你创建的用于存储 Obsidian 库文件的存储桶名称。
  • 填写到插件设置中: 将上面获取到的四个值,准确无误地填写到 Remotely Save 插件设置界面中对应的字段里(例如:Endpoint/Server Address 可能需要根据 COS_REGION 和 COS_BUCKET 构造,或者有单独的 Region 和 Bucket 字段,具体请参照插件说明或界面提示)。 配置完成后,可以尝试点击插件设置中的 检查 (Check) 或 同步 (Sync/Save) 按钮,看是否能成功连接并同步少量文件到你的 S3 存储桶。

二、GitHub设置#

现在,我们已经配置好了 Obsidian 通过 Remotely Save 与 S3 云存储同步的通道。接下来,我们需要在 GitHub 上设置一个自动化流程,让它定期从源仓库拉取最新的外刊,并将这些文件推送到我们之前配置好的 S3 存储桶中。

1.Fork源仓库#

我们的外刊内容来源于 hehonghui/awesome-english-ebooks 这个优秀的仓库。但我们不能直接在这个仓库上运行我们自己的自动化任务,也无法直接修改它。因此,我们需要先 Fork 这个仓库

hehonghui
/
awesome-english-ebooks
Waiting for api.github.com...
00K
0K
0K
Waiting...
  • 什么是 Fork? Forking 操作会在你的 GitHub 账户下创建一个源仓库的完整副本。这个副本完全属于你,你可以自由地修改代码、添加文件(比如我们的 Workflow 文件),而不会影响到原始仓库。
  • 如何操作?
  • 结果: 完成后,你的 GitHub 账号下就会出现一个名为 awesome-english-ebooks 的新仓库(地址类似 https://github.com/<你的用户名>/awesome-english-ebooks)。后续所有的操作都将在你 Fork 后的这个仓库中进行。

2.创建Workflow文件:定义自动化任务#

GitHub Actions 的核心是 Workflow 文件。这是一个 YAML 格式的文本文件,用来定义自动化任务的触发条件、执行步骤和所需环境。

  • 存放位置: GitHub Actions 要求 Workflow 文件必须存放在仓库根目录下的 .github/workflows/ 文件夹内。你需要先在你的 Fork 后的仓库 中创建这个目录结构。
  • 创建文件:
    • 方法一 (推荐,通过 GitHub 网页界面):
      1. 进入你 Fork 后的 awesome-english-ebooks 仓库页面。
      2. 点击上方的 Actions 标签页。
      3. 如果仓库还没有 Workflow,GitHub 可能会提示你创建。你可以点击 set up a workflow yourself (或者类似的链接)。
      4. 这会直接带你到创建新文件的界面,路径已经预设为 .github/workflows/。
      5. 如果你采用我的防范,那么你需要创建两个yml文件,分别是sync-upstream.yml和sync-to-obsidian.yml。前者负责自动和上游仓库同步,后者负责推送文件到Obsidian库。
    • 方法二 (本地操作): 不推荐该方法,因为原始仓库过大
      1. 将你 Fork 后的仓库克隆 (clone) 到本地。
      2. 在本地仓库的根目录下创建 .github 文件夹,再在其中创建 workflows 文件夹。
      3. 在 workflows 文件夹内创建一个新的 YAML 文件 (例如 sync-periodicals.yml)。
      4. 稍后编辑完文件内容后,通过 git add, git commit, git push 将这个文件推送到你的 GitHub 仓库。
  • 文件内容: 接下来的一步,我们将在这个 YAML 文件中编写具体的指令,告诉 GitHub Actions 要做什么。现在,你只需要先创建好这个空文件即可。

3.配置安全凭证 (GitHub Secrets):保护你的 S3 访问密钥#

我们的 Workflow 需要访问你的 S3 存储桶(例如腾讯云 COS)才能上传文件。这就需要用到我们在第一步中获取的四个凭证信息 (COS_SECRET_ID, COS_SECRET_KEY, COS_REGION, COS_BUCKET)。

极其重要: 绝对不能 将这些敏感信息直接写在 Workflow 文件 (YAML) 中!因为 Workflow 文件是代码仓库的一部分,会被公开(即使是私有仓库,也存在泄露风险)。

正确的做法是使用 GitHub Secrets。Secrets 是 GitHub 提供的用于存储敏感信息(如 API 密钥、密码、访问令牌等)的功能,它们会被加密存储,并且只在 Action 运行时作为环境变量注入,不会在日志或代码中暴露。

  • 如何设置 Secrets:
    1. 进入你 Fork 后的 awesome-english-ebooks 仓库页面。
    2. 点击上方的 Settings 标签页。
    3. 在左侧导航栏中,找到 Secrets and variables,点击展开,然后选择 Actions。
    4. 在 Repository secrets 部分,点击 New repository secret 按钮。
    5. 你需要 依次创建四个 Secret
      • Name: COS_SECRET_ID
        Secret: 粘贴你之前获取的腾讯云 SecretId 值。
      • Name: COS_SECRET_KEY
        Secret: 粘贴你之前获取的腾讯云 SecretKey 值。 (再次强调,这是最敏感的信息!)
      • Name: COS_REGION
        Secret: 粘贴你的腾讯云 COS 区域,例如 ap-beijing。
      • Name: COS_BUCKET
        Secret: 粘贴你的腾讯云 COS 存储桶名称。
    6. 确保 Secret 名称完全匹配 (COS_SECRET_ID, COS_SECRET_KEY, COS_REGION, COS_BUCKET),因为我们稍后会在 Workflow 文件中通过这些名称来引用它们。仔细检查拼写和大小写。

4.Github Action YAML文件#

当然,你也可以直接忽视下面这一坨文件,直接Fork 我的仓库,并按照上文第三小节的步骤修改自己的环境变量来进行同步。

4.1 定期同步上游仓库:sync-upstream.yml#

这个文件的核心目标是:确保你 Fork 的仓库能定期自动拉取 awesome-english-ebooks 源仓库的最新更新。 这样,当源仓库发布新一期杂志时,你的仓库也能及时获取到。 它的工作流程大致如下:

  1. 定时触发与手动触发 (on):
    • 它被设置为每周五的特定时间(UTC 16:00,对应北京时间周六 00:00)自动运行。这个时间点通常在源仓库更新之后,确保能拉取到最新的内容。
    • 同时,它也允许 workflow_dispatch,意味着你可以随时在 GitHub 仓库的 Actions 页面手动点击运行此流程,方便测试或在非预定时间更新。
  2. 执行环境 (jobs: sync):
    • 指定在最新的 Ubuntu 虚拟机环境中运行。
  3. 关键步骤 (steps):
    • 检出当前仓库 (actions/checkout): 首先,将你 自己 Fork 后的仓库代码下载到运行环境中。使用了 fetch-depth: 1 进行浅克隆,只获取最新版本历史,节省时间和资源。
    • 添加上游仓库 (git remote add upstream… & git fetch upstream…): 将原始的 awesome-english-ebooks 仓库添加为一个名为 upstream 的远程源,并拉取其最新的提交信息(同样使用浅层拉取)。
    • 备份 GitHub Action 文件: 这是一个重要的保险步骤。因为接下来的合并操作可能会覆盖你仓库中的 .github/workflows 文件夹(如果上游仓库恰好也有同名文件),所以这里先把这个文件夹备份到临时目录。
    • 合并上游仓库的更改 (git merge upstream/…): 尝试将从 upstream 拉取下来的最新更改合并到你当前的分支(通常是 master 或 main)。这一步就是将新的杂志文件同步到你的仓库副本中的关键操作。代码中包含了处理潜在分支名差异 (master vs main) 和自动设置提交者信息的逻辑。
    • 恢复 GitHub Action 文件: 将之前备份的 Workflow 文件复制回 .github/workflows 目录,并提交这个更改(如果确实有恢复操作)。这确保了即使合并时上游仓库有冲突或修改,你自己的自动化脚本不会丢失。
    • 推送更改到你的仓库 (git push origin): 最后,将所有本地合并、恢复后的更改推送回你自己的 GitHub 远程仓库。至此,你的 Fork 就包含了最新的外刊文件。 小结:这个 Workflow 的职责是“进货”,它定期检查源头,把最新的杂志文件同步到你的 GitHub 仓库中。 正式代码:
name: 同步上游仓库

on:
  schedule:
    # 每周六0点运行(UTC时间是每周五16:00,对应北京时间24:00,也就是周六00:00)
    - cron: '0 16 * * 5'
  # 允许手动触发工作流
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    
    steps:
      # 检出当前仓库代码(增加深度以获取更完整的历史记录)
      - name: 检出当前仓库
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      
      # 添加上游仓库并获取更完整的历史记录
      - name: 添加上游仓库
        run: |
          git remote add upstream https://github.com/hehonghui/awesome-english-ebooks.git
          git fetch upstream --depth=50
      
      # 备份GitHub Action文件
      - name: 备份GitHub Action文件
        run: |
          mkdir -p /tmp/github-actions-backup
          if [ -d ".github/workflows" ]; then
            cp -r .github/workflows /tmp/github-actions-backup/
          fi
      
      # 检测上游仓库的默认分支
      - name: 检测上游仓库的分支
        id: detect-branch
        run: |
          if git ls-remote --heads upstream main | grep main; then
            echo "UPSTREAM_BRANCH=main" >> $GITHUB_ENV
          else
            echo "UPSTREAM_BRANCH=master" >> $GITHUB_ENV
          fi
          if git rev-parse --verify --quiet main; then
            echo "LOCAL_BRANCH=main" >> $GITHUB_ENV
          else
            echo "LOCAL_BRANCH=master" >> $GITHUB_ENV
          fi
      
      # 合并上游仓库的更改
      - name: 合并上游仓库的更改
        run: |
          # 确保我们在正确的本地分支上
          git checkout ${{ env.LOCAL_BRANCH }}
          
          # 配置Git用户信息(在合并前设置)
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "GitHub Actions"
          
          # 使用--allow-unrelated-histories参数合并上游仓库的更改
          git merge upstream/${{ env.UPSTREAM_BRANCH }} --allow-unrelated-histories -m "同步上游仓库更改" || {
            echo "合并冲突,以上游版本为准"
            git reset --hard upstream/${{ env.UPSTREAM_BRANCH }}
          }
      
      # 恢复GitHub Action文件
      - name: 恢复GitHub Action文件
        run: |
          if [ -d "/tmp/github-actions-backup/workflows" ]; then
            mkdir -p .github/workflows
            cp -r /tmp/github-actions-backup/workflows/* .github/workflows/
            git add .github/workflows
            git commit -m "恢复GitHub Action文件" || echo "没有需要恢复的文件"
          fi
          
      # 推送更改到你的仓库
      - name: 推送更改到仓库
        run: |
          git push origin ${{ env.LOCAL_BRANCH }}


4.2 推送文件到Obsidian库:sync-to-obsidian.yml#

这个文件的核心目标是:在第一个 Workflow 成功同步了最新的外刊文件后,自动找出我们关心的那几本杂志的最新 PDF 文件,并将它们上传到你之前配置好的腾讯云 COS (或其他 S3) 存储桶的指定位置。 它的工作流程设计如下:

  1. 触发条件 (on):
    • 主要触发器 (workflow_run): 它被配置为在上一个 同步上游仓库 Workflow 成功完成后自动触发。这是一种链式反应,确保只有在“进货”成功后,才进行“上架”操作。
    • 手动触发 (workflow_dispatch): 同样允许手动触发,方便调试或单独执行上传操作。
  2. 运行条件 (jobs: sync-to-cos -> if):
    • 增加了一个检查:只有当触发它的 同步上游仓库 工作流结论是 success (成功),或者当它是被手动触发时,这个 Job 才会真正运行。防止在上游同步失败的情况下执行无效的上传。
  3. 关键步骤 (steps):
    • 检出当前仓库 (actions/checkout): 再次检出你仓库的代码。但这次使用了 sparse-checkout (稀疏检出) 的高级功能。它只下载仓库中你明确指定的几个包含外刊的目录(如 01_economist, 02_new_yorker 等),而不是整个仓库。这极大地提高了效率,尤其当仓库历史越来越大时。
    • 安装 AWS CLI: 安装 AWS 命令行工具。虽然我们用的是腾讯云 COS,但 COS 兼容 S3 API,所以可以使用 AWS CLI 来操作。
    • 配置 AWS 凭证: 这是连接到你 S3 存储桶的关键。它在运行环境中动态创建 AWS 配置文件 (~/.aws/credentials 和 ~/.aws/config)。这里安全地使用了你之前在 GitHub Secrets 中存储的 COS_SECRET_ID, COS_SECRET_KEY, COS_REGION 和 COS_BUCKET。注意,这里配置了腾讯云 COS 的特定 endpoint_url,让 AWS CLI 知道要连接的不是 AWS S3 而是腾讯云 COS。你的密钥绝不会暴露在代码或日志中。
    • 获取文件更新日期: 获取形如 YYYY.MM.DD 的刊物更新日期,存为一个变量,用于后续在 S3 中创建按日期组织的文件夹。
    • 列出目录内容: 一个辅助步骤,用于在 Action 日志中打印出检出的目录内容,方便调试时查看文件是否按预期存在。
    • 查找最新的杂志文件 (多个步骤): 针对你关心的每种刊物(经济学人、纽约客、大西洋月刊、连线),使用 shell 命令 (find, sort, head) 来定位其对应目录下最新创建的子目录。脚本假设最新的期刊文件存放在最新命名的子目录中。找到的目录路径会保存为输出变量(如 steps.economist.outputs.latest_dir)。
    • 同步各刊物到 COS (多个步骤): 这是最终的上传环节。
      • 条件执行 (if): 每个上传步骤都先检查上一步是否成功找到了对应的最新目录。
      • 遍历 PDF 文件: 在找到的最新期刊目录中,脚本会遍历查找所有的 .pdf 文件。
      • 上传 (aws s3api put-object): 对找到的每个 PDF 文件,执行 aws s3api put-object 命令进行上传。
        • —bucket: 指定你的 COS 存储桶名称 (来自 Secret)。
        • —key: 这是文件在存储桶中的最终路径和名称。它被精心构造成 2.外刊/<刊物名称>/<当前日期>/<原始文件名>.pdf 的格式。例如 2.外刊/经济学人/2023.10.27/The Economist - October 27 2023.pdf。这个结构化的路径将直接映射到你 Obsidian 中通过 Remotely Save 同步后的文件夹结构,非常清晰。
        • —body: 指定要上传的本地 PDF 文件。
        • —endpoint-url: 再次确保命令指向腾讯云 COS。

小结:这个 Workflow 是“上架”机器人。它在上一个 Workflow 完成后启动,智能地找到你关心的最新 PDF 期刊,然后按照预设的路径结构将它们精准地上传到你的 S3 云存储中,等待 Remotely Save 将它们同步到你的 Obsidian。 正式代码:

name: 同步外刊到Obsidian

on:
  # 在同步上游仓库工作流完成后运行
  workflow_run:
    workflows: ["同步上游仓库"]
    types:
      - completed
  # 允许手动触发
  workflow_dispatch:

jobs:
  sync-to-cos:
    runs-on: ubuntu-latest
    # 只有当触发的工作流成功完成时才运行
    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
    
    steps:
      # 检出当前仓库代码(使用稀疏检出策略,只检出必要的目录结构)
      - name: 检出当前仓库
        uses: actions/checkout@v3
        with:
          fetch-depth: 1
          sparse-checkout: |
            01_economist
            02_new_yorker
            04_atlantic
            05_wired
          sparse-checkout-cone-mode: true
      
      # 安装AWS CLI
      - name: 安装AWS CLI
        uses: unfor19/install-aws-cli-action@v1
        with:
          version: 2
      
      # 配置AWS凭证(用于访问腾讯云COS)
      - name: 配置AWS凭证
        run: |
          mkdir -p ~/.aws
          cat > ~/.aws/credentials << EOF
          [default]
          aws_access_key_id = ${{ secrets.COS_SECRET_ID }}
          aws_secret_access_key = ${{ secrets.COS_SECRET_KEY }}
          EOF
          
          cat > ~/.aws/config << EOF
          [default]
          region = ${{ secrets.COS_REGION }}
          output = json
          s3 =
            addressing_style = virtual
            endpoint_url = https://cos.${{ secrets.COS_REGION }}.myqcloud.com
          EOF
          
          # 验证配置是否生效
          echo "AWS CLI配置完成,当前配置:"
          aws configure list
      
      # 列出目录内容,以便于调试
      - name: 列出目录内容
        run: |
          echo "列出经济学人目录内容:"
          ls -la ./01_economist || echo "经济学人目录不存在"
          echo "列出纽约客目录内容:"
          ls -la ./02_new_yorker || echo "纽约客目录不存在"
          echo "列出Atlantic目录内容:"
          ls -la ./04_atlantic || echo "Atlantic目录不存在"
          echo "列出Wired目录内容:"
          ls -la ./05_wired || echo "Wired目录不存在"
      
      # 查找最新的经济学人文件
      - name: 查找最新的经济学人文件
        id: economist
        run: |
          if [ -d "./01_economist" ]; then
            LATEST_ECONOMIST_DIR=$(find ./01_economist -maxdepth 1 -type d -name "te_*" | sort -r | head -n 1)
            echo "latest_dir=$LATEST_ECONOMIST_DIR" >> $GITHUB_OUTPUT
            
            # 从目录名中提取日期(格式如te_2025.04.05)
            if [ -n "$LATEST_ECONOMIST_DIR" ]; then
              DIR_NAME=$(basename "$LATEST_ECONOMIST_DIR")
              ECONOMIST_DATE=$(echo "$DIR_NAME" | sed 's/te_//')
              echo "date=$ECONOMIST_DATE" >> $GITHUB_OUTPUT
              echo "找到最新的经济学人目录: $LATEST_ECONOMIST_DIR,日期: $ECONOMIST_DATE"
            fi
          else
            echo "经济学人目录不存在"
            echo "latest_dir=" >> $GITHUB_OUTPUT
          fi
      
      # 查找最新的纽约客文件
      - name: 查找最新的纽约客文件
        id: newyorker
        run: |
          if [ -d "./02_new_yorker" ]; then
            LATEST_NEWYORKER_DIR=$(find ./02_new_yorker -maxdepth 1 -type d -mindepth 1 -not -path "*/\.*" | sort -r | head -n 1)
            echo "latest_dir=$LATEST_NEWYORKER_DIR" >> $GITHUB_OUTPUT
            
            # 从目录名中提取日期
            if [ -n "$LATEST_NEWYORKER_DIR" ]; then
              DIR_NAME=$(basename "$LATEST_NEWYORKER_DIR")
              echo "date=$DIR_NAME" >> $GITHUB_OUTPUT
              echo "找到最新的纽约客目录: $LATEST_NEWYORKER_DIR,日期: $DIR_NAME"
            fi
          else
            echo "纽约客目录不存在"
            echo "latest_dir=" >> $GITHUB_OUTPUT
          fi
      
      # 同步经济学人到COS
      - name: 同步经济学人到COS
        if: steps.economist.outputs.latest_dir != ''
        run: |
          echo "配置AWS S3寻址样式"
          aws configure set default.s3.addressing_style virtual
          
          echo "开始同步经济学人目录: ${{ steps.economist.outputs.latest_dir }}"
          
          if [ -d "${{ steps.economist.outputs.latest_dir }}" ]; then
            # 只上传PDF文件
            for file in "${{ steps.economist.outputs.latest_dir }}"/*.pdf; do
              if [ -f "$file" ]; then
                # 提取文件名
                filename=$(basename "$file")
                # 构建S3路径
                s3_key="2.外刊/经济学人/${{ steps.economist.outputs.date }}/$filename"
                
                # 检查文件是否已存在
                echo "检查COS中是否已存在文件: $s3_key"
                if aws s3api head-object --bucket ${{ secrets.COS_BUCKET }} --key "$s3_key" --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com 2>/dev/null; then
                  echo "文件已存在,跳过上传: $s3_key"
                else
                  echo "正在上传PDF文件: $file"
                  # 获取文件大小
                  file_size=$(stat -c%s "$file")
                  echo "文件大小: $file_size 字节"
                  
                  # 直接使用低级命令上传
                  echo "使用s3api上传文件..."
                  aws s3api put-object \
                    --bucket ${{ secrets.COS_BUCKET }} \
                    --key "$s3_key" \
                    --body "$file" \
                    --content-length $file_size \
                    --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com
                  echo "文件上传完成: $s3_key"
                fi
              fi
            done
            echo "已同步经济学人PDF到COS"
          else
            echo "经济学人目录不存在或为空: ${{ steps.economist.outputs.latest_dir }}"
          fi
      
      # 同步纽约客到COS
      - name: 同步纽约客到COS
        if: steps.newyorker.outputs.latest_dir != ''
        run: |
          if [ -d "${{ steps.newyorker.outputs.latest_dir }}" ]; then
            # 只上传PDF文件
            for file in "${{ steps.newyorker.outputs.latest_dir }}"/*.pdf; do
              if [ -f "$file" ]; then
                # 提取文件名
                filename=$(basename "$file")
                # 构建S3路径
                s3_key="2.外刊/纽约客/${{ steps.newyorker.outputs.date }}/$filename"
                
                # 检查文件是否已存在
                echo "检查COS中是否已存在文件: $s3_key"
                if aws s3api head-object --bucket ${{ secrets.COS_BUCKET }} --key "$s3_key" --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com 2>/dev/null; then
                  echo "文件已存在,跳过上传: $s3_key"
                else
                  echo "正在上传PDF文件: $file"
                  # 获取文件大小
                  file_size=$(stat -c%s "$file")
                  echo "文件大小: $file_size 字节"
                  
                  # 直接使用低级命令上传
                  echo "使用s3api上传文件..."
                  aws s3api put-object \
                    --bucket ${{ secrets.COS_BUCKET }} \
                    --key "$s3_key" \
                    --body "$file" \
                    --content-length $file_size \
                    --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com
                  echo "文件上传完成: $s3_key"
                fi
              fi
            done
            echo "已同步纽约客PDF到COS"
          else
            echo "纽约客目录不存在或为空: ${{ steps.newyorker.outputs.latest_dir }}"
          fi
      
      # 同步The Atlantic到COS(如果有更新)
      - name: 同步The Atlantic到COS
        run: |
          if [ -d "./04_atlantic" ]; then
            LATEST_ATLANTIC_DIR=$(find ./04_atlantic -maxdepth 1 -type d -mindepth 1 -not -path "*/\.*" | sort -r | head -n 1)
            if [ -n "$LATEST_ATLANTIC_DIR" ] && [ -d "$LATEST_ATLANTIC_DIR" ]; then
              # 从目录名中提取日期
              DIR_NAME=$(basename "$LATEST_ATLANTIC_DIR")
              ATLANTIC_DATE=$DIR_NAME
              echo "找到最新的大西洋月刊目录: $LATEST_ATLANTIC_DIR,日期: $ATLANTIC_DATE"
              
              # 只上传PDF文件
              for file in "$LATEST_ATLANTIC_DIR"/*.pdf; do
                if [ -f "$file" ]; then
                  # 提取文件名
                  filename=$(basename "$file")
                  # 构建S3路径
                  s3_key="2.外刊/大西洋月刊/$ATLANTIC_DATE/$filename"
                  
                  # 检查文件是否已存在
                  echo "检查COS中是否已存在文件: $s3_key"
                  if aws s3api head-object --bucket ${{ secrets.COS_BUCKET }} --key "$s3_key" --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com 2>/dev/null; then
                    echo "文件已存在,跳过上传: $s3_key"
                  else
                    echo "正在上传PDF文件: $file"
                    # 获取文件大小
                    file_size=$(stat -c%s "$file")
                    echo "文件大小: $file_size 字节"
                    
                    # 直接使用低级命令上传
                    echo "使用s3api上传文件..."
                    aws s3api put-object \
                      --bucket ${{ secrets.COS_BUCKET }} \
                      --key "$s3_key" \
                      --body "$file" \
                      --content-length $file_size \
                      --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com
                    echo "文件上传完成: $s3_key"
                  fi
                fi
              done
              echo "已同步The Atlantic PDF到COS"
            else
              echo "未找到有效的The Atlantic目录"
            fi
          else
            echo "Atlantic目录不存在"
          fi
      
      # 同步Wired到COS(如果有更新)
      - name: 同步Wired到COS
        run: |
          if [ -d "./05_wired" ]; then
            LATEST_WIRED_DIR=$(find ./05_wired -maxdepth 1 -type d -mindepth 1 -not -path "*/\.*" | sort -r | head -n 1)
            if [ -n "$LATEST_WIRED_DIR" ] && [ -d "$LATEST_WIRED_DIR" ]; then
              # 从目录名中提取日期
              DIR_NAME=$(basename "$LATEST_WIRED_DIR")
              WIRED_DATE=$DIR_NAME
              echo "找到最新的连线杂志目录: $LATEST_WIRED_DIR,日期: $WIRED_DATE"
              
              # 只上传PDF文件
              for file in "$LATEST_WIRED_DIR"/*.pdf; do
                if [ -f "$file" ]; then
                  # 提取文件名
                  filename=$(basename "$file")
                  # 构建S3路径
                  s3_key="2.外刊/连线杂志/$WIRED_DATE/$filename"
                  
                  # 检查文件是否已存在
                  echo "检查COS中是否已存在文件: $s3_key"
                  if aws s3api head-object --bucket ${{ secrets.COS_BUCKET }} --key "$s3_key" --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com 2>/dev/null; then
                    echo "文件已存在,跳过上传: $s3_key"
                  else
                    echo "正在上传PDF文件: $file"
                    # 获取文件大小
                    file_size=$(stat -c%s "$file")
                    echo "文件大小: $file_size 字节"
                    
                    # 直接使用低级命令上传
                    echo "使用s3api上传文件..."
                    aws s3api put-object \
                      --bucket ${{ secrets.COS_BUCKET }} \
                      --key "$s3_key" \
                      --body "$file" \
                      --content-length $file_size \
                      --endpoint-url=https://cos.${{ secrets.COS_REGION }}.myqcloud.com
                    echo "文件上传完成: $s3_key"
                  fi
                fi
              done
              echo "已同步Wired PDF到COS"
            else
              echo "未找到有效的Wired目录"
            fi
          else
            echo "Wired目录不存在"
          fi


三、Cloudflare R2 配置#

在方法发布后,很多朋友发现Cloudflare R2没办法简单修改使用,我自己改一晚上bug满头包才发现是因为AWS CLI的较新版本(2.23.0和1.37.0以上)引入了默认校验和行为的修改,和Cloudflare R2的API不兼容才导致没办法直接套用的,令人感慨。

在腾讯云COS脚本的基础上,我将 AWS CLI 版本降级到已知与 Cloudflare R2 兼容的版本 2.22.35,在 在所有的 aws s3 cp 命令中添加了 --checksum-algorithm CRC32 参数,现在运行脚本之后已经可以正常上传了。 效果图: image|565x500

如果你想要使用R2,需要在github仓库里配置四个环境变量

  • R2_ACCOUNT_ID
  • R2_BUCKET_NAME
  • R2_ACCESS_KEY_ID
  • R2_SECRET_ACCESS_KEY

当然,你也可以直接fork我的仓库

最新的支持R2的脚本如下:

name: 同步外刊到Cloudflare R2

on:
  # 在同步上游仓库工作流完成后运行
  workflow_run:
    workflows: ["同步上游仓库"]
    types:
      - completed
  # 允许手动触发
  workflow_dispatch:

jobs:
  sync-to-r2:
    runs-on: ubuntu-latest
    # 只有当触发的工作流成功完成时才运行
    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
    
    steps:
      # 检出当前仓库代码(使用稀疏检出策略,只检出必要的目录结构)
      - name: 检出当前仓库
        uses: actions/checkout@v3
        with:
          fetch-depth: 1
          sparse-checkout: |
            01_economist
            02_new_yorker
            04_atlantic
            05_wired
          sparse-checkout-cone-mode: true
      
      # 安装AWS CLI
      - name: 安装AWS CLI
        uses: unfor19/install-aws-cli-action@v1
        with:
          version: 2.22.35
      
      # 配置AWS凭证(用于访问Cloudflare R2)
      - name: 配置AWS凭证
        run: |
          mkdir -p ~/.aws
          cat > ~/.aws/credentials << EOF
          [default]
          aws_access_key_id = ${{ secrets.R2_ACCESS_KEY_ID }}
          aws_secret_access_key = ${{ secrets.R2_SECRET_ACCESS_KEY }}
          EOF
          
          cat > ~/.aws/config << EOF
          [default]
          region = auto
          output = json
          EOF
          
          # 验证配置是否生效
          echo "AWS CLI配置完成,当前配置:"
          aws configure list
      
      # 列出目录内容,以便于调试
      - name: 列出目录内容
        run: |
          echo "列出经济学人目录内容:"
          ls -la ./01_economist || echo "经济学人目录不存在"
          echo "列出纽约客目录内容:"
          ls -la ./02_new_yorker || echo "纽约客目录不存在"
          echo "列出Atlantic目录内容:"
          ls -la ./04_atlantic || echo "Atlantic目录不存在"
          echo "列出Wired目录内容:"
          ls -la ./05_wired || echo "Wired目录不存在"
      
      # 查找最新的经济学人文件
      - name: 查找最新的经济学人文件
        id: economist
        run: |
          if [ -d "./01_economist" ]; then
            LATEST_ECONOMIST_DIR=$(find ./01_economist -maxdepth 1 -type d -name "te_*" | sort -r | head -n 1)
            echo "latest_dir=$LATEST_ECONOMIST_DIR" >> $GITHUB_OUTPUT
            
            # 从目录名中提取日期(格式如te_2025.04.05)
            if [ -n "$LATEST_ECONOMIST_DIR" ]; then
              DIR_NAME=$(basename "$LATEST_ECONOMIST_DIR")
              ECONOMIST_DATE=$(echo "$DIR_NAME" | sed 's/te_//')
              echo "date=$ECONOMIST_DATE" >> $GITHUB_OUTPUT
              echo "找到最新的经济学人目录: $LATEST_ECONOMIST_DIR,日期: $ECONOMIST_DATE"
            fi
          else
            echo "经济学人目录不存在"
            echo "latest_dir=" >> $GITHUB_OUTPUT
          fi
      
      # 查找最新的纽约客文件
      - name: 查找最新的纽约客文件
        id: newyorker
        run: |
          if [ -d "./02_new_yorker" ]; then
            LATEST_NEWYORKER_DIR=$(find ./02_new_yorker -maxdepth 1 -type d -mindepth 1 -not -path "*/\.*" | sort -r | head -n 1)
            echo "latest_dir=$LATEST_NEWYORKER_DIR" >> $GITHUB_OUTPUT
            
            # 从目录名中提取日期
            if [ -n "$LATEST_NEWYORKER_DIR" ]; then
              DIR_NAME=$(basename "$LATEST_NEWYORKER_DIR")
              echo "date=$DIR_NAME" >> $GITHUB_OUTPUT
              echo "找到最新的纽约客目录: $LATEST_NEWYORKER_DIR,日期: $DIR_NAME"
            fi
          else
            echo "纽约客目录不存在"
            echo "latest_dir=" >> $GITHUB_OUTPUT
          fi
      
      # 同步经济学人到R2
      - name: 同步经济学人到R2
        if: steps.economist.outputs.latest_dir != ''
        run: |
          echo "开始同步经济学人目录: ${{ steps.economist.outputs.latest_dir }}"
          
          if [ -d "${{ steps.economist.outputs.latest_dir }}" ]; then
            # 只上传PDF文件
            for file in "${{ steps.economist.outputs.latest_dir }}"/*.pdf; do
              if [ -f "$file" ]; then
                # 提取文件名
                filename=$(basename "$file")
                # 构建S3路径
                s3_key="2.外刊/经济学人/${{ steps.economist.outputs.date }}/$filename"
                
                # 检查文件是否已存在
                echo "检查R2中是否已存在文件: $s3_key"
                if aws s3api head-object --bucket ${{ secrets.R2_BUCKET_NAME }} --key "$s3_key" --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" 2>/dev/null; then
                  echo "文件已存在,跳过上传: $s3_key"
                else
                  echo "正在上传PDF文件: $file"
                  # 禁用分块上传,使用单一请求上传文件
                  aws configure set s3.multipart_threshold 999GB
                  aws s3 cp "$file" "s3://${{ secrets.R2_BUCKET_NAME }}/$s3_key" \
                    --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" \
                    --no-progress \
                    --checksum-algorithm CRC32
                  echo "文件上传完成: $s3_key"
                fi
              fi
            done
            echo "已同步经济学人PDF到R2"
          else
            echo "经济学人目录不存在或为空: ${{ steps.economist.outputs.latest_dir }}"
          fi
      
      # 同步纽约客到R2
      - name: 同步纽约客到R2
        if: steps.newyorker.outputs.latest_dir != ''
        run: |
          if [ -d "${{ steps.newyorker.outputs.latest_dir }}" ]; then
            # 只上传PDF文件
            for file in "${{ steps.newyorker.outputs.latest_dir }}"/*.pdf; do
              if [ -f "$file" ]; then
                # 提取文件名
                filename=$(basename "$file")
                # 构建S3路径
                s3_key="2.外刊/纽约客/${{ steps.newyorker.outputs.date }}/$filename"
                
                # 检查文件是否已存在
                echo "检查R2中是否已存在文件: $s3_key"
                if aws s3api head-object --bucket ${{ secrets.R2_BUCKET_NAME }} --key "$s3_key" --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" 2>/dev/null; then
                  echo "文件已存在,跳过上传: $s3_key"
                else
                  echo "正在上传PDF文件: $file"
                  # 禁用分块上传,使用单一请求上传文件
                  aws configure set s3.multipart_threshold 999GB
                  aws s3 cp "$file" "s3://${{ secrets.R2_BUCKET_NAME }}/$s3_key" \
                    --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" \
                    --no-progress \
                    --checksum-algorithm CRC32
                  echo "文件上传完成: $s3_key"
                fi
              fi
            done
            echo "已同步纽约客PDF到R2"
          else
            echo "纽约客目录不存在或为空: ${{ steps.newyorker.outputs.latest_dir }}"
          fi
      
      # 同步The Atlantic到R2(如果有更新)
      - name: 同步The Atlantic到R2
        run: |
          if [ -d "./04_atlantic" ]; then
            LATEST_ATLANTIC_DIR=$(find ./04_atlantic -maxdepth 1 -type d -mindepth 1 -not -path "*/\.*" | sort -r | head -n 1)
            if [ -n "$LATEST_ATLANTIC_DIR" ] && [ -d "$LATEST_ATLANTIC_DIR" ]; then
              # 从目录名中提取日期
              DIR_NAME=$(basename "$LATEST_ATLANTIC_DIR")
              ATLANTIC_DATE=$DIR_NAME
              echo "找到最新的大西洋月刊目录: $LATEST_ATLANTIC_DIR,日期: $ATLANTIC_DATE"
              
              # 只上传PDF文件
              for file in "$LATEST_ATLANTIC_DIR"/*.pdf; do
                if [ -f "$file" ]; then
                  # 提取文件名
                  filename=$(basename "$file")
                  # 构建S3路径
                  s3_key="2.外刊/大西洋月刊/$ATLANTIC_DATE/$filename"
                  
                  # 检查文件是否已存在
                  echo "检查R2中是否已存在文件: $s3_key"
                  if aws s3api head-object --bucket ${{ secrets.R2_BUCKET_NAME }} --key "$s3_key" --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" 2>/dev/null; then
                    echo "文件已存在,跳过上传: $s3_key"
                  else
                    echo "正在上传PDF文件: $file"
                    # 禁用分块上传,使用单一请求上传文件
                    aws configure set s3.multipart_threshold 999GB
                    aws s3 cp "$file" "s3://${{ secrets.R2_BUCKET_NAME }}/$s3_key" \
                      --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" \
                      --no-progress \
                      --checksum-algorithm CRC32
                    echo "文件上传完成: $s3_key"
                  fi
                fi
              done
              echo "已同步The Atlantic PDF到R2"
            else
              echo "未找到有效的The Atlantic目录"
            fi
          else
            echo "Atlantic目录不存在"
          fi
      
      # 同步Wired到R2(如果有更新)
      - name: 同步Wired到R2
        run: |
          if [ -d "./05_wired" ]; then
            LATEST_WIRED_DIR=$(find ./05_wired -maxdepth 1 -type d -mindepth 1 -not -path "*/\.*" | sort -r | head -n 1)
            if [ -n "$LATEST_WIRED_DIR" ] && [ -d "$LATEST_WIRED_DIR" ]; then
              # 从目录名中提取日期
              DIR_NAME=$(basename "$LATEST_WIRED_DIR")
              WIRED_DATE=$DIR_NAME
              echo "找到最新的连线杂志目录: $LATEST_WIRED_DIR,日期: $WIRED_DATE"
              
              # 只上传PDF文件
              for file in "$LATEST_WIRED_DIR"/*.pdf; do
                if [ -f "$file" ]; then
                  # 提取文件名
                  filename=$(basename "$file")
                  # 构建S3路径
                  s3_key="2.外刊/连线杂志/$WIRED_DATE/$filename"
                  
                  # 检查文件是否已存在
                  echo "检查R2中是否已存在文件: $s3_key"
                  if aws s3api head-object --bucket ${{ secrets.R2_BUCKET_NAME }} --key "$s3_key" --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" 2>/dev/null; then
                    echo "文件已存在,跳过上传: $s3_key"
                  else
                    echo "正在上传PDF文件: $file"
                    # 禁用分块上传,使用单一请求上传文件
                    aws configure set s3.multipart_threshold 999GB
                    aws s3 cp "$file" "s3://${{ secrets.R2_BUCKET_NAME }}/$s3_key" \
                      --endpoint-url "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" \
                      --no-progress \
                      --checksum-algorithm CRC32
                    echo "文件上传完成: $s3_key"
                  fi
                fi
              done
              echo "已同步Wired PDF到R2"
            else
              echo "未找到有效的Wired目录"
            fi
          else
            echo "Wired目录不存在"
          fi

四、最终效果#

效果 可以看到执行同步后,对应刊物的最新文章就被我们同步到Obsidian的Vault里了。

五、可能的改进优化#

1.更多的同步平台#

我们目前的解决方法是利用 GitHub Actions 将文件推送到 S3 兼容的云存储(如腾讯云 COS),然后依赖 Obsidian 的 Remotely Save 插件进行同步。但每个人的同步习惯和工具链可能不同:

  • OneDrive / Google Drive / Dropbox:

    • Obsidian 端: Remotely Save 插件本身就支持 OneDrive、Dropbox 等多种后端。如果你已经在 Obsidian 中配置了这些服务的同步,那么 Obsidian 这边不需要改动。
    • GitHub Actions 端 (挑战): 关键在于修改 sync-to-obsidian.yml 这个 Workflow。你需要找到或创建一个 GitHub Action,该 Action 能够安全地验证并上传文件到你选择的云盘服务。这通常涉及到:
      • 获取对应云盘服务的 API 访问令牌或使用特定的 CLI 工具 (如 rclone,一个强大的多云存储管理工具,或者针对特定服务的官方/社区 CLI)。
      • 将认证信息(如 API Token)安全地存储在 GitHub Secrets 中。
      • 修改 Workflow 中的上传步骤,使用相应的命令将文件推送到你的云盘指定目录。例如,使用 rclone copy 命令。
    • 优点: 可以直接利用你已有的、可能容量更大或更习惯的云盘服务。
    • 缺点: 需要额外配置 Actions 与特定云盘服务的集成,可能比 S3 稍微复杂一些。
  • Git (例如,使用 Obsidian Git 插件):

    • 思路: 不再需要 S3 作为中间存储。GitHub Actions 直接将下载到的最新外刊 PDF 文件提交 (commit) 并推送 (push) 到一个专门用于同步 Obsidian Vault 的 Git 仓库(或者就是你的 Vault 本身如果它是一个 Git 仓库)。
    • Obsidian 端: 你需要在 Obsidian 中安装并配置 Obsidian Git 插件,让它定期自动 pull 这个 Git 仓库的更新。
    • GitHub Actions 端: 修改 sync-to-obsidian.yml Workflow:
      • 移除所有 AWS CLI 和 S3 相关的配置和上传步骤。
      • 在检出 awesome-english-ebooks 内容后,将找到的最新 PDF 文件复制到 另一个 Git 仓库(你的 Obsidian Vault 仓库)的工作目录中,或者直接复制到当前仓库的某个指定目录下(如果打算用同一个仓库混合管理)。
      • 添加 Git 命令步骤 (git add, git commit, git push) 来提交这些新文件。需要配置好推送权限,可能需要使用 Personal Access Token (PAT) 或 SSH 密钥,并存储在 Secrets 中。
    • 优点: 完全基于 Git,对于熟悉 Git 的用户来说可能更自然;减少了对 S3 的依赖。
    • 缺点:
      • 仓库大小: Git 不擅长处理大型二进制文件。频繁添加 PDF 可能导致 Git 仓库迅速膨胀,克隆和拉取变慢。可能需要结合使用 Git LFS (Large File Storage) 来管理这些 PDF 文件,但这会增加设置的复杂性。
      • 冲突处理: 如果你在本地也修改了 Vault,自动 pull 可能会遇到合并冲突,需要手动解决。

2.AI加成下的文件处理#

  • 我们还可以在 GitHub Actions 中集成 pandoc 等工具(或者使用类似于Mistral OCR一类的工具),尝试将 EPUB 或甚至 PDF 转换为 Markdown 格式。这样可以直接在 Obsidian 中编辑、链接和引用文本内容,而不是仅仅处理一个附件。但这非常复杂,因为格式转换(尤其是 PDF)往往会丢失布局和图片,效果可能不理想,需要大量调整和测试。
  • 可以尝试在 Action 中解析文件名或文件内容(如果可能),提取期刊名称、期号、日期等元数据。然后,除了上传原始文件,还可以自动生成一个对应的 Markdown 笔记文件(.md),包含这些元数据、指向原始 PDF/EPUB 文件的链接以及可能的标签(如 #外刊 #经济学人 #待读)。这将极大地提升在 Obsidian 中的组织和可发现性。这可能需要编写更复杂的脚本(如 Python 脚本)并在 Action 中运行。

说实话我觉得这样不如自己notion开一个database,确实没必要自己硬在Obsidian里实现一个徒增维护成本的数据库

通过Github Action实现自动推送英文外刊到Obsidian中
https://www.lapis.cafe/posts/technicaltutorials/github-action-obsidian/
作者
时歌
发布于
2025-04-10
许可协议
CC BY-NC-SA 4.0