證交所提供完整且及時之股票資料, 大部分都可以使用 requests + BeautifulSoup 抓取, 經過清理後可用來進行量化分析 (但要注意爬取頻率不要太高, 否則會被反爬蟲程式鎖 IP 阻擋一段時間).
本篇測試是閱讀劉承彥老師寫的 "Python股票演算法交易實務 147 個關鍵技巧詳解" (第二版, 博碩 2021, 此書已經絕版) 第二章技巧 30 的實測紀錄.
本系列爬蟲測試筆記索引參考 :
證交所的個股日本益比, 殖利率及股價淨值比查詢網址如下 :
在上方選擇資料年月, 輸入股票代號按 "查詢" 就會顯示該標的的全月之日資料 :
利用瀏覽器的開發者工具分析, 此網頁是透過下列網址從後端伺服器取得資料 :
注意, date 參數可傳入該月之任一日期, 不管填哪一日期都會傳回當月之每日資料 (JSON 格式字串), 例如 :
{"stat":"OK","date":"20250801","title":"114年08月 台積電 個股日本益比、殖利率及股價淨值比(以個股月查詢)","fields":["日期","殖利率(%)","股利年度","本益比","股價淨值比","財報年/季"],"data":[["114年08月01日","1.49",113,"22.58","6.48","114/1"],["114年08月04日","1.50",113,"22.48","6.45","114/1"],["114年08月05日","1.48",113,"22.78","6.53","114/1"],["114年08月06日","1.51",113,"22.28","6.39","114/1"],["114年08月07日","1.44",113,"23.37","6.70","114/1"],["114年08月08日","1.45",113,"23.27","6.68","114/1"],["114年08月11日","1.44",113,"23.37","6.70","114/1"],["114年08月12日","1.44",113,"23.37","6.70","114/1"],["114年08月13日","1.42",113,"23.77","6.82","114/1"],["114年08月14日","1.45",113,"20.87","6.65","114/2"],["114年08月15日","1.44",113,"20.96","6.68","114/2"],["114年08月18日","1.44",113,"20.96","6.68","114/2"],["114年08月19日","1.43",113,"21.05","6.71","114/2"],["114年08月20日","1.50",113,"20.16","6.43","114/2"],["114年08月21日","1.48",113,"20.43","6.51","114/2"],["114年08月22日","1.50",113,"20.16","6.43","114/2"],["114年08月25日","1.45",113,"20.79","6.62","114/2"],["114年08月26日","1.45",113,"20.87","6.65","114/2"],["114年08月27日","1.43",113,"21.14","6.74","114/2"],["114年08月28日","1.47",113,"20.61","6.57","114/2"],["114年08月29日","1.47",113,"20.61","6.57","114/2"]],"total":21}
這就是用來渲染上面網頁所需的資料. 這個網址格式可寫成如下 f 字串 :
f'https://www.twse.com.tw/exchangeReport/BWIBBU?response=json&date={date}&stockNo={stock_no}'
這個 JSON 資料可以用 json 套件轉成 Python 字典方便後續處理 (例如轉成 DataFrame). 首先匯入 requests 與 json 套件 :
>>> import requests, json
定義日期與股票待號變數 :
>>> date='20250801'
>>> stock_no='2330'
然後將這兩個參數傳入資料取得網址的 f 字串中 :
>>> url=f'https://www.twse.com.tw/exchangeReport/BWIBBU?response=json&date={date}&stockNo={stock_no}'
>>> url
'https://www.twse.com.tw/exchangeReport/BWIBBU?response=json&date=20250801&stockNo=2330'
這樣便可以用 requests.get() 取得資料了 :
>>> res=requests.get(url)
爬取的資料是 JSON 格式的字串, 放在回應物件的 text 屬性中, 可以用 json.loads() 函式將此字串轉成 Python 字典, 用法參考 :
>>> dic_obj=json.loads(res.text)
>>> type(dic_obj)
<class 'dict'>
可以用 rich 套件的 print() 以結構化形式顯示字典內容 :
>>> from rich import print as pprint
>>> pprint(dic_obj)
{
'stat': 'OK',
'date': '20250801',
'title': '114年08月 台積電
個股日本益比、殖利率及股價淨值比(以個股月查詢)',
'fields': [
'日期',
'殖利率(%)',
'股利年度',
'本益比',
'股價淨值比',
'財報年/季'
],
'data': [
['114年08月01日', '1.49', 113, '22.58', '6.48', '114/1'],
['114年08月04日', '1.50', 113, '22.48', '6.45', '114/1'],
['114年08月05日', '1.48', 113, '22.78', '6.53', '114/1'],
['114年08月06日', '1.51', 113, '22.28', '6.39', '114/1'],
['114年08月07日', '1.44', 113, '23.37', '6.70', '114/1'],
['114年08月08日', '1.45', 113, '23.27', '6.68', '114/1'],
['114年08月11日', '1.44', 113, '23.37', '6.70', '114/1'],
['114年08月12日', '1.44', 113, '23.37', '6.70', '114/1'],
['114年08月13日', '1.42', 113, '23.77', '6.82', '114/1'],
['114年08月14日', '1.45', 113, '20.87', '6.65', '114/2'],
['114年08月15日', '1.44', 113, '20.96', '6.68', '114/2'],
['114年08月18日', '1.44', 113, '20.96', '6.68', '114/2'],
['114年08月19日', '1.43', 113, '21.05', '6.71', '114/2'],
['114年08月20日', '1.50', 113, '20.16', '6.43', '114/2'],
['114年08月21日', '1.48', 113, '20.43', '6.51', '114/2'],
['114年08月22日', '1.50', 113, '20.16', '6.43', '114/2'],
['114年08月25日', '1.45', 113, '20.79', '6.62', '114/2'],
['114年08月26日', '1.45', 113, '20.87', '6.65', '114/2'],
['114年08月27日', '1.43', 113, '21.14', '6.74', '114/2'],
['114年08月28日', '1.47', 113, '20.61', '6.57', '114/2'],
['114年08月29日', '1.47', 113, '20.61', '6.57', '114/2']
],
'total': 21
}
可見目標資料放在 data 屬性中 :
>>> pprint(dic_obj['data'])
[
['114年08月01日', '1.49', 113, '22.58', '6.48', '114/1'],
['114年08月04日', '1.50', 113, '22.48', '6.45', '114/1'],
['114年08月05日', '1.48', 113, '22.78', '6.53', '114/1'],
['114年08月06日', '1.51', 113, '22.28', '6.39', '114/1'],
['114年08月07日', '1.44', 113, '23.37', '6.70', '114/1'],
['114年08月08日', '1.45', 113, '23.27', '6.68', '114/1'],
['114年08月11日', '1.44', 113, '23.37', '6.70', '114/1'],
['114年08月12日', '1.44', 113, '23.37', '6.70', '114/1'],
['114年08月13日', '1.42', 113, '23.77', '6.82', '114/1'],
['114年08月14日', '1.45', 113, '20.87', '6.65', '114/2'],
['114年08月15日', '1.44', 113, '20.96', '6.68', '114/2'],
['114年08月18日', '1.44', 113, '20.96', '6.68', '114/2'],
['114年08月19日', '1.43', 113, '21.05', '6.71', '114/2'],
['114年08月20日', '1.50', 113, '20.16', '6.43', '114/2'],
['114年08月21日', '1.48', 113, '20.43', '6.51', '114/2'],
['114年08月22日', '1.50', 113, '20.16', '6.43', '114/2'],
['114年08月25日', '1.45', 113, '20.79', '6.62', '114/2'],
['114年08月26日', '1.45', 113, '20.87', '6.65', '114/2'],
['114年08月27日', '1.43', 113, '21.14', '6.74', '114/2'],
['114年08月28日', '1.47', 113, '20.61', '6.57', '114/2'],
['114年08月29日', '1.47', 113, '20.61', '6.57', '114/2']
]
而欄位則是放在 fields 屬性中 :
>>> dic_obj['fields']
['日期', '殖利率(%)', '股利年度', '本益比', '股價淨值比', '財報年/季']
可以呼叫 open() 函式將此字典存入檔案 :
>>> with open('twse_per_yield_pbr.json', 'w', encoding='utf-8') as f:
json.dump(dic_obj, f)
如果要讀回成字典則可呼叫 json.load() :
>>> with open('twse_per_yield_pbr.json', 'r', encoding='utf-8') as f:
dic_obj=json.load(f)
但通常會存入資料庫, 存取較方便.
以上測試之完整程式碼如下 :
# twse_per_yield_pbr.py
import requests
import json
from fake_useragent import UserAgent
def get_per_yield_pbr(date, stock_no):
url=f'https://www.twse.com.tw/exchangeReport/BWIBBU?response=json&date={date}&stockNo={stock_no}'
ua=UserAgent()
headers={'User-Agent': ua.random}
res=requests.get(url, headers=headers)
if res.status_code == requests.codes.ok:
data=json.loads(res.text)
return data
else:
print(f'Error: {res.status_code}, Response: {res.text}')
return False
if __name__ == '__main__':
date=input('請輸入日期 (格式 YYYYMMDD) :')
stock_no=input('請輸入股票代號 (例如 2330) :')
data=get_per_yield_pbr(date, stock_no)
if data:
print(data['fields'])
print(data['data'])
else:
print('爬取網頁失敗')
執行結果如下 :
>> %Run twse_per_yield_pbr.py
請輸入日期 (格式 YYYYMMDD) :20250801
請輸入股票代號 (例如 2330) :2330
['日期', '殖利率(%)', '股利年度', '本益比', '股價淨值比', '財報年/季']
[['114年08月01日', '1.49', 113, '22.58', '6.48', '114/1'], ['114年08月04日', '1.50', 113, '22.48', '6.45', '114/1'], ['114年08月05日', '1.48', 113, '22.78', '6.53', '114/1'], ['114年08月06日', '1.51', 113, '22.28', '6.39', '114/1'], ['114年08月07日', '1.44', 113, '23.37', '6.70', '114/1'], ['114年08月08日', '1.45', 113, '23.27', '6.68', '114/1'], ['114年08月11日', '1.44', 113, '23.37', '6.70', '114/1'], ['114年08月12日', '1.44', 113, '23.37', '6.70', '114/1'], ['114年08月13日', '1.42', 113, '23.77', '6.82', '114/1'], ['114年08月14日', '1.45', 113, '20.87', '6.65', '114/2'], ['114年08月15日', '1.44', 113, '20.96', '6.68', '114/2'], ['114年08月18日', '1.44', 113, '20.96', '6.68', '114/2'], ['114年08月19日', '1.43', 113, '21.05', '6.71', '114/2'], ['114年08月20日', '1.50', 113, '20.16', '6.43', '114/2'], ['114年08月21日', '1.48', 113, '20.43', '6.51', '114/2'], ['114年08月22日', '1.50', 113, '20.16', '6.43', '114/2'], ['114年08月25日', '1.45', 113, '20.79', '6.62', '114/2'], ['114年08月26日', '1.45', 113, '20.87', '6.65', '114/2'], ['114年08月27日', '1.43', 113, '21.14', '6.74', '114/2'], ['114年08月28日', '1.47', 113, '20.61', '6.57', '114/2'], ['114年08月29日', '1.47', 113, '20.61', '6.57', '114/2']]
此程式使用第三方套件 fake_useragent 產生 HTTP 標頭的 User-Agent 用來偽裝成一般瀏覽器, 避免被證交所伺服器反爬蟲程式阻擋, 用法參考 :


沒有留言 :
張貼留言