電腦關機也能跑!用 GitHub Actions 免費託管你的股票監控機器人

2025-12-30 02:56 | By justin | python Playwright Pandas discord github actions
(Updated: 2025-12-30 02:56)

電腦關機也能跑!用 GitHub Actions 免費託管你的股票監控機器人

各位好!

歡迎來到我們「股票監控機器人」系列的最終章。

回顧一下我們做到了什麼: 1. 用 Playwright 抓取了元大官網的 0050 成分股資料。 2. 用 Python 程式邏輯 清洗並整理了資料。 3. 用 Discord Webhook 將精簡的報表推送到你的手機。

現在這套系統很完美,但它還有一個最大的缺點:你的電腦必須開著,程式才會跑。 如果你筆電蓋起來,或是出門旅遊,這隻爬蟲就掛了。這不符合我們「懶人自動化」的極致精神!

今天我們要用 GitHub Actions 來解決這個問題。它提供每個月 2000 分鐘的免費額度,對於我們這種每天跑幾秒鐘的小爬蟲來說,根本用不完!


為什麼要用 GitHub Actions?

  • 完全免費:對於個人專案或輕量級自動化任務,幾乎不需要花任何費用。
  • 無需伺服器:你不需要租用 VPS 或學習複雜的雲服務。只要有 GitHub 帳號即可。
  • 排程功能:內建 Cron Job 排程,可以設定每天、每週、每月固定時間自動執行。
  • 穩定可靠:由 GitHub 提供的雲端環境,網路穩定,不用擔心自家網路斷線。

專案結構與程式碼準備

首先,在你的電腦上建立一個資料夾(例如 stock_monitor),然後將以下程式碼依序放入對應的檔案中。

1. 核心程式碼:main.py

這個檔案包含爬蟲、資料處理和發送 Discord 訊息的所有邏輯。注意 DISCORD_WEBHOOK_URL 已經改成從環境變數讀取,這是安全性的考量。

# main.py
import os
import requests
import time
from playwright.sync_api import Playwright, sync_playwright

# 從環境變數讀取 Discord Webhook 網址
# 在本機測試時,你可以在終端機設定環境變數,或暫時貼上網址測試(上傳 GitHub 前記得刪掉!)
DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL")

def send_discord_message(content):
    """發送訊息到 Discord"""
    if not DISCORD_WEBHOOK_URL:
        print("錯誤: 未設定 DISCORD_WEBHOOK_URL 環境變數,無法發送訊息。")
        # 為了避免在 CI/CD 環境因為這個錯誤而直接中斷,這裡可以選擇 return 或 raise
        # 這裡選擇 return,讓 GitHub Actions 的 Job 能夠完成,但會有錯誤訊息
        return

    data = {
        "content": content,
        "username": "0050 監控機器人" # 你可以隨意改名
    }

    try:
        response = requests.post(DISCORD_WEBHOOK_URL, json=data)
        if response.status_code == 204: # 204 No Content 代表成功發送
            print("訊息已成功發送至 Discord")
        else:
            print(f"發送失敗: HTTP {response.status_code} - {response.text}")
    except Exception as e:
        print(f"發送時發生錯誤: {e}")

def run(playwright: Playwright) -> None:
    print("啟動爬蟲...")
    # 啟動瀏覽器 (在 GitHub Actions 上必須是 headless=True)
    browser = playwright.chromium.launch(headless=True)
    page = browser.new_page()

    try:
        # 1. 前往元大官網
        print("前往元大官網...")
        page.goto("https://www.yuantaetfs.com/product/detail/0050/ratio")

        # 2. 處理彈窗 (點擊確定)
        # 使用 try/except 處理彈窗,如果沒出現也不會報錯
        try:
            btn = page.get_by_role("button", name="確定")
            if btn.is_visible(timeout=5000): 
                btn.click()
            else:
                print("Info:彈窗未出現跳過。")
        except Exception:
            print("Info: 彈窗處理失敗或未出現,繼續執行。")

        print("點擊展開按鈕...")
        page.get_by_text("展開").click()

        # 4. 等待表格內容出現後,抓取表格文字
        print("抓取表格資料...")
        page.wait_for_selector("div:nth-child(3) > .each_table")
        raw_text = page.locator("div:nth-child(3) > .each_table").all_inner_texts()[0]

        print("正在整理報表數據...")
        lines = raw_text.strip().split('\n')

        # 準備 Markdown 格式的 Discord 訊息
        message_lines = [
            f"**0050 前十大成分股權重監控**", 
            f"**更新時間**: {time.strftime('%Y-%m-%d %H:%M:%S')}",
            "```text" # Discord 的程式碼區塊,用於等寬字體排版
        ]
        # 表頭
        message_lines.append(f"{'代號':<6} {'名稱':<6} {'權重(%)':>8}")
        message_lines.append("-" * 24) # 分隔線

        count = 0
        for line in lines:
            parts = line.split()
            # 過濾邏輯:只要有 4 個欄位,且第一個欄位(代碼)是純數字
            if len(parts) == 4 and parts[0].isdigit():
                code, name, _, weight = parts
                # 格式化字串:<6 靠左對齊佔 6 格,>8 靠右對齊佔 8 格
                message_lines.append(f"{code:<6} {name:<6} {weight:>8}")
                count += 1
                if count >= 10: # 只取前 10 名
                    break

        message_lines.append("```") # 結束程式碼區塊
        final_msg = "\n".join(message_lines)

        # 發送訊息到 Discord
        send_discord_message(final_msg)

    except Exception as e:
        print(f"爬蟲執行發生未預期錯誤: {e}")
        # 如果執行失敗,也發送一個錯誤通知到 Discord
        error_msg = f"**0050 監控機器人執行失敗!**\n錯誤訊息: `{e}`"
        send_discord_message(error_msg)
    finally:
        browser.close()
        print("程式結束。")

if __name__ == "__main__":
    with sync_playwright() as playwright:
        run(playwright)

2. 依賴清單:requirements.txt

# requirements.txt
playwright
requests

3. GitHub Actions 設定檔:.github/workflows/daily_monitor.yml

請在你的專案根目錄下建立 .github/workflows/ 資料夾,然後在裡面建立 daily_monitor.yml 檔案。

# .github/workflows/daily_monitor.yml
name: Daily Stock Monitor

on:
  # 設定排程執行 (Cron Job)
  schedule:
    # 這是 UTC 時間!台灣是 UTC+8
    # 所以如果你想在台灣時間早上 09:00 執行,要設成 UTC 01:00
    # 語法: '分 時 日 月 週'
    # 每天 01:00 UTC,週一到週五 (1-5) 執行
    - cron: '0 1 * * 1-5'

  # 允許手動點擊按鈕觸發 Workflow (方便測試用)
  workflow_dispatch:

jobs:
  run_scraper:
    # 使用最新的 Ubuntu Linux 環境
    runs-on: ubuntu-latest

    steps:
      # 步驟 1: 下載你的程式碼
      - name: Checkout code
        uses: actions/checkout@v4

      # 步驟 2: 設定 Python 環境
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12' # 建議指定一個穩定的 Python 版本

      # 步驟 3: 安裝 Python 套件 (從 requirements.txt)
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      # 步驟 4: 安裝 Playwright 的瀏覽器 (這是最容易漏掉但關鍵的一步!)
      # Playwright 需要真實的瀏覽器內核才能運行
      - name: Install Playwright Browsers
        run: |
          playwright install chromium # 我們只用 Chromium,所以只安裝它

      # 步驟 5: 執行爬蟲腳本
      - name: Run Python Scraper Script
        # 將 GitHub Secrets 中的 Webhook 網址注入到環境變數中
        env:
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
        run: |
          python main.py

部署與驗收:一步步來!

現在,你的專案資料夾應該長這樣:

stock_monitor/
├── main.py
├── requirements.txt
└── .github/
    └── workflows/
        └── daily_monitor.yml

請按照以下步驟進行部署:

1. 將程式碼推送到 GitHub

將你本地的 stock_monitor 資料夾初始化為 Git Repository,並將所有檔案推送到你的 GitHub 遠端儲存庫 (Repository)。

cd stock_monitor
git init
git add .
git commit -m "feat: Add 0050 stock monitor with Discord notification"
git branch -M main
git remote add origin https://github.com/你的用戶名/你的repo名稱.git
git push -u origin main

(請替換 你的用戶名你的repo名稱)

2. 設定 GitHub Secrets (保護你的 Discord Webhook)

  1. 打開你的 GitHub 專案頁面。
  2. 點擊頂部的 Settings (設定)。
  3. 在左側選單找到 Secrets and variables,然後點擊 Actions
  4. 點擊右上角的 New repository secret
    • Name: 請填入 DISCORD_WEBHOOK_URL (這個名稱必須與 main.py.yml 檔案中使用的環境變數名稱完全一致)。
    • Secret: 貼上你之前從 Discord 頻道複製的那串 Webhook 網址
  5. 點擊 Add secret

這樣,GitHub Actions 在執行時就能讀取這個網址,但它不會被公開顯示。

3. 手動觸發 Workflow 進行測試

我們需要先測試一下設定是否正確。

  1. 在你的 GitHub 專案頁面,點擊上方的 Actions 分頁。
  2. 在左側的 Workflows 列表中,點擊 Daily Stock Monitor
  3. 在右側你會看到一個藍色的按鈕 Run workflow,點擊它。
  4. 在彈出的視窗中,再次點擊綠色的 Run workflow 按鈕。

這將會立即觸發一次 Job 執行。

4. 觀察執行結果並驗收

  1. 點擊進入正在執行的 Job,你會看到它一步步完成:Checkout code -> Set up Python -> Install dependencies -> Install Playwright Browsers -> Run Python Scraper Script
  2. 耐心等待幾分鐘,如果一切順利,你應該會看到actions出現綠勾勾
  3. 最重要的是:拿起你的手機,檢查你的 Discord 頻道! 你應該會收到一則來自「0050 監控機器人」的報價訊息。

如果收到了,恭喜你!你的自動化系統已經成功在雲端運行了!以後它就會根據你設定的排程(週一到週五早上九點,台灣時間)自動發送通知。


結語:你達成了一項成就!

恭喜!你現在擁有的不只是一隻爬蟲,而是一個 Serverless (無伺服器)的自動化系統

  • 你不需要付錢買 VPS。
  • 你不需要整天開著電腦。
  • 只要 GitHub 不倒,你的機器人就會在每個交易日的早上 9 點準時上工。

這套「Playwright + GitHub Actions + Discord Webhook」的組合技,基本上可以解決個人開發者 90% 的自動化需求。無論是監控機票價格、新書通知、還是追蹤特定商品的庫存狀態,通通都能搞定。

全系列完結!感謝大家的閱讀,快去動手打造你的懶人機器人吧!


0 留言

目前沒有留言

發表留言
回覆