一直都想試試用 Python 撈股市資料與作圖,終於用好啦。這篇文章應該“有點”適合完全不會 Python 的人看。雖然我全部都說不清楚,因為我也真的不太會 Python(如果說錯,還請多多包涵),但我都有貼每個用法的“說明文章”,所以只要有耐心依序看完這些文章,那基本上應該沒問題,嗯,只要有耐心看完 XD
這篇文章的目的不是要分析股市,畢竟我也不懂股市。這篇的目的在於簡單說明如何用 Python 從「公開資訊觀測站」撈取簡單資料,並且作圖。只要學會這些,其他的操作我認為是大同小異,頂多是比較複雜的數據處理。但只要撈到數據了,基本上數據處理的程式語言應該不會太難。
推薦使用 Pycharm 玩 Python:
用 PyCharm 下載各種 library 的方法(如 numpy、matplotlib 與 panda 等):
Install, uninstall, and upgrade packages
首先,這些程式碼是來自〈python爬蟲 – 個股每季財報〉,我複製後簡單修改而已。
第一步就是設定各資料表格的網址。
BalanceSheetURL = "https://mops.twse.com.tw/mops/web/ajax_t164sb03"; # 資產負債表 ProfitAndLoseURL = "https://mops.twse.com.tw/mops/web/ajax_t164sb04"; # 綜合損益表 CashFlowStatementURL = "https://mops.twse.com.tw/mops/web/ajax_t164sb05"; # 現金流量表 EquityStatementURL = "https://mops.twse.com.tw/mops/web/ajax_t164sb06"; # 權益變動表
以總太 3056 為例,首先進入資產負債表,選擇「歷史資訊」、3056、108年、第1季,然後“先別按下查詢”。此時你的網址並不能餵給 Python,必須藉由 Chrome 的「開發人員工具」才能看到。
點開「開發人員工具」後,選 Network > All,然後這時才按下「查詢」按鈕,這時右方就會出現網址,底下就會有爬蟲所需的 “form data”。
有了這些 “form data”,你就能開始爬蟲了!另外,在按下「查詢」後,你的網頁應該會如下:
如果沒找到 “form data”,那可以看看〈【茶包射手筆記】Chrome 開發者工具看不到 Form Data〉。底下這個 crawl_financial_report 函數就是要藉由上述的網址以及 form data 來撈取「資產負債表」的表格,請參考〈Day28 Python 基礎 – 函數介紹〉。
def crawl_financial_report(url, stock_number, year, season): form_data = { 'encodeURIComponent': 1, 'step': 1, 'firstin': 1, 'off': 1, 'co_id': stock_number, 'year': year, 'season': season, } r = requests.post(url, form_data) html_df = pd.read_html(r.text)[1].fillna("") return html_df
此外,我們在這也用到很強大的 pandas 的 dataframe,請參考〈Pandas 基礎教學〉。我撈完後,就把它存在 “Data” 中:
# 爬取目標網站 year = 108 season = 1 stock_number = 3056 # 結果 Data = crawl_financial_report(BalanceSheetURL, 3056, year, season)
接下來是示範怎麼從 Data 得到「資產負債表」中各欄位的數據。首先是直接把 Data 印出來,看看會發生什麼事:
print('(1): 所有資料') print(Data)
接著我們來讀取第一列的所有數據,也就是「現金及約當現金」,這裡需要用到 .iloc 指令,推薦看看〈快速学会pandas中Dataframe索引.ix,.iloc,.loc的使用以及区别〉。
print('(2): 第一列(row)')
print(Data.iloc[1])
以及第二列:
print('(3): 第二列(row)')
print(Data.iloc[2])
再來是讀取
- 第一列第一行,也就是「現金及約當現金」與「金額」,iloc[1, 1]。
- 第一列第二行,也就是「現金及約當金額」與「%數」,iloc[1, 2]。
- 第一列第一行,跟上面一樣,不過我們這次改個讀取方式,iloc[1][1]。
print('(4): 第一列(row)且第一行(column)的元素(第一季結尾3/31的現金及約當現金的“金額”)') print(Data.iloc[1, 1]) print('(5): 第一列(row)且第二行(column)的元素(第一季結尾3/31的現金及約當現金的“金額%數”)') print(Data.iloc[1, 2]) print('(6): 第一列(row)且第一行(column)的元素(第一季結尾3/31的現金及約當現金的“金額”)') print(Data.iloc[1][1])
再來是讀取日期,也就是「108年03月31日」,那這就需要用到 columns() 功能,詳見〈How to get column names in Pandas dataframe〉。
print('(7): 行表頭(column header)')
print(Data.columns)
print('(8): 第一個行表頭(column header)')
print(Data.columns[0])
print('(9): 第二個行表頭(column header)')
print(Data.columns[1])
print('(10): 第三個行表頭(column header)')
print(Data.columns[2])
所謂的「行」,是由上到下的方向,而我們上方所讀取的 columns[0],是指由左到右序號為 0 的那一行,columns 則是序號為 1 的那一行,以此類推。
Python 是由 0 開始計數的,而不是 1。例如
a = ['s','t','u']
那麼:
- a[0] 是指與第一個值 ‘s’ 偏移 0 的數值,也就是 ‘s’ 本身。
- a[1] 是指與第一個值 ‘s’ 偏移 1 的數值,也就是 ‘t’。
- a[2] 是指與第一個值 ‘s’ 偏移 2 的數值,也就是 ‘u’。
因此,
- 由左向右數第一行有(‘民國108年第1季’, ‘單位:新台幣仟元’, ‘會計項目’, ‘Unnamed: 0_level_3’)
- 由左向右數第二行有(‘民國108年第1季’, ‘單位:新台幣仟元’, ‘108年03月31日’, ‘金額’)
- 由左向右數第三行有(‘民國108年第1季’, ‘單位:新台幣仟元’, ‘108年03月31日’, ‘%’)
那麼為什麼每一行有這麼多東西呢?因為其實我們的「表格」是落在「由上往下屬第五層」,所以每一行的「標題」或是「名字」就會有「四層」,仔細數上方的單引號 ‘ … ‘ 數目,就可發現確實有四層,如下圖所示。
於是,我們要的日期就在「第二行且第三層」:
print('(11): 第二個行表頭且“第三層”')
print(Data.columns[1][2])
既然知道該怎樣讀取「金額」以及「日期」,那就可以開始讀取各年份的數據來做圖了。
Date = []
Cash_and_equivalents = []
for yr in np.arange(102, 109, 1):
tmp_Data = crawl_financial_report(BalanceSheetURL, 3056, yr, season)
Date.append(tmp_Data.columns[1][2][0:3])
Cash_and_equivalents.append(tmp_Data.iloc[1, 1])
首先建立兩個列表(list),詳見〈Python 初學第五講 — 串列的基本用法〉,即 Date 與 Case_and_equivalents,接著用 for loop,詳見〈Python 初學第四講 — 迴圈〉,來一一撈取各年份的「資產負債表」。這裡使用了 numpy(縮寫為 np)中的 arange()。之所以會縮寫為 np,是因為在程式的一開始(詳見 Github 完整程式碼),我們讀取了 numpy,並將其命名為 np。
# import numerical python library
import numpy as np
再來,np.arange(102, 109, 1) 其實會產生一個數列,其實在這裡你也可以將它改為 [102,103,104,105,106,107,108],for loop 效果相同,詳見〈Python 基礎——range() 與 np.arange()〉,如下:
Date = [] Cash_and_equivalents = [] for yr in [102,103,104,105,106,107,108]: tmp_Data = crawl_financial_report(BalanceSheetURL, 3056, yr, season) Date.append(tmp_Data.columns[1][2][0:3]) Cash_and_equivalents.append(tmp_Data.iloc[1, 1])
之所以選 102-108 年,純粹是因為公開資訊觀測站似乎只提供 102年(含)以後的數據。接著宣告且定義了 tmp_Data 為在 yr 年的資產負債表,並將它們的「年份」與「現金及約當現金」存到 Date 與 Cash_and_equivalents 列表(list)裡頭,用到了 append() 附加功能,詳見〈Python List append()方法〉。這裡我故意用了複雜的下方程式碼,主要是想順便介紹字串(string)的功能,詳見〈How to display the first few characters of a string in Python?〉。宜中的[0:3]就是「第零個到第二個字母(共三個,0、1、2)」不然也可簡單取代為 Date.append(i),甚至根本不用 Date,直接用 np.arange(102,109,1) 作為年份陣列(np.array)。
tmp_Data.columns[1][2][0:3]
Numpy 的 Array 與 Python 內建的 List 差很多,np.array() 非常好用,例如
>>> 2 * [1,2,3] [1, 2, 3, 1, 2, 3] >>> import numpy as np >>> test_array = np.array([1,2,3]) >>> 2 * test_array array([2, 4, 6])
可以看到,List 不太適合拿來當作「可數值計算的數列」,因為對 Python 而言,List 只不過是「各個物件的表列」。但是 np.array 就是把裡面的東西當數字陣列,所以乘法就有“分配律”(勉強這麼說)。
接著印出 Date 與 Cash_and_equivalents 結果:
print(Date) print(Cash_and_equivalents)
最後就是畫圖!
在畫圖之前,必須先引入 matplotlib 的 pyplot 物件,詳見〈Matplotlib 简介〉。底下我們引入之後,還設定了一些繪圖格式參數 params。
# import plot package import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot as plt import matplotlib.pylab as pylab params = {'legend.fontsize': 'x-large', 'figure.figsize': (7.5, 7), 'axes.labelsize': 'x-large', 'axes.titlesize':'x-large', 'xtick.labelsize':'x-large', 'ytick.labelsize':'x-large'} pylab.rcParams.update(params) plt.rcParams.update({'font.size': 10})
接著就是真的畫圖了。
plt.figure(1)
plt.plot(Date, Cash_and_equivalents, '-bo')
plt.xlabel('Year')
plt.ylabel('Cash and equivalents')
plt.grid()
plt.title('Stock: 3056')
plt.show()
首先宣告這是編號為 1 的圖,建立這個物件。再來給入 x 座標 Date 與 y 座標 Cash_and_equivalents,設定顏色為藍色、實線且數據記號(marker)為圓點(’-bo’)。設定橫軸、縱軸名稱,畫上網格(grid),設定繪圖標題(Stock: 3056),以及最重要的“畫圖”:
plt.show()
沒有這一行,就不能畫圖囉。感謝你看到這,希望能幫助你上手 Python。