免責聲明:我們盡力確保本文的正確性,但本文不代表任何投資的建議,我們也無法擔保因使用本文的內容所造成的任何損失。如對本文內容有疑問,請詢問財經相關的專家。
大盤指數本身雖然不像 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
物件,一個是目標日期 year
和 month
,我們的迴圈需要這兩個日期點判斷迴圈結束的時機。
建立使用 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) 開始爬,所以 currYear
和 currMonth
會以過去日期來設置。為什麼要從過去日期開始爬?因為我們想要歷史資料的日期是順向的,這樣就不用爬取後再把數據反向。順向的數據對於數據的視覺化來說會比較方便,因為習慣上圖表的左方代表先前日期的交易數據。
我們在這裡會先預選一次年份,這樣之後再跑迴圈時不同每個月重新選取年份。這是筆者邊寫程式邊觀察爬蟲的動作所得的結論,不一定適用在所有網站,也不要死記這個手法。
由於這個迴圈比較大,我們先把實作的部分移除,只看邏輯的部分:
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 取得跨月份的歷史數據。這些都是實作這隻爬蟲時學到的寶貴經驗。