爬蟲第八步:用 Python 爬蟲取得股票除權息歷史

一、前言

我們在進行回測時,最先遇到的問題往往是:除權息的跳空該怎麼處理?不妥善處理的話,有偏差的回測結果會導致數據完全不具參考性。先前我們帶大家做過 ETF 配息歷史資料的爬蟲實作。這篇我們接著把上市和上櫃股票的除權息歷史爬蟲也搞定吧!

立即訂閱電子報,掌握最新資訊!

    稱呼

    電子郵件

    以下非必填,但若您願意分享,我們將能推送更精準的內容給您

    投資經驗

    是否為理工科背景、工程師或有寫程式的經驗?

    有興趣的主題
    量化交易台股期貨海外期貨虛擬貨幣美股

    有興趣的量化交易軟體/平台
    不清楚MultiChartsTradingViewPythonXQ

    想透過量化交易達成甚麼目的?
    不確定自動交易選股回測投資績效量化自己的投資方法想找現成的策略套用

    還有什麼想詢問的?

    好富投 1920x400
    好富投 978x258

    點我了解更多資訊


    二、除權息歷史資料來源

    我們直接使用證交所與櫃買中心官方第一手數據,確保資料正確。換句話說,這隻爬蟲同樣也能爬到 ETF 配息歷史,不一定要用另一篇 MoneyDJ 網站作為數據來源。

    上市證券除權息歷史,從網站進去的話是在「市場公告→計算結果表」中,如下圖。

    Python Stock Ex Dividend 101202131514

    上櫃證券除權息歷史,從網站進去的話是在「公告及法規→市場公告→除權除息→計算結果表」中,如下圖。

    Python Stock Ex Dividend 101202131515

    我們現在知道資料在什麼 URL 下了,那接下來各自破解吧!

    Py 101209161710
    Py 101209161711

    三、上市證券爬蟲

    上市的相對好爬,直接秒殺。首先,只需要使用 pandas。

    import pandas as pd

    接著,我們根據證交所的 URL 日期參數的規則,生成 URL 的字串,這樣能讓爬取不同日期區間的操作變得更方便。這裡的日期格式是八位數西元年月日,例如 “20220101”。本篇最後會整理呼叫函數的指令和全部程式碼。

    在生成 URL 後,直接簡單暴力使用 pd.read_html 並取出爬取結果的第 0 號位置的 DataFrame,就大功告成了。

    def get_twse_dividend_history(start_date: str, end_date: str):
        url = f"https://www.twse.com.tw/exchangeReport/TWT49U?response=html&strDate={start_date}&endDate={end_date}"
        dividend_history = pd.read_html(url)
    
        return dividend_history[0]

    四、上櫃證券爬蟲

    上櫃證券相對麻煩一點,因為實際觀察瀏覽器的行為,發現呼叫回傳的結果,需要再稍微做點處理,才能解析成我們方便調用的格式。

    因此,這裡不能直接用 pandas 爬蟲,只能乖乖用 requests 來爬,然後再用 pandas 處理欄位。

    import requests
    import pandas as pd

    接下來我們列點解說:

    • 櫃買中心 URL 所接受的日期格式是 “民國年/月/日”,但我們設計成函數就是為了更一般化使用,所以我們傳入 start_date 和 end_date 一樣是傳入八位數的西元年月日字串,但是多了一道日期格式轉換的手續。如下方程式碼第 2 ~ 4 行。
    • 生成日期後,即可生成爬蟲的 URL。如第 5 行。
    • 根據櫃買中心的網站欄位名稱,我們手動定義一個 list 作為之後使用。因為櫃買中心的 URL 並不會回傳欄位名稱。如下方很長的一大串中文字,這個換行規則是使用 VSCode 內 formatter 做程式碼格式化,以便後續維護程式的閱讀。
    • 使用 requests.get 獲取資料,並用 .json() 進行解析,取出其中 “aaData” 下對應的資料。這些資料即可直接使用 pd.DataFrame 做成大家熟悉的 DataFrame。剛才製作的欄位名稱也可以順手放進來。這樣就大功告成了!
    def get_two_dividend_history(start_date: str, end_date: str):
        # parse YYYYMMDD to YYY/MM/DD
        start_date = f"{int(start_date[:4])-1911}/{start_date[4:6]}/{start_date[6:]}"
        end_date = f"{int(end_date[:4])-1911}/{end_date[4:6]}/{end_date[6:]}"
        url = f"https://www.tpex.org.tw/web/stock/exright/dailyquo/exDailyQ_result.php?l=zh-tw&d={start_date}&ed={end_date}"
        
        dividend_history_columns = [
            "除權息日期",
            "代號",
            "名稱",
            "除權息前收盤價",
            "除權息參考價",
            "權值",
            "息值",
            "權值+息值",
            "權/息",
            "漲停價",
            "跌停價",
            "開始交易基準價",
            "減除股利參考價",
            "現金股利",
            "每仟股無償配股",
            "現金增資股數",
            "現金增資認購價",
            "公開承銷股數",
            "員工認購股數",
            "原股東認購股數",
            "按持股比例仟股認購",
        ]
        res = requests.get(url)
        dividend_history = pd.DataFrame(
            res.json()["aaData"], columns=dividend_history_columns
        )
        return dividend_history

    接著我們來呼叫這兩個函數吧!假設我們指定的是 2022 全年的除權息歷史資料,呼叫方式如下。

    start_date = “20220101”
    end_date = “20221231”
    
    twse_dividend_history = get_twse_dividend_history(start_date, end_date)
    twse_dividend_history.to_csv("test.csv", index=False)
    
    two_dividend_history = get_two_dividend_history(start_date, end_date)
    two_dividend_history.to_csv("test_2.csv", index=False)

    如此一來爬蟲就完成了!不過,實際上我們在使用時,往往還會追求欄位名稱統一。這部分沒什麼難度,就依各位讀者的喜好自行處理囉!


    加入LINE社群量化交易討論群」無壓力討論與分享!

    加入Discord 「量化交易討論群」即時獲取實用的資源!

    Write Together 101306261122
    Write Together 101306261121
    QP66
    QP66

    具備逾十年交易經驗,研究交易資產橫跨股票、債券、外匯、原物料,以及加密貨幣。現為量化避險基金交易員,亦曾任職於資金規模逾百億的避險基金,以及在區塊鏈企業擔任顧問一職。

    擅長從宏觀至微觀,由淺入深挖掘交易機會,並運用Python實現全自動化的投資組合管理。

    文章: 24

    發佈留言

    發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *