2024年4月10日 星期三

Python 學習筆記 : 網頁爬蟲實戰 (一) 台銀牌告匯率

學完 re, requests, selenium 與 BeautifulSoup 這幾個 Python 套件後就可以來開始寫網路爬蟲了, 只有透過實戰才能真正磨練爬蟲技能. 

本系列之前的筆記參考 : 



本篇要爬的對象是台銀牌告匯率網站, 取材自 "Python 3.x 網頁資料節與與分析特訓教材" 這本書的第 2-1-1 節 : 





此網站屬於靜態網站 (不使用 Ajax 或 Javascript 產生最終內容), 只要用 HTTP GET 方法請求, 伺服器在回應時就會把網頁內容全部傳回給客戶端, 因此用 BeautifulSoup 就可以擷取網頁中的資料, 我們要擷取的目標資料是底下牌告匯率表中的各外幣匯率, 步驟如下 : 


1. 分析網頁原始碼定位目標 :  

在 Chrome 瀏覽台銀牌告匯率網站, 按滑鼠右鍵點選 "檢視原始碼" : 




目標資料放在一個 title 屬性值為 "牌告匯率" 的 table 元素裡面 : 




而匯率數據則是放在 tbody 下的 tr 元素中的 td 裡 :




所以我們應先透過 title="牌告匯率" 屬性先取的 table 元素之 Tag 物件, 然後用 find_all() 方法抓出 tbody 中的全部 tr 元素, 這樣就可以從中取得幣別名稱與匯率了. 


2. 資料擷取測試 :    

首先匯入 requests 與 bs4 套件 :

>>> import requests, bs4   

用 requests.get() 方法對網站提出 GET 請求 : 

>>> url='https://rate.bot.com.tw/xrt?Lang=zh-TW'   
>>> response=requests.get(url)                         # 提出 GET 請求
>>> soup=bs4.BeautifulSoup(response.text)    # 建立 BeautifulSoup 物件

首先利用 class='time' 屬性取得放置匯率時間的 span 元素 :

牌價最新掛牌時間:<span class="time">2024/04/10 14:48</span>

>>> the_time=soup.find('span', {'class': 'time'}).text     
>>> the_time    
'2024/04/10 19:41'    

其次擷取底下匯率表格 :

>>> table=soup.find('table', {'title': '牌告匯率'})     # 取得 table 元素的 Tag 物件

接下來利用 find_all() 取得所有 tbody 下的全部 tr 元素 (列) 之 Tag 物件串列, 然後用 for 迴圈走訪這些列元素, 取得每一列中的全部儲存格, 幣別名稱就在第 1 個儲存格, 而現金賣出匯率則在第 3 個儲存格 :

>>> for tr in table.find('tbody').find_all('tr'):    # 取得所有 tbody 下所有 tr 元素 Tag 串列
  tds=tr.find_all('td')     # 取得該列的所有 td 儲存格元素之 Tag 物件
  the_currency=tds[0].find('div', {'class': 'visible-phone'}).text.strip()    # 取得幣別
  the_rate=tds[2].text     # 取得第 3 儲存格中的台銀現金賣出匯率
  print(f'{the_currency} {the_rate}')   
  
美金 (USD) 32.28
港幣 (HKD) 4.135
英鎊 (GBP) 41.57
澳幣 (AUD) 21.59
加拿大幣 (CAD) 24.02
新加坡幣 (SGD) 24.14
瑞士法郎 (CHF) 35.81
日圓 (JPY) 0.2144
南非幣 (ZAR) -
瑞典幣 (SEK) 3.17
紐元 (NZD) 19.83
泰幣 (THB) 0.9418
菲國比索 (PHP) 0.6322
印尼幣 (IDR) 0.00238
歐元 (EUR) 35.3
韓元 (KRW) 0.02586
越南盾 (VND) 0.00145
馬來幣 (MYR) 7.222
人民幣 (CNY) 4.486

此處使用元素 div 標籤名稱與其 class 屬性從第一個 td 元素中找出含有幣別名稱的 div 元素 :

<div class="visible-phone print_hide">
     美金 (USD)
</div>

注意幣別名稱的前後有跳行, 所以用 text 屬性取得此 div 元素之內容後, 還要呼叫字串的 strip() 方法清除兩側的跳行字元與空格, 這可以由路徑取得內容來驗證 :

>>> table.tbody.td.div.find_all('div')[1]    # 取得 td 下第一層 div 下所有 div 中的第 2 個
<div class="visible-phone print_hide">
                                美金 (USD)
                            </div>
>>> table.tbody.td.div.find_all('div')[1].text    
'\r\n                                美金 (USD)\r\n                            '

可見台銀的牌告匯率網頁基本上並沒有防爬蟲機制, 連 User-Agent 都不會過濾, 可直接取得回應網頁. 


3. 匯率擷取函式 :    

最後將上面的測試寫成一個函式 get_bot_rate() :

# bot_rate.py
import requests
from bs4 import BeautifulSoup

def get_bot_rate():
    url='https://rate.bot.com.tw/xrt?Lang=zh-TW'
    response=requests.get(url)
    soup=BeautifulSoup(response.text, 'lxml')
    the_time=soup.find('span', {'class': 'time'}).text
    currency=[]
    rate=[]
    table=soup.find('table', {'title': '牌告匯率'})
    for tr in table.find('tbody').find_all('tr'):
        tds=tr.find_all('td')
        the_currency=tds[0].find('div', {'class': 'visible-phone'}).text.strip()
        the_rate=tds[2].text
        currency.append(the_currency)
        rate.append(the_rate)
    return the_time, currency, rate

if __name__ == '__main__':
    the_time, currency, rate=get_bot_rate()
    print(the_time)
    for c, r in zip(currency, rate):
        print(c, r)

此處 get_bot_rate() 會傳回一個由時間, 幣別名稱串列, 匯率串列組成的 tuple, 將傳回值 currency, rate 傳給 zip() 來產生一個 zip 物件 (以兩個串列 currency 與 rate 的對應元素配對為 tuple 組成之串列), 然後用 print() 印出 k, v 值, 結果如下 :

>>> %Run bot_rate.py
2024/04/10 22:41
美金 (USD) 32.28
港幣 (HKD) 4.135
英鎊 (GBP) 41.19
澳幣 (AUD) 21.31
加拿大幣 (CAD) 23.85
新加坡幣 (SGD) 24.02
瑞士法郎 (CHF) 35.52
日圓 (JPY) 0.2133
南非幣 (ZAR) -
瑞典幣 (SEK) 3.15
紐元 (NZD) 19.58
泰幣 (THB) 0.9375
菲國比索 (PHP) 0.6322
印尼幣 (IDR) 0.00238
歐元 (EUR) 34.98
韓元 (KRW) 0.02575
越南盾 (VND) 0.00145
馬來幣 (MYR) 7.222
人民幣 (CNY) 4.478

沒有留言 :