簡易教學:用 Python 撈取股市資料與作圖

一直都想試試用 Python 撈股市資料與作圖,終於用好啦。這篇文章應該“有點”適合完全不會 Python 的人看。雖然我全部都說不清楚,因為我也真的不太會 Python(如果說錯,還請多多包涵),但我都有貼每個用法的“說明文章”,所以只要有耐心依序看完這些文章,那基本上應該沒問題,嗯,只要有耐心看完 XD

這篇文章的目的不是要分析股市,畢竟我也不懂股市。這篇的目的在於簡單說明如何用 Python 從「公開資訊觀測站」撈取簡單資料,並且作圖。只要學會這些,其他的操作我認為是大同小異,頂多是比較複雜的數據處理。但只要撈到數據了,基本上數據處理的程式語言應該不會太難。

總太 (3056) 的「現金及約當現金」變化圖

Github 完整程式碼

推薦使用 Pycharm 玩 Python:

Download PyCharm

用 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 的「開發人員工具」才能看到。

Google chrome「開發人員工具」

點開「開發人員工具」後,選 Network > All,然後這時才按下「查詢」按鈕,這時右方就會出現網址,底下就會有爬蟲所需的 “form data”。

由 Google chrome「開發人員工具」尋找爬蟲所需的網址
3056 的 form data

有了這些 “form data”,你就能開始爬蟲了!另外,在按下「查詢」後,你的網頁應該會如下:

總太 3056 在 108 年第 1 季的「資產負債表」

如果沒找到 “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)
所有資料的內容,這些就是每一列(Row,直行橫列)的名稱。

接著我們來讀取第一列的所有數據,也就是「現金及約當現金」,這裡需要用到 .iloc 指令,推薦看看〈快速学会pandas中Dataframe索引.ix,.iloc,.loc的使用以及区别〉。

print('(2): 第一列(row)')
print(Data.iloc[1])

以及第二列:

print('(3): 第二列(row)')
print(Data.iloc[2])

再來是讀取

  1. 第一列第一行,也就是「現金及約當現金」與「金額」,iloc[1, 1]。
  2. 第一列第二行,也就是「現金及約當金額」與「%數」,iloc[1, 2]。
  3. 第一列第一行,跟上面一樣,不過我們這次改個讀取方式,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']

那麼:

  1. a[0] 是指與第一個值 ‘s’ 偏移 0 的數值,也就是 ‘s’ 本身。
  2. a[1] 是指與第一個值 ‘s’ 偏移 1 的數值,也就是 ‘t’。
  3. a[2] 是指與第一個值 ‘s’ 偏移 2 的數值,也就是 ‘u’。
由上到下為「行」,表格其實在「由上往下數第五層」,「第三層」才有日期。

因此,

  1. 由左向右數第一行有(‘民國108年第1季’, ‘單位:新台幣仟元’, ‘會計項目’, ‘Unnamed: 0_level_3’)
  2. 由左向右數第二行有(‘民國108年第1季’, ‘單位:新台幣仟元’, ‘108年03月31日’, ‘金額’)
  3. 由左向右數第三行有(‘民國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。

總太 (3056) 的「現金及約當現金」變化圖

關於〈簡易教學:用 Python 撈取股市資料與作圖〉,寫得還可以嗎?

View Results

Loading ... Loading ...

關於「Ethan」

我是 Ethan,科學普及教育愛好者。
分類: Python, 投資理財, 生活,標籤: 。這篇內容的永久連結

發表迴響