昨晚回到鄉下, 早上去晉德針灸回來後繼續來玩爬蟲 (希望能在六月底結束這場已耗時近三個月的爬蟲戰爭), 今天要爬的對象是從下面陳會安老師的這本書看到的範例 :
# 文科生也可以輕鬆學習網路爬蟲 : Python + Web Scraper (碁峰, 陳會安) 14-3
此範例是要擷取新加坡交易所 (SGX) 的富時中國 A50 指數期貨報價資訊, 該指數由英國富時公司編製, 目標是追蹤中國滬深交易所市值最大的 50 家 A 股公司的市場表現.
本系列之前的筆記參考 :
一. 檢視目標網頁 :
新加坡交易所 (SGX) 的富時中國 A50 指數期貨報價資訊網址如下 :
可見目標資料是以表格方式呈現, 但檢視原始碼會發現裡面一個 table 元素也沒有, 網頁應該是 Javascript 動態產生 :
這可用 Quick Javascript Switcher 來確認, 當關閉 Javascript 功能時整個表格會消失不見, 可見此網頁確實是透過 Javascript 動態產生, 此種網頁無法直接用 requests 套件來擷取 (因為網頁中沒有實體資料, 是網頁載入後才繪製的). 不過, 透過分析 HTTP 請求流程, 有機會找到含有目標資料的未公開 API, 通常是 JSON (主流) 或 XML (較少見了) 格式之資料.
在 Chrome 按 F12 開啟開發人員工具視窗並切換到 "Network/XHR" 頁籤, 然後重新整理目標網頁, 左下方視窗繪出現瀏覽器所提出的 HTTP 請求, 逐一點選並觀察右下方視窗的 Preview 內容, 可發現其中 CN?order=asc&orderby=... 這個請求的 Preview 內容就是繪製網頁表格的資料來源 (通常是檔案大小較大那個 URL) :
也可以切到 Response 頁籤觀察回應訊息來確認 :
切到 Header 頁籤可知此請求使用 HTTP GET 方法 :
在 CN?order=asc&orderby=... 請求上按滑鼠右鍵點選 Copy/Copy URL 即可複製此請求之完整網址, 將其貼到瀏覽器網址列 :
事實上最後面兩個參數 session 與 t 不給也是可以的 :
這就是隱藏在背後未公開的 API, 與上一篇測試的景氣對策信號網頁處理方式類似, 只要從 HTTP 請求流程中找出目標資料的 API 網址, 接下來就可以用 requests 套件來擷取它了, 但要注意的是發出 HTTP 請求的方法, 務必依照真人操作瀏覽器所用的方法, 例如此例用的是 GET 方法, 而前一篇景氣對策信號網頁用的是 POST 方法, 方法用錯可能無法擷取到資料, 因為不是每個伺服器都會同時實作資源的 GET 與 POST 方法.
由於是使用 GET 方法, 可以將上面網址直接貼到瀏覽器網址列來瀏覽此 JSON 資料 :
現在新版瀏覽器都有一個 "美化排版" 的貼心功能, 勾選它就會將 JSON 格式資料以鍵值對方式整齊排列以增進可閱讀性. 由上可知, 目標資料都放在 data 這個鍵裡面.
二. 使用 requests 擷取目標資料 (JSON) :
先匯入會用到的套件模組 :
>>> import requests
>>> import json
>>> from pprint import pprint
用 GET 方法擷取目標資料並且用 json.loads() 將回應的 JSON 字串轉成 Python 字典 :
>>> url='https://api.sgx.com/derivatives/v1.0/contract-code/CN?order=asc&orderby=delivery-month&category=futures'
>>> res=requests.get(url) # 注意此例必須用 get()
>>> res.encoding
'utf-8'
>>> data=json.loads(res.text)
用 pprint.pprint() 來檢視 data 鍵之值 (很長略去) :
>>> pprint(data['data'])
[{'aggregate-total-volume': 337725.0,
'base-date': '20240621',
'best-ask-price': 1206500.0,
'best-ask-price-abs': 12065.0,
'best-ask-price-adj': 12065.0,
'best-ask-quantity': 5.0,
'best-bid-price': 1205100.0,
'best-bid-price-abs': 12051.0,
'best-bid-price-adj': 12051.0,
'best-bid-quantity': 9.0,
'category': 'futures',
'change': -13100.0,
'change-abs': 131.0,
'change-adj': -131.0,
'change-percentage': -1.0753570842226234,
'contract-code': 'CN',
'contract-name': 'SGX FTSE China A50 Index Futures',
'current-trading-session': '0',
'daily-settlement-price': 1205100.0,
'daily-settlement-price-abs': 12051.0,
'daily-settlement-price-adj': 12051.0,
'default-dsp': '2',
'delivery-month': '2024-06',
'derivative-type': 'equityindex',
'first-trading-date': '2023-06-29',
'it': 'cnfc',
'last-trade-price': 1205100.0,
'last-traded-price-abs': 12051.0,
'last-traded-price-adj': 12051.0,
'last-trading-date': '2024-06-26',
'last-update-time': '2024-06-22 05:07:18.0',
'open-interest': 963882.0,
'outright-traded-volume': 305652.0,
'p': 'X',
'preliminary-settlement-price': 1218200.0,
'preliminary-settlement-price-abs': 12182.0,
'preliminary-settlement-price-adj': 12182.0,
...(略)...
'session-close': '-',
'session-close-abs': None,
'session-close-adj': None,
'session-open': None,
'session-open-abs': None,
'session-open-adj': None,
'session-traded-high': None,
'session-traded-high-abs': None,
'session-traded-high-adj': None,
'session-traded-low': None,
'session-traded-low-abs': None,
'session-traded-low-adj': None,
'spread-volume': 0.0,
'strike-price': None,
'symbol': 'CNH25',
'total-volume': 0.0,
'updated-time': 1719007206472,
'v': '',
'volume-session': '1',
'volume-trade': 0.0}]
由於目標資料較大, 我們可將 data 字典存入 json 檔案 :
>>> with open('sgx-ftse-a50.json', 'w', encoding='utf-8') as f:
json.dump(data, f)
完整程式碼如下 :
import requests
import json
url='https://api.sgx.com/derivatives/v1.0/contract-code/' +\
'CN?order=asc&orderby=delivery-month&category=futures'
res=requests.get(url)
data=json.loads(res.text) # 亦可用 data=res.json()
with open('sgx-ftse-a50.json', 'w', encoding='utf-8') as f:
json.dump(data, f)
print('擷取結果儲存於 ./sgx-ftse-a50.json 檔案')
執行結果如下 :
>>> %Run sgx-ftse-a50-test-1.py
擷取結果儲存於 ./sgx-ftse-a50.json 檔案
三. 擷取關鍵欄位存成 csv 檔 :
從上面取得的目標資料可知, data['data'] 本身也是一個字典, 它包含相當多的鍵 (欄位), 其中對交易者較關鍵者如下 :
- last-update-time: 最近更新時間
- last-trading-date: 最近到期日
- symbol: 代號
- current-trading-session: 盤別
- change-abs: 漲跌
- change-percentage: 漲跌幅
- session-open-abs: 開盤價
- session-traded-high-abs: 最高價
- session-traded-low-abs: 最低價
- last-traded-price-abs: 收盤價
- daily-settlement-price-abs: 結算價
- total-volume: 成交量
- open-interest: 未平倉量
下列程式碼從 data['data'] 中擷取這些關鍵欄位並存成 csv 檔 :
import requests
import csv
url='https://api.sgx.com/derivatives/v1.0/contract-code/' +\
'CN?order=asc&orderby=delivery-month&category=futures'
res=requests.get(url)
data=res.json()
csv_file='sgx-ftse-a50-2.csv'
with open(csv_file, 'w+', newline='', encoding='utf-8') as f: # newline='' 避免 Excel 空列
writer=csv.writer(f)
header=['最近更新時間',
'最近到期日',
'代號',
'盤別',
'漲跌',
'漲跌幅',
'開盤價',
'最高價',
'最低價',
'收盤價',
'結算價',
'成交量',
'未平倉量']
writer.writerow(header) # 寫入標頭列
for item in data['data']: # 遍歷所有項目
row=[] # 儲存每列資料用
row.append(item['last-update-time'])
row.append(item['last-trading-date'])
row.append(item['symbol'])
if item['current-trading-session']=='0': # 改換顯示
row.append('T') # 當日交易日
else:
row.append('T+') # 次交易日
row.append(item['change-abs'])
row.append(item['change-percentage'])
row.append(item['session-open-abs'])
row.append(item['session-traded-high-abs'])
row.append(item['change-percentage'])
row.append(item['session-traded-low-abs'])
row.append(item['last-traded-price-abs'])
row.append(item['daily-settlement-price-abs'])
row.append(item['total-volume'])
row.append(item['open-interest'])
writer.writerow(row) # 寫入列
print(f'擷取結果儲存於 ./{csv_file} 檔案')
執行結果如下 :
>>> %Run sgx-ftse-a50-test-2.py
擷取結果儲存於 ./sgx-ftse-a50-2.csv 檔案
用純文字編輯器開啟此 csv 檔 :
因 Excel 預設是 ANSI 編碼, 若直接用 Excel 開啟此 UTF-8 編碼的 csv 檔, 則中文會變成亂碼 :
解決之道是先用記事本開啟此 csv 檔, 然後另存新檔 (可用不同檔名, 例如後面加 BOM, 以免覆蓋原檔), 並選擇編碼格式為 "使用 BOM 之 UTF-8" 而非單純之 "UTF-8" :
這樣用 Excel 開啟 sgx-ftse-a50-2-BOM.csv 時就不會出現亂碼了 :
沒有留言 :
張貼留言