Towards Data Science

I Tried to Schedule My ETL Pipeline. Here’s What I Didn’t Expect.

8.5内容质量

TL;DR · AI 摘要

ETL 管道的调度问题实际上源于环境依赖,而非工具选择,需通过配置环境变量实现可移植性。

核心要点

  • 硬编码路径导致 ETL 管道无法在 Colab 外运行。
  • 通过环境变量配置数据库路径可提高可移植性。
  • 调度问题的核心是环境依赖,而非工具选择。

结构提纲

按章节快速跳转。

  1. 作者在数据工程学习过程中,尝试构建 ETL 管道并遇到了调度问题。

  2. 作者发现 ETL 管道无法在 Colab 外运行,问题源于硬编码路径。

  3. 通过使用环境变量配置数据库路径,提高代码的可移植性。

  4. 调度问题的核心是环境依赖,而非工具选择。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • ETL 管道调度问题
    • 环境依赖问题
      • 硬编码路径
      • 解决方案:环境变量配置
    • 调度问题的本质
      • 环境依赖,而非工具选择

金句 / Highlights

值得收藏与分享的关键句。

#ETL#数据工程#调度#环境配置
打开原文

我尝试安排我的ETL流水线。这是我没想到的。 | Towards Data Science

数据工程

我尝试安排我的ETL流水线。这是我没想到的。

我原本以为这是一个调度问题,结果却发现首先是一个可移植性问题。

Ibrahim Salami

2026年6月19日

8分钟阅读

分享

由Gemini AI生成

在我上一篇文章中,我提到调度是我接下来要面对的下一个障碍。

所以,我想我确实在这里,正朝它走去。

但在进入发生了什么之前,让我为第一次偶然看到这篇文章的人提供一些背景信息。

我是一名系统分析师,决定转向数据工程。我没有仅仅参加课程和收集证书,而是决定通过构建和公开撰写来学习。这个系列中的每一篇文章都记录了我实际构建的东西、我做出的决定、出现的问题以及我从中学到的内容。

第一篇文章是我的12个月自学路线图,其中我详细说明了我如何计划进行这次转型。第二篇是我在完全没有任何经验的情况下,使用GitHub API从头开始构建我的第一个ETL流水线。在第三篇中,我使用SQLite存储、幂等性处理和Google Drive持久性(全部在Google Colab中)使该流水线更加适合生产环境。

这篇文章是第四篇,它从上一篇文章结束的地方继续。

我原本预计大部分时间将用于选择一个调度工具并进行配置。但我没想到的是,在我甚至考虑调度之前,我必须处理一个更基本的问题。我的流水线无法在Google Colab之外运行。在这一点改变之前,世界上任何调度器都无法帮助我。

这就是实际发生的故事。

第一堵墙:我的流水线存在于Colab中

在我甚至开始调度之前,我想了解实际需要什么才能自动运行我的流水线。因此,我带着这个问题,第一次仔细查看了我的代码。

加载部分的代码如下所示:

code
conn = sqlite3.connect('/content/drive/MyDrive/github_repos.db')

这个路径,/content/drive/MyDrive/,只存在于Google Colab中。它是Colab在你将Drive连接到笔记本时提供的挂载Google Drive路径。在Colab之外,这个路径不存在。如果任何调度器尝试运行这个脚本,它会在那里崩溃。

有趣的是,我的代码中没有任何google.colab的导入。没有特定于Colab的库。只有一个我之前没有真正思考就一直输入的硬编码路径。这个路径是依赖项,而不是代码本身。

这是我没想到的第一件事。我以为挑战将是学习一个调度工具。但第一个教训是,我的环境是流水线的一部分,而我却没有注意到。

修复方法很简单。而不是硬编码Colab路径,我通过环境变量使数据库路径可配置:

code
import os

DB_PATH = os.environ.get('DB_PATH', 'github_repos.db')
conn = sqlite3.connect(DB_PATH)

现在,脚本使用环境变量中设置的任何路径。如果没有设置任何路径,它会回退到在同一文件夹中创建一个本地的github_repos.db文件。一次更改,流水线就不再依赖于Colab。

第一次在Colab之外运行它

在设置任何调度器之前,我想先确认这个脚本本身确实可以独立运行。所以我把它保存为 pipeline.py,并创建了一个 requirements.txt 文件,其中包含它所需的两个库:

code
requests
pandas

然后我从终端运行它:

它打印出:Pipeline complete. Duplicates handled.

并且我的文件夹中出现了一个名为 github_repos.db 的文件。我之前在 Colab 中运行的相同管道现在作为一个普通的 Python 脚本运行,可以在任何地方运行。

这比我预期的要重要得多。不是因为这个改变很复杂,它其实并不复杂。而是因为我意识到我之前一直把我的管道当作一个笔记本来看待,而实际上我拥有的是一个恰好存在于笔记本中的脚本。

选择一个调度工具

到目前为止,我有了一个独立的脚本。现在我需要一个能够在预定时间运行它的工具。

我查看了几个选项。APScheduler 允许你在 Python 代码中定义调度,这在会话运行时有效,但一旦你关闭终端,它就会停止。这并不是真正的调度,它只是一个循环。Airflow 是用于管道编排的行业标准工具,但它需要运行一个服务器、一个元数据数据库和一个网页界面。对于我现在所处的阶段来说,这需要太多基础设施。

GitHub Actions 处于中间位置。它是免费的,它在 GitHub 的服务器上运行,调度在代码中定义,并且不需要我维护任何基础设施。权衡是,它是为 CI/CD 工作流设计的,而不是管道编排,因此在处理复杂依赖和监控方面有一些限制。但对于我目前阶段的管道来说,它是一个实际的选择。

我也想诚实地说:像 Airflow 这样的工具之所以存在是有原因的。当管道变大时,当任务之间有依赖关系时,当需要了解哪些任务运行了、哪些失败了时,你需要正确的编排。GitHub Actions 并不是这样的工具。但它是一个很好的第一步,理解它的限制是学习那些更严肃的工具真正解决的问题的一部分。

设置 GitHub Actions

GitHub Actions 通过工作流文件来工作,这些文件是 YAML 文件,你将它们放在仓库中的特定文件夹中。文件夹结构如下所示:

code
github-etl/
├── .github/
│   └── workflows/
│       └── schedule.yml
├── pipeline.py
└── requirements.txt

这是我创建的完整工作流文件:

code
name: Run ETL Pipeline

on:
  schedule:
    - cron: '0 9 * * *'
  workflow_dispatch:

jobs:
  run-pipeline:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run pipeline
        run: python pipeline.py

让我来逐步解释每个部分的作用。

  • cron: '0 9 * * *' 是实际的调度。Cron 是一种基于时间的作业调度格式,几十年来一直用于 Unix 系统。五个值分别代表分钟、小时、月中的日期、月份和周中的日期。因此,0 9 * * * 表示:每天的 9 点 0 分,每个月,每周的每一天。换句话说,就是每天 UTC 时间的 9 点。
  • workflow_dispatch 添加了一个手动触发器。这意味着你也可以通过点击 GitHub 中的一个按钮来运行工作流,而不需要等待预定时间。这对于测试非常有用。
  • runs-on: ubuntu-latest 告诉 GitHub 为每次运行启动一台新的 Linux 机器。每次工作流触发时,GitHub 都会创建一个干净的环境,安装你的依赖项,运行你的脚本,然后关闭所有内容。没有一台持久运行你代码的机器在某处运行。它是短暂的。

步骤非常直接。Checkout 从仓库中将代码拉取到运行器中。Setup Python 安装你指定的版本。Install dependencies 运行 pip install -r requirements.txt。然后 Run pipeline 执行你的脚本。

当我运行它时发生了什么

将工作流文件推送到 GitHub 后,我前往仓库的 Actions 标签页,并使用 workflow_dispatch 按钮手动触发它。

它运行了。从开始到结束用了二十七秒。该管道从 GitHub API 拉取数据,对其进行转换,并将其加载到 SQLite 中,所有操作都在 GitHub 服务器上完成,我只需点击按钮后就无需再做任何事情。

在第一次运行时,我确实收到了一个警告:

code
Node.js 20 actions 已弃用...

这是因为我使用了较旧版本的 checkout 和 setup-python 操作。修复方法是将 actions/checkout@v3 更新为 actions/checkout@v4,将 actions/setup-python@v4 更新为 actions/setup-python@v5。在完成这些更新后,工作流运行得干净无误。

我真正学到的东西

在开始之前,我认为调度是关于选择合适的工具。但事实上,调度让我不得不思考一个我之前没有认真考虑过的问题:可移植性。

一个只能在特定环境中运行的管道并不是真正的管道。它只是一个与平台绑定的脚本。使其可调度意味着首先要使其可移植,而使其可移植意味着理解它实际上依赖于什么。

硬编码的路径是一件小事。但发现它改变了我对编写管道代码的思考方式。每次我编写路径、凭证或特定于环境的值时,我现在都会问自己,这个东西是否会在我构建的上下文之外存在。

我学到的另一件事是,调度和编排是两个不同的问题。GitHub Actions 在调度方面处理得很好。但它无法处理诸如使用退避机制重试失败运行、在出现问题时发出警报、可视化管道依赖关系或管理相互依赖的多个管道等任务。这些是编排问题,而它们正是 Airflow 等工具所构建来解决的。

我还没有达到那个层次。但我现在比以前更清楚地理解了这些工具存在的原因。

接下来要做什么

现在,该管道每天上午 9 点 UTC 时间运行一次。数据正在被收集。我开始注意到一些事情:当你每天运行一个管道时,你会以不同的方式开始关注它所产生的数据。

所有记录都是干净的吗?有没有仓库因为缺少字段而被遗漏?病毒标志是否真的有意义,还是我以某种方式定义它,使得几乎所有内容都为“否”?

这些都是数据质量的问题。它们是我接下来要面对的下一个障碍。

这是我的系列文章的一部分,记录了我从系统分析师向数据工程师的转变过程。如果你一直关注,感谢你的支持。如果你是第一次阅读这个系列的文章,前面的文章链接如下。

从数据分析师到数据工程师:我的12个月自学路线图

我作为完全的新手构建了我的第一个ETL管道。这是我的经验分享。

我以为数据工程只是编写脚本。我错了。

在 LinkedIn、YouTube 和 Twitter 上关注我。

作者

查看 Ibrahim Salami 的所有文章

数据工程师

,

数据管道

数据科学

ETL

Google Colab

分享这篇文章

  • 在 Facebook 上分享
  • 在 LinkedIn 上分享
  • 在 X 上分享

Towards Data Science 是一份社区出版物。提交你的见解,以触达全球受众,并通过 TDS 作者支付计划获得报酬。

将 href 更新为你的实际提交 URL

为 TDS 写作

✦ 结束 CTA ✦