什麼是Python網路爬蟲?
Python 網路爬蟲就是利用撰寫 Python 程式碼去對網路資訊進行擷取,例如蒐集匯率的歷史走勢、熱門議題的輿情...等等
現在網路發展的成熟度相當高,幾乎可以在網路上找到、或者說 google 到大部份需要的資訊,而當找到資訊後,如何彙整、蒐集,甚至是定期的去執行這些工作,其實都能夠透過程式來完成 。
今天要一步一步從介紹套件、解析資料來實作股價爬蟲,並前往證券交易所爬取股票價格資訊。
爬蟲不是一般人常聽到的字詞,可以想作抓資料的概念,而抓取資料雖然要撰寫程式碼,不過可以將訣竅簡化為下面幾個步驟,後面的實作也會照著這個步驟來解說
- 選定要爬取的目標
- 解讀與分析資料
- 成功爬取一小部份
- 觀察資料的異同
- 利用迴圈爬取全部
寫程式要完成任務前,都會先導入(import)需要使用的套件,除了資料處理上常用到的 pandas 之外,本次任務也會使用 requests。
requests 是寫 Python 爬蟲常使用到的套件,能夠讓程式對網頁送出請求,取得資料。 Python 有許多強大的套件應用在各種不同領域,透過運用這些套件將能更容易、迅速的達成工作目標。
Python爬蟲-證交所股價擷取
1. 選定要爬取的目標
按照前面所說的步驟,來開始今天的實作吧! 先來看一下今天要爬取的目標,網址在這,從網頁中可以看到分類的選單,在下拉的內容中找到選取全部,按下查詢,再點選左邊的 CSV 下載
接著將會得到一個包含當天市場上各類商品、指數、股票…等等全部的資訊,打開後可以看到包山包海,有好多資料,由於剛剛的下拉選單中並沒有單純股票這個選項,所以必須要從這裡頭來萃取股價資訊。
用 Excel 將檔案打開後,可以發現和網頁上的資訊是相同的,這份檔案就是將網頁上的資訊整理成 csv 後下載。(Windows 用戶打開檔案應該沒問題, Mac 可能會碰到中文亂碼 QQ)
而把 csv 拉到接近最下方,可以看到出現股票的資料,像下圖中一樣,看到台泥就是了,大約有快 1000 筆,這就是本次爬蟲任務的爬取目標。
雖然可以透過直接下載 csv 的方式取得資料,不過如果今天要的是一年份的資料,一天一天下載、整理,效率太低,所以讓 Python 來完成剛剛的動作吧!
首先,先回到剛剛的證交所網頁,對網頁按右鍵 inspect (希望你瀏覽器也是用 Chrome),然後下方會出現一個區塊,點選 Network
的分頁。
此時下方區塊會開始記錄執行的動作,我們再點選一次 CSV 下載 ,這時候下方的區塊會多出一個紀錄,這是剛點選下載的動作,點下後旁邊可以看到 Request URL:
的地方,這個網址就是 csv 資料的來源。
在寫這篇教學文時看到的 Request URL
是下面這樣
https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?date=20230928&type=ALL&response=csv
從 URL 中有看到三個參數,分別為 date
, type
以及 response
從字面上就能迅速理解參數, date
為資料日期, type
為資料內容,網址上顯示的 ALL
就是因為前面我們在下拉選單所選的全部 。 至於 response
則更白話,就是下載的資料格式是 csv
啦。
確認爬許目標,也找到資料來源後,就可以來寫 code 囉!
首先先導入套件
import pandas as pd
import requests
接著讓 requests 來執行類似下載的動作。 requests 後方的 get 為對資料來源的請求方式,爬蟲大多時候會碰到的有兩種, get 和 post。
把結果存在 data 這個變數中,然後進入下一個步驟
url = 'https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?date=20230928&type=ALL&response=csv'
res = requests.get(url)
data = res.text
2. 解讀與分析資料
data 的資料類型是 str
,來偷看一下抓到的資料內容是不是符合預期
data[0:500]
得到結果
'"112年09月28日 價格指數(臺灣證券交易所)"\r\n"指數","收盤指數","漲跌(+/-)","漲跌點數","漲跌百分比(%)","特殊處理註記",\r\n"寶島股價指數","18,734.41","+","49.77","0.27","",\r\n"發行量加權股價指數","16,353.74","+","43.38","0.27","",\r\n"臺灣公司治理100指數","9,128.66","+","19.97","0.22","",\r\n"臺灣50指數","12,059.05","+","27.29","0.23","",\r\n"臺灣50權重上限30%指數","11,402.72","+","27.00","0.24","",\r\n"臺灣中型100指數","16,739.40","+","42.01","0.25","",\r\n"臺灣資訊科技指數","22,409.60","+","71.22","0.32","",\r\n"臺灣發達指數","9,604.56","+","4.72","0.05","",\r\n"臺灣高股息指數","8,073.05","+","36.32","0.45","",\r'
看起來和 csv 裡的內容相同,符合預期。
很多人剛開始可能會被資料雜亂的模樣給嚇到,但仔細一看會發現,如果要整理還是有跡可循的,可以看到有很多不斷出現的 \n
,這符號其實就代表著換行的意思。
所以能先用split()
這個函數來將資料分解,split
的用法就是把資料依指定的元素來切割
for d in data[0:500].split('\n'):
print(d)
印出的結果會像這樣,突然變得像在 Excel 裡看到的模樣,一排一排的往下顯示,看起來是不是親民多了。
"112年09月28日 價格指數(臺灣證券交易所)"
"指數","收盤指數","漲跌(+/-)","漲跌點數","漲跌百分比(%)","特殊處理註記",
"寶島股價指數","18,734.41","+","49.77","0.27","",
"發行量加權股價指數","16,353.74","+","43.38","0.27","",
"臺灣公司治理100指數","9,128.66","+","19.97","0.22","",
"臺灣50指數","12,059.05","+","27.29","0.23","",
"臺灣50權重上限30%指數","11,402.72","+","27.00","0.24","",
"臺灣中型100指數","16,739.40","+","42.01","0.25","",
"臺灣資訊科技指數","22,409.60","+","71.22","0.32","",
"臺灣發達指數","9,604.56","+","4.72","0.05","",
"臺灣高股息指數","8,073.05","+","36.32","0.45","",
這時候對資料的概念馬上就從一堆看似亂碼的樣子變成了打開 csv 檔後出現的模樣,也就是說..想抓取的股票資訊在這串資料的下半部份
3. 成功爬取一小部份
此時可以快速地想辦法先抓取其中一小部份來映證看看,股票資料是不是真的在這串資料下頭。剛剛只是把資料印出來,現在將split()
完的結果儲存起來。
s_data = data.split('\n')
經過了split()
的處理後,資料的型態是list
,可以透過偷懶的 index 找尋股票價格資料是否在裡頭。
s_data[-1000]
印出的資料會像下方這樣
'"1232","大統益","29,681","133","4,202,864","142.00","142.00","141.00","141.50","-","0.50","141.50","1","142.00","6","23.16",\r'
當執行上面的程式碼後,可以發現股票資料確實也在裡頭! 股票資料確實如我們原本在 csv 檔中看到的,在整堆資料的下頭。
因此,前面的所有動作,就如同直接從網站上將檔案下載下來
4. 觀察資料的異同
不過,不可能慢慢的一檔一檔股票的抓,要把股票資訊從整堆CSV中萃取出來,並且整理成乾淨、整齊的模樣,因此觀察看看,股票資料和其它要忽略的資訊,有甚麼不一樣的地方。
目標資料的長度和類型,明顯和 csv 上方指數資料不同,也就是說,可以透過資料長度來過濾掉上方的資料。
再把 csv 往下拉可以看到股票的資料是和上面的權證資料連在一起的,表頭也是共用的,如此一來,剛剛用的資料長度並沒有辦法讓過濾掉這些權證、ETF的資料
接著就需要比較細節的去觀察,可以看到第一個證券代號的儲存格,只要非股票的資料,前頭都會有個 =
的符號
這個特別之處,就能夠讓幫助過濾掉不要的資料,已經很接近完成囉! 加油!
5. 利用迴圈爬取全部
當發現資料的異同後,就能夠透過給予限制過濾掉要忽略的資料,並且寫成迴圈印出來。
資料長度的部份,用剛剛找到的股票資料來查看,在 split()
之後,每行股票資料的長度應為 16,等等用 len()
函數來過濾
>>> row = s_data[-1000]
>>> row.split(',')
['"1232', '大統益', '29,681', '133', '4,202,864', '142.00', '142.00', '141.00', '141.50', '-', '0.50', '141.50', '1', '142.00', '6', '23.16",\r']
>>> len(row.split('","'))
16
而用資料內容的話,透過指定每一列資料的第一個元素的第一個符號不等於 =
來過濾掉權證、ETF等資料。綜合上面寫出程式碼如下
output = []
for d in s_data:
_d = d.split('","')
length = len(_d)
symbol = _d[0]
if length == 16 and not symbol.startswith('='):
output.append(_d)
把 output
的前幾筆印出來看看
output[0:3]
結果正確,有表頭跟前兩筆資料。
[['"證券代號', '證券名稱', '成交股數', '成交筆數', '成交金額', '開盤價', '最高價', '最低價', '收盤價', '漲跌(+/-)', '漲跌價差', '最後揭示買價', '最後揭示買量', '最後揭示賣價', '最後揭示賣量', '本益比",\r'], ['"1101', '台泥', '18,375,914', '10,223', '609,037,501', '33.10', '33.30', '33.05', '33.25', '+', '0.20', '33.20', '146', '33.25', '165', '27.25",\r'], ['"1101B', '台泥乙特', '16,381', '17', '771,372', '47.20', '47.20', '46.90', '47.20', ' ', '0.00', '46.95', '2', '47.20', '18', '0.00",\r']]
恭喜! 印出來的結果已經成功萃取出股票的資料了! 如此一來要做的就是將資料整理成乾淨的樣式。
至於整理的話,下方就直接附上程式碼和簡單的解釋,讀者可以專注在瞭解前面爬蟲的邏輯
資料清洗
資料中會有一些元素是不需要的例如 ",\r
之類,所以要把資料清理乾淨後再存起來
# 把爬下來的資料整理乾淨
output = []
for d in s_data:
_d = d.split('","')
length = len(_d)
symbol = _d[0]
if length == 16 and not symbol.startswith('='):
output.append([
ele.replace('",\r','').replace('"','')
for ele in _d
])
接著利用 pandas 把資料整理成 DataFrame
, DataFrame
是一種 pandas 提供的資料型態,像表格一樣。把整理乾淨的 output
丟給 pd.DataFrame()
,然後指定第一筆資料當表頭,指定證券代號那欄作為索引。
df = pd.DataFrame(output[1:], columns=output[0])
df.set_index('證券代號', inplace=True)
最後來把爬到的股價,用 df.to_csv()
來輸出成 csv 檔案吧!
df.to_csv('stock_price_20230928.csv')
如果一切順利,檔案打開後就像下方圖片一樣,乾乾淨淨、整整齊齊的股價資料,終於完成囉!
完整程式碼
雖然爬蟲的過程看似很複雜,但大多可以利用這 5 個簡化過的步驟來解決 ,等到越來越熟悉之後,就會內化在心裡頭,爬的越來越快,程式也會越寫越簡潔。
完整的程式碼如下,我有再改寫稍微改寫一點,這是抓一天的範本,如果要蒐集歷史資料,就逐步地往回抓每個營業日的股價就 OK 哩
import pandas as pd
import requests
# 設定目標日期
target_date = '20230928'
# 把 csv 檔抓下來
url = f'https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?date={target_date}&type=ALL&response=csv'
res = requests.get(url)
data = res.text
# 把爬下來的資料整理成需要的格式
s_data = data.split('\n')
output = []
for d in s_data:
_d = d.split('","')
length = len(_d)
symbol = _d[0]
if length == 16 and not symbol.startswith('='):
output.append([
ele.replace('",\r','').replace('"','')
for ele in _d
])
# 轉成 DataFrame 並存成 csv 檔
df = pd.DataFrame(output[1:], columns=output[0])
df.set_index('證券代號', inplace=True)
df.to_csv(f'stock_price_{target_date}.csv')
快速取得股價的方法
如果有立即的資料需求,用爬的可能來不及,這時可以透過 fmd 取得股價資料, fmd 是一個 Python 套件,讓使用者輕鬆透過 FMD API 獲取臺灣的金融市場相關資料。
安裝的方式跟一般套件相同,直接用 pip
即可
pip install fmd
舉例來說,假設需要台積電的股價,指定日期區間為 2019/1/1 到 2024/8/31,可以參考下方的範例,先用 fmd 取得資料,再透過 pandas
轉成 DataFrame
然後存成 csv
from fmd import FmdApi
import pandas as pd
fa = FmdApi()
stock = fa.stock.get('2330')
price = stock.get_price(start_date='2019-01-01', end_date='2024-08-31')
df = pd.DataFrame(price)
df.set_index('date', inplace=True)
df.to_csv('2330_stock_price.csv')
除了股價外,fmd 也提供像是股利、損益表,甚至也有 ETF 的相關資料,可以參考官方文件
下方範例展示如何取得台積電股利並存成 csv 檔。
from fmd import FmdApi
import pandas as pd
fa = FmdApi()
stock = fa.stock.get('2330')
dividend = stock.get_dividend()
df = pd.DataFrame(dividend)
df.to_csv('2330_stock_dividend.csv', index=False)
不用寫程式也能取得股價
未必要撰寫程式來取得股價,也有現成的解決方案,例如FMD-財金市場資料就有提供股價的歷史資訊,選擇股價和股票代號後,再挑選需要的日期範圍即可。
除了股價外,FMD也有提供像股利、財務報表等資訊,像ETF的股利也有整理好的歷史資料,推推。