免責聲明:我們盡力確保本文的正確性,但本文不代表任何投資的建議,我們也無法擔保因使用本文的內容所造成的任何損失。如對本文內容有疑問,請詢問財經相關的專家。
在本文中,我們會撰寫一個爬取 OFX 提供的外匯保證金的歷史終盤匯率的頁面以抓取相關資料的爬蟲,由於這個頁面沒有下載 CSV 資料的連結,我們也會展示如何自行生成 CSV 檔案。要注意 OFX 提供的匯率是外匯保證金 (forex) 的貨幣對,和一般在銀行的外匯不同。
以下是相對應的動作:
- 前往 OFX 的歷史匯率頁面
- 點選基礎匯率 (base currency) 的箭頭
- 選擇我們想要的基礎匯率,像
EUR
- 再點選基礎匯率的箭頭以關閉該選項框
- 點選目標匯率 (target currency) 的箭頭
- 選擇我們想要的目標匯率,像
USD
- 再點選目標匯率的箭頭以關閉該選項框
- 選擇時距 (durtion) 的箭頭
- 選擇我們想要的時距,像是 5 年
- 再選擇時距的箭頭以關閉該選項框
- 按下送出 (submit) 按鈕
- (需使用爬蟲) 抓取歷史匯率資料並寫入 CSV 檔案中
同樣地,請讀者手動操作一次,體會一下這個過程;Selenium 程式就像一個口令一個動作的士兵,指令對才會執行正確的動作。另外,由於該頁面是動態產生的,直接使用 requests 抓取會失敗。
由於完整的程式碼偏長,我們將整個程式碼放在這裡;本文會簡化並拆解這個範例。
一開始,先載入相關的模組:
import csv
import time
from selenium import webdriver
設置好相關的參數:
baseCurrency = "EUR"
targetCurrency = "USD"
targetDuration = "Last 5 years"
downloadPath = os.path.dirname(os.path.abspath(__file__))
我們為了簡化範例,我們將參數寫死。實際在撰寫這類程式時,可以用寫命令列程式的手法將其參數化,就可以創造出一個實用小工具。
使用 Chrome 瀏覽器:
# Use headless mode
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
# Create a new instance of the Chrome driver
driver = webdriver.Chrome(chrome_options=options)
我們在這裡使用 headless 模式,在這個模式下,不會有 Chrome 的 UI 畫面,只會在背景跑 Chrome。一開始在撰寫程式時不用馬上使用此模式,因為在 headless 模式下無法觀察該程式實際的動作。可在整個程式都寫完後再改為此模式即可。
前往 OFX 的歷史匯率頁面:
# Go to OFX Historical Data page
driver.get("https://www.ofx.com/en-us/forex-news/historical-exchange-rates/")
# Wait the page to fresh.
time.sleep(10)
同樣地,我們會暫停程式數秒,等頁面刷新。
接著,選取基礎匯率:
# Select base arrow.
baseArrow = driver.find_element_by_css_selector(
".historical-rates--camparison--base .select2 .selection span .select2-selection__arrow")
baseArrow.click()
time.sleep(2) # Simulate idling.
# Click on base currency.
baseOptions = driver.find_elements_by_css_selector(
".historical-rates--camparison--base select optgroup option")
for option in baseOptions:
if baseCurrency in option.text:
option.click()
break
time.sleep(1) # Simulate idling.
baseArrow.click()
time.sleep(1) # Simulate idling.
實際上,動作拆成三部分。我們先選取基礎匯率的選單的箭頭,展開選單,接著選擇我們想要的基礎匯率,最後再把選單合起來。經筆者實測,如果沒把選單合起來,會蓋掉底下的網頁元素,引發程式的錯誤。
我們用同樣的思維去選我們想要的目標匯率:
# Select target arrow.
targetArrow = driver.find_element_by_css_selector(
".historical-rates--camparison--target .select2 .selection span .select2-selection__arrow")
targetArrow.click()
time.sleep(2) # Simulate idling.
# Click on target currency.
targetOptions = driver.find_elements_by_css_selector(
".historical-rates--camparison--target select optgroup option")
for option in targetOptions:
if targetCurrency in option.text:
option.click()
break
time.sleep(1) # Simulate idling.
targetArrow.click()
time.sleep(1) # Simulate idling.
接著,我們再用同樣思維去選擇時距:
# Select target period.
periodArrow = driver.find_element_by_css_selector(
".historical-rates--period ~ .select2 .selection span .select2-selection__arrow")
periodArrow.click()
time.sleep(2) # Simulate idling.
# Click on target period.
periodOptions = driver.find_elements_by_css_selector(
".historical-rates--period option")
for option in periodOptions:
if targetDuration in option.text:
option.click()
break
time.sleep(1) # Simulate idling.
periodArrow.click()
time.sleep(1) # Simulate idling.
按下送出 (submit) 按鈕,即可得到歷史匯率數據:
# Submit for the result.
submitButton = driver.find_element_by_css_selector(".historical-rates--submit")
submitButton.click()
# Wait the page to fresh.
time.sleep(5)
同樣要等待數秒,讓頁面跑完。這時候數據還在頁面上,要用爬蟲抓取下來。
這時候,我們做了一個額外的處理,本程式抓取匯率的平均值,並定出其上下限:
avgRate = float(driver.find_element_by_css_selector(".historical-rates--table--average--value").text)
ratio = 15
upLimit = avgRate * ratio
downLimit = avgRate / ratio
這個動作不是必需的,因筆者在嘗試此程式的過程中,發現有時候該頁面偶爾會有爆高或爆低的不合理匯率,說實在的,筆者無法確認這些數值的真假,但我們會將其剔除。
我們一邊抓取資料,一邊將其寫入 CSV 檔案中:
# Save historical currency data into CSV.
with open("%sto%s.csv" % (baseCurrency, targetCurrency), 'w', newline='') as csvfile:
csvwriter = csv.writer(csvfile)
csvwriter.writerow(["Date", "Rate"])
records = driver.find_elements_by_css_selector(".historical-rates--table--results tr")
for record in records:
d = record.find_element_by_css_selector(".historical-rates--table--date").text
r = float(record.find_element_by_css_selector(".historical-rates--table--rate").text)
if r >= upLimit or r <= downLimit:
continue
csvwriter.writerow([d, r])
我們用 with
區塊來代替 close
函式,可以清楚地展示出我們這個區塊和寫入 CSV 檔案相關。在寫入 CSV 時,要用 newline=''
將換行設為空字串,以免插入額外的空行。我們另外自行寫一個 CSV 的 header,可視需求加入或略去。
在這個程式範例中,我們去除極端值,這並不是必需的,讀者可自行考慮要不要做這個動作。
最後,別忘了關掉瀏覽器:
# Close the browser.
driver.quit()