通過 Github Action 實現定時推送天氣預報

偶然間,看到 GitHub Actions 教程:定時發送天氣郵件 – 阮一峰的網路日誌 這篇文章,沒錯,這個正好能打發自己的折騰之心,也能通過程式碼給生活引入一些變化。

還是在這裡簡單記錄一下實現過程吧。

第一步 獲取天氣預報出現問題

按照阮一峰的教程走,一開始使用了 wttr 的結果作為數據來源,也在 文檔 上研究了很久,最終的結果總是不盡如人意。

最終展現到郵件上的結果如下:

桂林 wttr 天氣預報

從上面就可以看出一些問題:

  • 展示到郵件中的是一個 HTML 頁面,白色的背景使得結果展示不理想
  • 默認返回的結果比較多,根據配置做調整之後返回的結果又比較少,結果不盡如人意
  • 從頁面上看返回的都是不太好理解的單位,不能讓人一眼就能理解
  • ……

其實還有很多問題,最主要的原因還是其 API 的結果更符合國外的理解,而不適合我用。

第二步 尋找新的數據來源

通過在網上尋找,最終找到了一個 墨跡天氣 的 API 作為數據來源,雖然沒有找到出處,但是暫時還可用。

其返回的結果是一個 JSON 對象,可根據自己的需求去組裝。下面是返回的示例:

{
    "code": 0,
    "msg": "操作成功",
    "data": {
        "total": 7,
        "sourceName": "墨跡天氣",
        "list": [
            {
                "city": "廣州",
                "lastUpdateTime": "2022-10-13 08:55:08",
                "date": "2022-10-13",
                "weather": "晴",
                "temp": 20.0,
                "humidity": "35%",
                "wind": "東北風3級",
                "pm25": 29.0,
                "pm10": 43.0,
                "low": 20.0,
                "high": 30.0,
                "airData": "43",
                "airQuality": "優",
                "dateLong": 1665590400000,
                "weatherType": 0,
                "windLevel": 3,
                "province": "廣東"
            },
            {
                "city": "廣州",
                "lastUpdateTime": "2022-10-13 08:00:00",
                "date": "2022-10-14",
                "weather": "晴",
                "humidity": "未知",
                "wind": "微風",
                "pm25": 0.0,
                "low": 21.0,
                "high": 30.0,
                "airData": "80",
                "airQuality": "良",
                "dateLong": 1665676800000,
                "weatherType": 0,
                "windLevel": 1,
                "province": "廣東"
            },
            {
                "city": "廣州",
                "lastUpdateTime": "2022-10-13 08:00:00",
                "date": "2022-10-15",
                "weather": "晴",
                "humidity": "未知",
                "wind": "北風",
                "pm25": 0.0,
                "low": 21.0,
                "high": 31.0,
                "airData": "80",
                "airQuality": "良",
                "dateLong": 1665763200000,
                "weatherType": 0,
                "windLevel": 3,
                "province": "廣東"
            },
            {
                "city": "廣州",
                "lastUpdateTime": "2022-10-13 08:00:00",
                "date": "2022-10-16",
                "weather": "多雲",
                "humidity": "未知",
                "wind": "北風",
                "pm25": 0.0,
                "low": 22.0,
                "high": 32.0,
                "airData": "70",
                "airQuality": "良",
                "dateLong": 1665849600000,
                "weatherType": 1,
                "windLevel": 4,
                "province": "廣東"
            }
        ],
        "logoUrl": "//iflycar.hfdn.openstorage.cn/xfypicture/dev/logo/moji.png"
    }
}

根據上述的返回結果,簡單組裝了一個自己想要的結果:

位置:廣東-廣州  今天:2022-10-11
當前:15.0°C  最低:15.0°C  最高:26.0°C
空氣品質:優  濕度:29%
風向:東北風4級  PM2.5:17.0

位置:廣西-桂林  今天:2022-10-11
當前:11.0°C  最低:11.0°C  最高:25.0°C
空氣品質:優  濕度:30%
風向:北風5級  PM2.5:23.0

實際上是非常簡陋的,但卻也暫時夠用了,後續有相關的需求再加內容上去。

第三步 通過腳本簡化

解決了數據來源和展示文本之後,其實已經是解決了需求端的問題,然後來到程式設計師的實現端。

現在,我們先將需求做拆解,落實到程式上應該有以下工作要做:

  • 通過 API 獲取到數據來源,組裝成推送的文本格式
  • 定時觸發,可以通過 Github Action 白嫖
  • 發送郵件,可以通過 QQ 郵箱白嫖

上述工作中的第一步,我最終是選擇使用 Python 對其腳本化,程式碼如下:

import sys

import requests


def generate_weather_text(weather: dict) -> str:
    ret = [
        f'位置:{weather.get("province")}-{weather.get("city")}  今天:{weather.get("date")}',
        f'當前:{weather.get("temp")}°C  最低:{weather.get("low")}°C  最高:{weather.get("high")}°C',
        f'空氣品質:{weather.get("airQuality")}  濕度:{weather.get("humidity")}',
        f'風向:{weather.get("wind")}  PM2.5:{weather.get("pm25")}',
    ]
    return '\n'.join(ret)


def get_weather(city: str) -> dict:
    url = '//autodev.openspeech.cn/csp/api/v2.1/weather'
    params = {
        'openId': 'aiuicus',
        'clientType': 'android',
        'sign': 'android',
        'city': city,
    }
    res = requests.get(url, params=params).json()
    return res['data']['list'][0]


def get_weather_text(city: str) -> str:
    weather = get_weather(city)
    return generate_weather_text(weather)

if __name__ == '__main__':
    if len(sys.argv) >= 2:
        ret = [get_weather_text(_) for _ in sys.argv[1:]]
        print('\n\n'.join(ret))
    else:
        print('請求參數錯誤')

第四步 配置 Github Action

Github Action 的配置文件趨同於阮一峰的教程,下面是這個配置文件的一些解釋。

定時觸發

name: "天氣預報"

on:
  push:
  schedule:
    # 需要減 8 個小時
    - cron: "0 23 * * *"

這裡比較好理解,name 是名稱,on 是觸發的時機,push 是我們提交程式碼到 Github 時觸發,schedule 是定時觸發,需要注意的時候,定時觸發的時間需要減掉 8 個小時,其遵循國際標準時間而不是北京時間。

運行流程

runs-on: ubuntu-latest
steps:
  - name: "切換程式碼"
    uses: actions/checkout@v3

進入到 jobs 運行流程中,runs-on 指定運行環境是最新的 Ubuntu 即可,actions/checkout@v3 用作從程式碼倉庫獲取程式碼。

獲取時間

- name: "獲取時間"
  run: echo "WEATHER_REPORT_DATE=$(TZ=':Asia/Shanghai' date '+%Y-%m-%d %T')" >> $GITHUB_ENV

直接通過 Linux 命令獲取當前時間,然後轉換成北京時間,這個時間主要是用於後續寫入到郵件的標題當中。

在這裡,通過 echo "{environment_variable_name}={value}" >> $GITHUB_ENV 的方式寫入環境變數,在後續的步驟中都可以訪問到這個環境變數。

執行腳本

- uses: actions/setup-python@v4
  with:
    python-version: "3.10"
- run: pip install -r requirements.txt

- name: "獲取天氣結果"
  run: 'echo "$(python open_api/weather.py 廣州 桂林)" > output.txt'

這裡有兩個步驟,一個是指定 Python 的運行環境並且安裝好相關的依賴,第二個是執行 Python 的腳本獲取結果。

在這裡,為了方便將腳本的執行結果給到後續的步驟,選擇將執行結果寫入到一個文件當中。當然,選擇怎樣的方式主要看自己。

發送郵件

- name: "發送郵件"
  uses: dawidd6/action-send-mail@v3
  with:
    server_address: smtp.qq.com
    server_port: 465
    username: ${{ secrets.SENDER_USER }}
    password: ${{ secrets.SENDER_PASSWORD }}
    subject: 天氣預報 - ${{env.WEATHER_REPORT_DATE}}
    from: GitHub Actions
    to: [email protected]
    body: file://output.txt

按照阮一峰的腳本,使用 Send email · Actions 發送郵件,和其不同的就是相關的配置。

當然,也可以通過將發送郵件直接寫入到 Python 腳本當中,它們各有自己的優勢。

使用 GIthub Action 發送郵件更易懂,只需要填寫配置即可,也可以將腳本和發送郵件解耦。

使用 Python 發送郵件可以省下 Github Action 的步驟,直接通過腳本一步到位,耦合就比較高。

總結

通過這一次的嘗試,使用 Github Action 實現了自動化及定時,也是為以後實現自己的自動化做鋪墊。本篇文章的源碼可以通過 GitHub – fatedeity/weather-action 訪問。

生命在於折騰,看似無用的一次嘗試,希望能給自己帶來美好的未來。