Try   HackMD

爬取銀行匯率資訊

tags: python crawler

說明

爬蟲目標

爬取台灣銀行匯率網頁(https://rate.bot.com.tw/xrt?Lang=zh-TW),將所有幣別的匯率存成CSV檔案。

規格

實現步驟

  1. 確認網頁可以連線。
  2. 檢視網頁原始碼確認有要取得的資訊。
  3. 使用BeautifulSoup取得全部網頁。
  4. 研究規則並透過BeautifulSoup的各種方法(如:find()find_all()等等)來取得目標資料。

會用到的模組

模組名稱 說明
bs4 BeautifulSoup網頁解析模組
lxml 網頁解析器
requests HTTP模組,用來直接透過網路讀取網頁資料
csv 操作CSV檔案格式模組
time 取得時間

程式碼解說

1.

# text屬性為html文件原始碼
html_doc = response.text 

# 指定lxml作為解析器來建立Beautiful物件
soup = BeautifulSoup(html_doc, "lxml") 
解說

response物件中的text屬性就是我們需要的網頁原始碼,另外存到新的變數html_doc中後再交給BeautifulSoup來解析。如果不另外存到新的變數,也可以直接形成一行如下:

# 指定lxml作為解析器來建立Beautiful物件
soup = BeautifulSoup(response.text, "lxml") 

2.

rate_table = soup.find('table').find('tbody')
解說

取出匯率資訊網頁內的table內的tbody標籤,因為匯率網頁的第一個table就是匯率資訊,所以直接使用find()方法找出第一個table標籤即可。

3.

rate_table_rows = rate_table.find_all('tr')
解說

因為每一筆匯率都被一個tr標籤包圍,因此透過find_all()方法取得每一個tr標籤。

4.

if c.attrs['data-table'] == '幣別':
解說

因為每一個欄位(column)內都有data-table屬性來標示該欄位的資料標題(例如:幣別),因此透過該屬性可以針對不同欄位來做特殊處理。

attrs為每個soup的屬性,透過指定的標籤的屬性名稱,可以用來對應的屬性值。

5.

last_div = None
divs = c.find_all('div')
            
# 取得最後一個div標籤
for last_div in divs:pass
解說

這裡用了一個小技巧,因為幣別的tr標籤內有多個div標籤,而大部分都是我們不要的資訊,剛好最後一個div標籤內有我們需要的幣別(例如:美金USD),所以透過for-in迴圈的列舉特性,更新事先宣告好的變數,當一個什麼事都不做的for-in迴圈結束後,該變數內就會是最後被列舉(iterator)出來的div標籤。

6.

# 取得幣別
data.append(last_div.string.strip())
...
# 存入匯率資訊
data.append(c.getText().strip())
解說

透過string屬性取得的文字因為前後都多了一些「換行」與「空白」字元,所以使用Pythonstring物件本身提供的方法strip()來去掉。

7.

# 以目前時間建立檔名
now = time.localtime()
file_name = time.strftime('%Y%m%d_%H%M%S.csv', now)
解說

time模組內提供了strftime()方法可以用來自訂日期顯示的格式,參數說明如下:

參數 說明
%Y %Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(1-31)
%H 24小時制的時(0-23)
%M 分鐘數(00-59)
%S 秒(00-59)

8.

# 開啟輸出的 CSV 檔案
with open(file_name, 'w', newline='') as csvfile:
解說

使用 with 開啟檔案時,會將開啟的檔案放在 csvfile 變數中,但是這個 csvfile 只有在這個 with 的範圍內才可以使用,離開這個範圍後, csvfile 就會自動被關閉,並回收相關的資源,好處就是,避免忘記呼叫close()方法而造成檔案使用完畢卻沒有釋放資源,造成記憶體浪費或是檔案無法再次被開啟(open)的問題。

9.

writer.writerow(['幣別', '現金買入', '現金賣出', '即期買入', '即期賣出'])
writer.writerows(result)
解說

透過Python提供的csv模組的writer物件,呼叫writerow()方法可以寫入一列,參數為一個stirnglist,而writerows()(注意方法翠後面多了一個s),則需要提供一個listlist物件,這個list內的每一筆資料都是一個list,代表一列資料。

完整程式碼

from bs4 import BeautifulSoup
import requests
import csv
import time


# 存下全部匯率資料
result = []

# 台灣銀行匯率網址
url = 'https://rate.bot.com.tw/xrt?Lang=zh-TW'

# 使用requests物件的get方法把網頁抓下來
response = requests.get(url) 

# text屬性為html文件原始碼
html_doc = response.text 

# 指定lxml作為解析器來建立Beautiful物件
soup = BeautifulSoup(html_doc, "lxml") 

# 找到匯率內容表格
rate_table = soup.find('table').find('tbody')

rate_table_rows = rate_table.find_all('tr')

for row in rate_table_rows:
    # 解析每一列的資料
    columns = row.find_all('td')

    # 存放解析後的每一筆資料
    data = []
    
    for c in columns:
        if c.attrs['data-table'] == '幣別':
            last_div = None
            divs = c.find_all('div')
            
            # 取得最後一個div標籤
            for last_div in divs:pass
            
            # 取得幣別
            data.append(last_div.string.strip())
        elif c.getText().find('查詢') != 0 and str(c).find('print_width') > 0 :
            # 存入匯率資訊
            data.append(c.getText().strip())
    
    # 存入已解析完的一個幣別的全部匯率
    result.append(tuple(data))

print(result)

# 以目前時間建立檔名
now = time.localtime()
file_name = time.strftime('%Y%m%d_%H%M%S.csv', now)
print('輸出的檔案:', file_name)

# 開啟輸出的 CSV 檔案
with open(file_name, 'w', encoding='utf-8', newline='') as csvfile:
    # 建立 CSV 檔寫入器
    writer = csv.writer(csvfile)
    writer.writerow(['幣別', '現金買入', '現金賣出', '即期買入', '即期賣出'])
    writer.writerows(result)

使用CSS Selector的方式

from bs4 import BeautifulSoup import requests # 下載網頁用的模組 import csv import time url = 'https://rate.bot.com.tw/xrt?Lang=zh-TW' # 匯率網址 # 下載台灣銀行匯率網頁原始碼 response = requests.get(url) html_doc = response.text # 寫入本地端確認有下載完整 # with open('rate.html', 'w', encoding='utf-8') as rate: # rate.write(html_doc) soup = BeautifulSoup(html_doc, 'html.parser') # print(soup.prettify()) # 格式化下載後的網頁 tbody = soup.find('tbody') all_rates_rows = tbody.find_all('tr') all_rate_data = [] for row in all_rates_rows: rate_data = [] # 解析每一列資料 rate_data.append(row.select_one('.hidden-phone.print_show').string.strip()) rate_data.append(row.select_one('td[data-table="本行現金買入"].print_width').string.strip()) rate_data.append(row.select_one('td[data-table="本行現金賣出"].print_width').string.strip()) rate_data.append(row.select_one('td[data-table="本行即期買入"].print_width').string.strip()) rate_data.append(row.select_one('td[data-table="本行即期賣出"].print_width').string.strip()) all_rate_data.append(rate_data) # 全部資料 print(all_rate_data) # 以目前時間產生檔名 now = time.localtime() file_name = time.strftime('%Y%m%d_%H%M%S.csv', now) print('輸出的檔案名稱:', file_name) with open(file_name, 'w', encoding='utf-8', newline='') as csvfile: csvfile.write('\ufeff') # UTF-8 BOM writer = csv.writer(csvfile) # 建立CSV寫入器 writer.writerow(['幣別', '現金買入', '現金賣出', '即期買入', '即期賣出']) writer.writerows(all_rate_data)