抓完集保資料後再接再厲, 今天改抓國發會的景氣對策信號網頁, 本篇測試參考下面這本書中的範例 (我是想趕快看完把書還掉) :
# 文科生也可以輕鬆學習網路爬蟲 : Python + Web Scraper (碁峰, 陳會安) 12-5
本系列之前的筆記參考 :
一. 檢視目標網頁 :
國發會的景氣對策信號位於官網首頁下方 :
將官網首頁往下拉到 "重要指標" 項目即可看到景氣對策信號與分數圖表 :
不過觀察原始碼可知, 首頁的這張圖只是單純的一張 png 圖檔而已 :
要按圖表左側的 "查詢系統" 進入下列網址才能找到資料 :
這是一個 Javascript 動態圖表, 當滑鼠在圖上移動時會顯示該位置的資料值. 使用 Quick Javascript Switcher 檢查, 關掉 Javascript 就會讓線圖消失, 確認此網頁乃是 Javascript 動態繪製產生, 這種網頁無法直接用 requests 套件來擷取, 但有可能透過分析其 HTTP 動態發現隱藏未公開的 API.
在 Chrome 按 F12 開啟開發人員工具視窗後重新載入目標網頁, 然後切到 "Network/XHR" 頁籤會發現左側有一個 lightscore 的 HTTP 請求, 點選它會在右方的 Preview 或 Response 出現 JSON 資料, 這就是繪製此圖所需的資料 :
在 lightscore 上按滑鼠右鍵點選 Copy/Copy URL 可得到此資源的 URL :
但是如果直接連線此網址會得到如下回應 :
這是因為通過瀏覽器網址列連線使用的是 HTTP GET 方法, 而真人操作瀏覽器從首頁連過去使用的是 POST 方法, 將開發者工具切到 Headers 頁籤即知 :
二. 使用 requests 擷取目標資料 (JSON) :
因為目標資料是 JSON 字串, 因此除了 requests 外還要匯入 json 模組 :
>>> import requests
>>> import json
>>> url='https://index.ndc.gov.tw/n/json/lightscore'
>>> res=requests.post(url)
>>> res.encoding
'utf-8'
然後呼叫 json 模組的 loads() 函式將 JSON 字串轉換成字典 :
>>> data=json.loads(res.text)
如果要用漂亮的格式列印此字典, 可呼叫 pprint.pprint() 函式 :
>>> from pprint import pprint
>>> pprint(data)
{'lang': {'highcharts_coincident': '同時指標不含趨勢指數',
'highcharts_coincident_legend': '同時指標不含趨勢指數',
'highcharts_downloadJPEG': '下載jpg',
'highcharts_downloadPDF': '下載pdf',
'highcharts_downloadPNG': '下載png',
'highcharts_downloadSVG': '下載svg',
'highcharts_lagged': '落後指標不含趨勢指數',
'highcharts_lagged_legend': '落後指標不含趨勢指數',
'highcharts_leading': '領先指標不含趨勢指數',
'highcharts_leading_legend': '領先指標不含趨勢指數',
'highcharts_lightscore': '景氣對策信號及分數',
'highcharts_printChart': '列印',
'highcharts_resetZoom': '重設縮放',
'highcharts_resetZoomTitle': '重設原比例',
'highcharts_score': '分數',
'ndc_spec_essence': '實質'},
'line': [{'x': '202305', 'y': 12},
{'x': '202306', 'y': 13},
{'x': '202307', 'y': 15},
{'x': '202308', 'y': 15},
{'x': '202309', 'y': 17},
{'x': '202310', 'y': 16},
{'x': '202311', 'y': 20},
{'x': '202312', 'y': 22},
{'x': '202401', 'y': 27},
{'x': '202402', 'y': 29},
{'x': '202403', 'y': 31},
{'x': '202404', 'y': 35}],
'next': '2024-06-27 16:00',
'right': {'10': {'code': 'SR0037',
'd': [{'m': '202403', 'n': 3}, {'m': '202404', 'n': 5}],
'lang': '機械及電機設備進口值',
'sort': 7,
'trans': 'default'},
'11': {'code': 'SR0057',
'd': [{'m': '202403', 'n': 3}, {'m': '202404', 'n': 5}],
'lang': '批發、零售及餐飲業營業額',
'sort': 8,
'trans': 'default'},
'3': {'code': 'SR0031',
'd': [{'m': '202403', 'n': 2}, {'m': '202404', 'n': 2}],
'lang': '貨幣總計數 M1B',
'sort': 0,
'trans': 'default'},
'4': {'code': 'SR0033',
'd': [{'m': '202403', 'n': 5}, {'m': '202404', 'n': 5}],
'lang': '股價指數',
'sort': 1,
'trans': 'default'},
'5': {'code': 'SR0034',
'd': [{'m': '202403', 'n': 4}, {'m': '202404', 'n': 5}],
'lang': '工業生產指數',
'sort': 2,
'trans': 'default'},
'6': {'code': 'SR0055',
'd': [{'m': '202403', 'n': 3}, {'m': '202404', 'n': 4}],
'lang': '製造業銷售量指數',
'sort': 3,
'trans': 'default'},
'7': {'code': 'SR0056',
'd': [{'m': '202403', 'n': 3}, {'m': '202404', 'n': 3}],
'lang': '製造業營業氣候測驗點',
'sort': 4,
'trans': 'up'},
'8': {'code': 'SR0035',
'd': [{'m': '202403', 'n': 3}, {'m': '202404', 'n': 3}],
'lang': '工業及服務業加班工時',
'sort': 5,
'trans': 'default'},
'9': {'code': 'SR0036',
'd': [{'m': '202403', 'n': 5}, {'m': '202404', 'n': 3}],
'lang': '海關出口值',
'sort': 6,
'trans': 'default'}}}
其中 line 屬性的 (x, y) 座標就是過去 12 個月 (x) 的景氣分數 (y). 關於 pprint 用法參考 :
呼叫 json 模組的 dump() 函式則可以將結果字典寫入 ,json 檔 :
>>> with open('monitoring_indicators.json', 'w', encoding='utf-8') as f:
json.dump(data, f)
關於 json 模組用法參考 :
可見這種 Javascript 生成的網頁只要能找出隱藏的 API, 其實也很好擷取, 也毋須動用 Selenium, 而且這種隱藏的 API 網頁通常沒有防爬機制, 像這個景氣對策信號網頁, 它甚至都不檢查 User-Agent, 直接 POST 即可取得.
沒有留言 :
張貼留言