2024年6月21日 星期五

Python 學習筆記 : 網頁爬蟲實戰 (十二) 國發會景氣對策信號

抓完集保資料後再接再厲, 今天改抓國發會的景氣對策信號網頁, 本篇測試參考下面這本書中的範例 (我是想趕快看完把書還掉) : 


本系列之前的筆記參考 : 



一. 檢視目標網頁 :  

國發會的景氣對策信號位於官網首頁下方 : 


將官網首頁往下拉到 "重要指標" 項目即可看到景氣對策信號與分數圖表 :




不過觀察原始碼可知, 首頁的這張圖只是單純的一張 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 即可取得.

沒有留言 :