Skip to content

Daily News Refresh 工作流深度拆解

归类:DevOps / GitHub Actions / news-base
日期:2026-04-07
状态:✅ 已优化落地


一、背景

news-base 是一个基于 VitePress 的新闻聚合站点,需要每日自动完成以下链路:

GitHub Actions 定时触发
  → pnpm crawl(抓取新闻 JSON)
  → pnpm build(生成静态站点)
  → git commit + push(提交数据产物)
  → Cloudflare Pages 自动部署

二、最终工作流全文(带注释)

yaml
name: Daily News Refresh

on:
  workflow_dispatch:
  schedule:
    # UTC 22:00 = 北京时间 06:00,提前 3h 触发,补偿免费 Runner 队列延迟
    # 预期实际执行时间:北京时间 09:00 左右
    - cron: '0 22 * * *'

# 防止同名工作流并发执行,避免重复抓取与冲突提交
concurrency:
  group: daily-news-refresh
  cancel-in-progress: false

permissions:
  contents: write
  issues: write

jobs:
  refresh-news:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      # 提前迁移 Actions JS 运行时到 Node.js 24,消除 Node.js 20 弃用警告
      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'   # 读取 .nvmrc,当前为 22.22.0

      - name: Enable Corepack
        run: |
          corepack enable
          corepack prepare pnpm@9.15.9 --activate
          pnpm --version

      # 获取 pnpm store 路径,供缓存 key 使用
      - name: Get pnpm store directory
        id: pnpm-cache
        run: echo "store=$(pnpm store path --silent)" >> $GITHUB_OUTPUT

      # 缓存 pnpm store,命中时 install 可从几十秒降至几秒
      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache.outputs.store }}
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      # 抓取失败时自动重试,最多 3 次,每次间隔 60s,应对网络抖动或源站限流
      - name: Refresh news data (with retry)
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 10
          max_attempts: 3
          retry_wait_seconds: 60
          command: pnpm run crawl

      - name: Build site
        run: pnpm run build

      - name: Commit refreshed API data
        run: |
          if git diff --quiet -- data/daily public/api docs/public; then
            echo "No API changes to commit."
            exit 0
          fi

          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add data/daily public/api docs/public
          git commit -m "chore: refresh daily news $(date +'%Y-%m-%d')"
          git push origin HEAD:main

      # 任意步骤失败后,通过创建 GitHub Issue 留存故障记录
      - name: Create failure issue on error
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            const { data: issues } = await github.rest.issues.listForRepo({
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['ci-failure'],
              state: 'open',
            });
            const today = new Date().toISOString().slice(0, 10);
            const exists = issues.some(i => i.title.includes(today));
            if (exists) return;

            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `[CI 失败] Daily News Refresh 工作流异常 ${today}`,
              body: [
                '## 工作流执行失败',
                `- **Run**: [#${context.runNumber}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`,
                `- **触发方式**: ${context.eventName}`,
                `- **时间 (UTC)**: ${new Date().toISOString()}`,
                '请检查 Actions 日志排查原因。',
              ].join('\n'),
              labels: ['ci-failure'],
            });

三、每步设计意图

Step 1:actions/checkout@v4

  • fetch-depth: 0:保留完整 git 历史,避免后续 git push 时出现 shallow clone 错误。

Step 2:actions/setup-node@v4

  • 使用 node-version-file: '.nvmrc' 读取项目声明版本,与本地保持一致。
  • 不要硬编码版本号(如 node-version: '22'),升级时只需改 .nvmrc

Step 3:Enable Corepack

  • corepack 是 Node.js 官方 pnpm/yarn 版本管理工具,不需要额外 install pnpm。
  • pnpm@9.15.9 --activate 确保版本与 packageManager 字段一致。

Step 4~5:pnpm 缓存

  • 通过 pnpm store path 获取缓存目录(不同系统路径不同,不能硬编码)。
  • hashFiles('**/pnpm-lock.yaml') 确保依赖变化时自动失效旧缓存。

Step 6:pnpm install --frozen-lockfile

  • --frozen-lockfile:CI 环境中不允许更新 lock 文件,发现不一致直接报错,避免隐式依赖变更。

Step 7:Crawl with retry

  • 新闻源稳定性参差不齐,网络抖动会导致偶发失败。
  • 3 次重试 + 60s 间隔可覆盖绝大多数临时限流场景。

Step 8:Build site

  • pnpm run build 触发 VitePress 静态站点构建,生成 dist/

Step 9:Commit(条件提交)

  • git diff --quiet 先检查是否有实际变化,无变化直接退出,避免无意义提交。
  • $(date +'%Y-%m-%d') 在 commit message 中附加日期,便于 git log 定位。
  • git push origin HEAD:maingit push 更稳健,明确推送目标分支。

Step 10:失败 Issue 通知

  • if: failure() 在任意前置步骤失败时才触发。
  • 查重逻辑避免同一天重复创建 Issue(如重试多次后都失败)。
  • 使用内置的 GITHUB_TOKEN,无需额外配置 Secrets。

四、遇到的问题与修复

问题 1:实际执行时间远晚于预期

现象:cron 设 10 1 * * *(北京 09:10),实际执行为 12:08~12:17,延迟约 3 小时。

原因:GitHub 免费共享 Runner 在流量高峰时排队长达 2~3 小时。

修复:将 cron 提前至 0 22 * * *(北京 06:00),以补偿队列延迟,使实际执行目标为北京 09:00。

问题 2:Node.js 20 弃用警告

现象

Node.js 20 actions are deprecated. actions/checkout@v4, actions/setup-node@v4
will be forced to run with Node.js 24 by default starting June 2nd, 2026.

原因:Actions 内部 JS 运行时(与项目 Node 版本无关)使用了 Node.js 20。

修复

  1. 在 job 级添加 FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true 环境变量
  2. .nvmrc20.19.0 升级为 22.22.0(与本地实际版本对齐)
  3. 更新 package.jsonengines.node22.x

问题 3:push 被远端拒绝

现象

! [rejected] main -> main (fetch first)
error: failed to push some refs

原因:本地有未同步的本地提交,而远端(GitHub Actions 自动提交)已新增 commit。

修复

bash
git pull --rebase origin main
git push origin main

Rebase 模式比 merge 更干净,不会产生多余的 merge commit。


五、预防建议

  1. 定时任务 cron 预留缓冲时间:免费 Runner 排队不可控,目标执行时间前至少预留 3 小时。
  2. 依赖缓存是基础优化:pnpm/npm store 缓存可以把 install 时间从 30s+ 降至秒级,几乎零成本。
  3. 网络密集步骤一定要加重试:爬虫类工作流必须加 retry,否则偶发失败率会很高。
  4. 用条件提交代替无条件提交git diff --quiet 先检查,无变化不 commit,避免产出无意义的 git 历史。
  5. 使用 FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 提前适配:不要等到 GitHub 强制切换再处理,届时可能有 breaking change。