前一個 Stage,我們用少量樣本進行資料剖析(Data Profiling),確認臺北捷運 OD 資料的結構、資料粒度與可能存在的品質問題。接下來要進入 Stage 2,建立一條從政府資料來源、Amazon S3 到 PostgreSQL 的資料管線(Data Pipeline)。這一篇的目標不是把資料送進 PostgreSQL,而是先建立一個能長期保留原始檔案、需要時可以重新處理的起點Raw Data Layer(原始資料層)。
政府資料來源
↓
Amazon S3---Raw Data Layer
↓
PostgreSQL---結構化資料
開發初期把少量 CSV 放在本機 data/ 很方便,但臺北捷運 OD 資料每月都有數百 MB。如果資料管線未來要定期執行,原始資料不能只存在某位開發者的電腦。這不只是硬碟空間問題。假設資料已經載入 PostgreSQL,後來才發現:
如果沒有保留當時的原始檔案,就只能再次向政府網站下載。但我們無法保證來源檔案永遠存在,也無法保證相同網址未來仍提供完全相同的內容。因此,Raw Data Layer 的重點是保留一份尚未修改的原始資料,讓下游流程出錯時可以重新執行。
原始檔案保留在 S3
↓
載入或轉換出錯
↓
修正程式
↓
從同一份原始檔案重新處理
Amazon S3 是一種物件儲存服務(Object Storage Service),適合保存 CSV、JSON、Parquet、圖片等非結構化檔案。它不依賴某台特定電腦,也能用一致的 Key 組織不同年份與月份的資料。Amazon S3、Google Cloud Storage 與 Azure Blob Storage 都能滿足這類需求。本專案選擇 Amazon S3,原因是它能透過 IAM 控制權限,並能使用 Boto3 與 Python 整合。S3 不是唯一答案,而是符合目前需求的實作選擇。Google Drive 雖然也能放檔案,但主要用途是人員共享與文件管理,不適合作為資料管線的原始資料層。
S3 和 PostgreSQL 解決的問題不同。
Amazon S3:保存從來源取得、尚未修改的原始 CSV
PostgreSQL:保存已有欄位結構、資料型別與查詢需求的資料
PostgreSQL 裡的資料是「載入結果」,S3 裡的 CSV 是「重新處理的起點」。只保留 PostgreSQL 的資料,一旦載入邏輯或資料模型有誤,就可能失去重新處理的依據。至於 PostgreSQL 的資料表如何設計,以及如何載入大型 CSV,會在後面的文章正式進入 Load 流程時說明。
決定使用 S3 後,先認識四個概念:
這個專案將 2026 年 6 月的資料存成:ridership/year=2026/202606.csv在 AWS Console 裡看起來很像資料夾:
ridership/
└── year=2026/
└── 202606.csv
但 S3 不是傳統檔案系統。對 S3 來說:
完整 Key:ridership/year=2026/202606.csv
Prefix:ridership/
Prefix:ridership/year=2026/
固定的 Key 規則很重要,因為之後 Python 只要取得 year 與 month,就能推導出檔案位置,不需要人工尋找。
Bucket 建立好後,本機 Python 程式仍不能直接讀寫資料。S3 預設不應公開,因此 AWS 必須先回答三個問題:
誰提出請求?
可以執行什麼操作?
可以操作哪些資源?
本專案仍在本機開發,所以建立一個專用 IAM User,讓資料管線用這個身分存取 S3。其中有兩個容易混淆的東西:
Access Key
↓
辨識 IAM User
↓
IAM Policy
↓
判斷是否允許操作指定資源
Access Key 本身不是權限。即使有 Access Key,IAM Policy 沒有允許某項操作,請求仍會被拒絕。
最簡單的做法是直接授予 AmazonS3FullAccess,但這會讓程式取得遠超過目前需求的權限。本專案採用最小權限原則(Principle of Least Privilege):**只提供完成工作所需的最少權限。**目前 Pipeline 需要三個 S3 操作:
s3:ListBucket # 列出指定 Prefix 下的物件
s3:GetObject # 讀取物件
s3:PutObject # 上傳物件
物件的讀寫範圍也只開放到本專案使用的路徑:arn:aws:s3:::<bucket-name>/ridership/*這代表程式可以讀寫 ridership/ 下的捷運資料,但不能任意操作其他 Bucket 或不相關的 Object。
以下會說明完整的設定與驗證流程,但不會逐一使用截圖帶大家操作。這個系列會把重點放在每項設定背後的原因,以及它在資料管線中的作用,而不是特定版本的介面位置。如果實際操作時仍覺得需要圖片引導,歡迎在留言區告訴我;我會再依需求整理一篇獨立的圖文操作說明,並將連結補充在留言。雲端端的設定都在 AWS Management Console 完成,順序如下:
1. 建立 S3 Bucket
2. 設定 Bucket 不公開
3. 建立專用 IAM User
4. 建立最小權限 IAM Policy
5. 為 IAM User 建立 Access Key
建立 Bucket 時,本專案採用以下設定:
這些選項不是開得越多越完整,而是要**根據需求選擇。**接著在 IAM 建立專用 User 與 Policy,將操作限制為 ListBucket、GetObject、PutObject,資源限制在指定 Bucket 的 ridership/ Prefix。最後為這個 IAM User 建立 Access Key,供本機 AWS CLI 與 Boto3 識別身分。
Access Key 不能寫進 Python 程式,也不能提交到 GitHub。本專案透過 AWS CLI 將憑證保存在本機,再由 Boto3 的標準憑證機制讀取。AWS CLI 是獨立的命令列工具,不是本專案的 Python 套件,因此不使用 pip install。Windows 安裝方式可參考 AWS 官方文件。
安裝後重新開啟 PowerShell,確認指令可用:aws --version
看到 aws-cli/2 開頭的版本資訊後,執行:aws configure
依序輸入 Access Key ID、Secret Access Key、預設 Region 與輸出格式。這些實際值不應出現在文章、程式碼或 Git Repository 中。
Bucket 名稱則放在專案根目錄的 .env S3_BUCKET=<你的 bucket 名稱>.env 已加入 .gitignore;RepO只保留不含真實值的 .env.example。
如果未來將 Pipeline 部署到 EC2、ECS 或其他 AWS 運算服務,就不應把本機 Access Key 一起搬上去,而應改用 IAM Role 與臨時憑證。
設定完成不代表一定正確。我們要驗證兩件事:
先建立一個不含敏感資料的測試檔案:Set-Content -Path .\s3_test.txt -Value "S3 permission test"
aws sts get-caller-identity 這一步驗證身分(Authentication),AWS 是否知道請求者是誰。輸出的 ARN 應對應到本專案建立的 IAM User。
列出 ridership/ Prefix:aws s3 ls s3://<bucket-name>/ridership/
上傳測試檔案:aws s3 cp .\s3_test.txt s3://<bucket-name>/ridership/s3_test.txt
讀取物件資訊:
aws s3api head-object `
--bucket <bucket-name> `
--key ridership/s3_test.txt
這些操作成功,代表 IAM User 能在授權範圍內列出、上傳與讀取 Object。
只測成功還不夠。Policy 規定物件只能寫入 ridership/*,因此刻意嘗試上傳到未授權路徑:aws s3 cp .\s3_test.txt s3://<bucket-name>/other/s3_test.txt
預期結果AccessDenied,這次失敗才是正確結果,因為它證明 Policy 不只允許正確路徑,也確實阻止範圍外的寫入。這是在驗證 Authorization(授權):目前身分被允許執行哪些操作。
回到一開始的架構:
政府資料來源
↓
Amazon S3--Raw Data Layer← 今天完成
↓
PostgreSQL
今天完成的是:
目前資料仍是手動上傳,還沒有真正從政府來源自動進入 S3。
下一篇會把擷取與上傳流程寫進 extract.py,讓 Python 根據年月取得政府 CSV,直接串流上傳到 S3,並驗證來源檔案是否完整寫入。
Day 7|Stage 2(2/5):資料上傳 S3 就算成功了嗎?建立原始資料驗證(Raw Validation)