Selenium 網路爬蟲教學: 用 Selenium 到 OFX 抓外匯保證金交易資料

PUBLISHED ON OCT 23, 2018 — WEB
FacebookTwitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    免責聲明:我們盡力確保本文的正確性,但本文不代表任何投資的建議,我們也無法擔保因使用本文的內容所造成的任何損失。如對本文內容有疑問,請詢問財經相關的專家。

    在本文中,我們會撰寫一個爬取 OFX 提供的外匯保證金的歷史終盤匯率的頁面以抓取相關資料的爬蟲,由於這個頁面沒有下載 CSV 資料的連結,我們也會展示如何自行生成 CSV 檔案。要注意 OFX 提供的匯率是外匯保證金 (forex) 的貨幣對,和一般在銀行的外匯不同。

    以下是相對應的動作:

    1. 前往 OFX 的歷史匯率頁面
    2. 點選基礎匯率 (base currency) 的箭頭
    3. 選擇我們想要的基礎匯率,像 EUR
    4. 再點選基礎匯率的箭頭以關閉該選項框
    5. 點選目標匯率 (target currency) 的箭頭
    6. 選擇我們想要的目標匯率,像 USD
    7. 再點選目標匯率的箭頭以關閉該選項框
    8. 選擇時距 (durtion) 的箭頭
    9. 選擇我們想要的時距,像是 5 年
    10. 再選擇時距的箭頭以關閉該選項框
    11. 按下送出 (submit) 按鈕
    12. (需使用爬蟲) 抓取歷史匯率資料並寫入 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()
    
    TAGS: SELENIUM