Selenium 網路爬蟲教學: 用 Selenium 抓取台股大盤指數 (發行量加權股價指數) 歷史資料

PUBLISHED ON NOV 12, 2018 — WEB
FacebookTwitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    免責聲明:我們盡力確保本文的正確性,但本文不代表任何投資的建議,我們也無法擔保因使用本文的內容所造成的任何損失。如對本文內容有疑問,請詢問財經相關的專家。

    大盤指數本身雖然不像 ETF 般可以直接購買,但大盤指數可以反映目前台灣股市的整體景氣變化,故仍有一定參考性。台股證交所內建的 CSV 下載連結只能取得單月份的數據,對於中長期的分析來說不太方便。本文實作一個可以爬取中長期大盤指數的爬蟲,透過本爬蟲,可以自動化取得連續數年的大盤指數,對於後續的分析比較方便。

    同樣地,我們先拆解抓取大盤指數所進行的動作:

    • 前往大盤指數歷史數據網站
    • 選取特定年份
    • 選取特定月份
    • 按下查詢按鈕
    • (需用爬蟲) 抓取該月份的歷史數據
    • (需用爬蟲) 重新選取另一個年份、月份後再查詢

    由此可知,如果這個動作用純手動,要抓取跨月資料時會有困難;這時候,就很適合用爬蟲來減少不必要的手工。

    由於這個程式碼略長,我們將完整的程式碼放在這裡,除了可以追蹤程式碼,這段程式碼也是一個立即可用的命令列工具,只要台股證交所網站不改版就可以繼續使用。接下來,我們會拆解這段程式碼,供有興趣學習實作的讀者參考。

    引入相關的套件:

    import csv
    import os
    import sys
    import time
    import datetime
    
    from selenium import webdriver
    

    設置時距:

    validDurations = ['YTD', '1Y', '3Y', '5Y', '10Y', 'Max']
    duration = 'YTD'
    

    原本證交所上的網頁並沒有中長期時距的概念,這段時距是我們自行加上去的。讀者若有需要也可自行加入其他的時距。

    設置相關時間點:

    now = datetime.datetime.now()
    
    year = None
    month = now.month
    
    if duration == 'YTD':
        year = now.year
        month = 1
    elif duration == '1Y':
        year = now.year - 1
    elif duration == '3Y':
        year = now.year - 3
    elif duration == '5Y':
        year = now.year - 5
    elif duration == '10Y':
        year = now.year - 10
    elif duration == 'Max':
        year = 88 + 1911
        month = 1
    

    我們會有兩個日期點,一個是目前的日期 now 物件,一個是目標日期 yearmonth,我們的迴圈需要這兩個日期點判斷迴圈結束的時機。

    建立使用 Chrome 的 web driver:

    # Create a new instance of the Chrome driver
    driver = webdriver.Chrome()
    

    前往大盤指數所在的頁面:

    # Go to TAIEX page
    driver.get("http://www.twse.com.tw/zh/page/trading/indices/MI_5MINS_HIST.html")
    
    # Wait the page to fresh.
    time.sleep(10)
    

    頁面需要暫停數秒,待網頁載入完成。

    抓取查詢按鈕:

    queryBtn = driver.find_element_by_css_selector(".main a")
    

    現在不用急著按下查詢鈕,待會會在適當的時間按下查詢鈕。

    準備進入這隻爬蟲最重要的部分,按月份爬取大盤指數歷史數據:

    data = []
    isEnd = False
    currYear = year
    currMonth = month
    
    # Select the initial year.
    ys = driver.find_elements_by_css_selector("select[name=\"yy\"] option")
    for y in ys:
        if y.get_attribute("value") == str(currYear):
            y.click()
            time.sleep(2)
            break
    
    while not isEnd:
        # Run the crawler here.
    

    我們在這裡從時距的過去日期 (past date) 開始爬,所以 currYearcurrMonth 會以過去日期來設置。為什麼要從過去日期開始爬?因為我們想要歷史資料的日期是順向的,這樣就不用爬取後再把數據反向。順向的數據對於數據的視覺化來說會比較方便,因為習慣上圖表的左方代表先前日期的交易數據。

    我們在這裡會先預選一次年份,這樣之後再跑迴圈時不同每個月重新選取年份。這是筆者邊寫程式邊觀察爬蟲的動作所得的結論,不一定適用在所有網站,也不要死記這個手法。

    由於這個迴圈比較大,我們先把實作的部分移除,只看邏輯的部分:

    while not isEnd:   
        if currYear < now.year:
            if currMonth <= 12:
                # Crawl the website.
                
                currMonth += 1
            else:
                currMonth = 1
                currYear += 1
    
                # Crawl the website.
        else:
            if currMonth <= now.month:
                # Crawl the website.
                
                currMonth += 1
            else:
                isEnd = True
    

    從這段程式碼中看得出來,我們的迴圈就是按月遞增,當迴圈遞增到目前日期時,迴圈就中止。

    我們來看當目前的年份小於現在年份時的情形:

    while not isEnd:   
        if currYear < now.year:
            if currMonth <= 12:
                ms = driver.find_elements_by_css_selector("select[name=\"mm\"] option")
    
                for m in ms:
                    if m.get_attribute("value") == str(currMonth):
                        m.click()
                        time.sleep(2)
                        queryBtn.click()
                        time.sleep(3)
    
                        items = driver.find_elements_by_css_selector("#report-table_wrapper tbody tr")
    
                        for item in items:
                            tds = item.find_elements_by_css_selector("td")
    
                            data.append([td.text for td in tds])
                        break
                
                currMonth += 1
            else:
                currMonth = 1
                currYear += 1
    
                # Update the year when one year progresses.
                ys = driver.find_elements_by_css_selector("select[name=\"yy\"] option")
                
                for y in ys:
                    if y.get_attribute("value") == str(currYear):
                        y.click()
                        time.sleep(2)
                        break
        else:
            if currMonth <= now.month:
                # Crawl the website.
                
                currMonth += 1
            else:
                isEnd = True
    

    在每個月份中,我們選取特定月份並按下查詢鈕。之後用爬蟲抓取資料,將資料加入 data 串列的尾端。在這裡我們不直接將資料寫入 CSV 檔案,因為爬取時間較久,這樣整個開啟檔案的時間會拉得很長,我們先將數據存在記憶體,整個爬完後再將數據寫入 CSV 檔。

    當跨到下一個年度時,我們重新選取下一個年份。在這個網頁不需在每個月都選一次年份,可節省一點點操作時間。

    接下來看一下當下年份等於目前年份的情形:

    while not isEnd:   
        if currYear < now.year:
            if currMonth <= 12:
                # Crawl the website.
                
                currMonth += 1
            else:
                currMonth = 1
                currYear += 1
    
                # Crawl the website.
        else:
            if currMonth <= now.month:
                ms = driver.find_elements_by_css_selector("select[name=\"mm\"] option")
    
                for m in ms:
                    if m.get_attribute("value") == str(currMonth):
                        m.click()
                        time.sleep(2)
                        queryBtn.click()
                        time.sleep(3)
    
                        items = driver.find_elements_by_css_selector("#report-table_wrapper tbody tr")
    
                        for item in items:
                            tds = item.find_elements_by_css_selector("td")
    
                            data.append([td.text for td in tds])
                        break
                
                currMonth += 1
            else:
                isEnd = True
    

    其實爬資料的動作是一樣的,重點是邏輯的部分有變化,我們不會爬完整年份的資料,只會爬到當下月份的資料,因為目前的月份還沒走完,無法取得整年的數據。另外,我們也要在適當的時機結束這個迴圈。

    我們設置一些字串,這些字串會用到自動化生成檔名:

    def monToStr(m):
        if m < 10:
            return '0' + str(m)
        else:
            return str(m)
    
    pastDateStr = "%d%s" % (year, monToStr(month))
    currDateStr = "%d%s" % (now.year, monToStr(now.month))
    

    將歷史數據寫入 CSV 檔:

    with open("TAIEX_%sto%s.csv" % (pastDateStr, currDateStr), 'w', newline='') as csvfile:
        csvwriter = csv.writer(csvfile)
    
        csvwriter.writerow(["Date", "Open", "High", "Low", "Close"])
    
        for d in data:
            csvwriter.writerow(d)
    

    最後別忘了關掉瀏覽器。

    # Close the browser.
    driver.quit()
    

    透過這個程式,我們就可以取得中長期的大盤指數歷史數據。由於原本證交所網站沒有中長期時距的概念,我們在程式中自行加入;另外,我們也可以練習如何用 Selenium 取得跨月份的歷史數據。這些都是實作這隻爬蟲時學到的寶貴經驗。

    TAGS: SELENIUM