你必須很努力

Day20 - 用 Ruby on Rails 抓臺灣證券交易所資料-每日收盤行情

2021/10/03
字數統計: 1.1k閱讀時間: 4 min

前言

這篇開始會有幾篇是與「臺灣證券交易所」有關,示範如何用 Ruby on Rails 來爬蟲將資料抓回來處理,並自己建立 DB,方便自己在 Local 可以測試

這部分起,不提供 sample pr or repo (個人有放 GitHub private,目前沒打算公開)

預計會示範如何抓「每日收盤行情」、「除權除息計算結果表」、設計 DB 架構、寫點簡易的技術指標選股

謎之音: 其實是原本規劃的題目,寫到膩了,想調整下內容 (才不承認是拿之前自己做的 Side Project 來擠牙膏)

說明

這邊不會教股票相關的知識,我也只是一隻小菜鳥,會以技術實作層面呈現,若有寫錯或更好的寫法,歡迎不吝指教

取得「每日收盤行情」CSV 檔

目標是從臺灣證券交易所的「每日收盤行情」取得每日的 CSV 檔

note: 本資訊自民國93年2月11日起提供

下載的檔案內容如下

從上面已經知道,只提供 2004-02-11 之後的檔案

實作

預計會用到以下 3 個 Gem,分別是 iconvdownactiverecord-import

note: Mac 的話,iconv 可以不用裝,最初是在 Windows 上寫的,後來變成在兩種作業系統輪流寫 XD

1
2
3
4
5
# Gemfile

gem 'iconv', '~> 1.0', '>= 1.0.8'
gem 'down', '~> 5.2', '>= 5.2.2'
gem 'activerecord-import', '~> 1.1'

由於已經知道下載、儲存 DB 會有許多方法是共用的,因此這邊直接抽 helpers

note: 剛開始做的時候不知道,邊做邊重構,慢慢整理的,其中 decode_data 這方法會在存 DB 時才會用到,因此可先忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# app/features/twse/helpers.rb

module Twse
module Helpers

BASE_URL = "https://www.twse.com.tw/".freeze

private

# ----- download start -----
def upload_to_github
need_update_github = `cd data/twse && git remote -v`

system('cd data/twse && git add . && git commit -m "update data" && git push') if need_update_github.present?
end
# ----- download end -----

# ----- save_to_db start -----
def decode_data(file_path, is_linux)
if is_linux
# for windows Ubuntu
file = File.open(file_path, "r:UTF-8")
decoded_data = Iconv.iconv("utf-8", "big-5", file.read)
decoded_data[0].split("\r\n")
else
# for mac
file = File.open(file_path, "r:BIG5")
decoded_data = file.read
decoded_data.split("\r\n")
end
end
# ----- save_to_db end -----

end
end

下載每日收盤行情中的「每日收盤行情(全部(不含權證、牛熊證、可展延牛熊證))」

note: 不能太密集的抓資料,會被 Bang,至於間隔幾秒怎麼知道的,trial and error 是一個方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# app/features/twse/allbut_0999/download.rb

module Twse::Allbut0999
class Download

include Twse::Helpers

def execute
current_time = Time.current
puts "#{self.class}, start_time: #{current_time.to_s}"
data_path = Rails.root.join("data/twse/ALLBUT0999")
start_date = find_latest_transaction_date(current_time)
return puts "#{self.class}, 已經是最新的資料" if start_date == false

(start_date..Date.current).each do |date|
month_path = data_path.join(date.year.to_s, date.month.to_s)
file_path = month_path.join("MI_INDEX_ALLBUT0999_#{date.strftime("%Y%m%d")}.csv")
next if File.exists?(file_path) || date.sunday?

FileUtils.mkdir_p(month_path) unless File.directory?(month_path)

download_file(date, month_path)
sleep 3 # 太密集抓資料會被 bang
end
upload_to_github
puts "#{self.class}, done_time:#{Time.current}, #{(Time.current - current_time).to_s} sec"
rescue StandardError => e
puts "errors: #{e.inspect}, backtrace: #{e.backtrace}"
end

private

def find_latest_transaction_date(current_time)
latest_transaction_date = DailyQuote.latest_transaction_date
if latest_transaction_date
return false if latest_transaction_date == current_time

latest_transaction_date + 1.day
else
Date.parse("2004-02-11") # 僅支援抓 2014-02-11 之後的資料
end
end

def download_file(date, month_path)
remote_file = Down.download("#{BASE_URL}exchangeReport/MI_INDEX?response=csv&date=#{date.strftime("%Y%m%d")}&type=ALLBUT0999")

if remote_file.size.zero?
FileUtils.rm_rf(remote_file.path)
else
FileUtils.mv(remote_file.path, month_path.join(remote_file.original_filename))
end
end

end
end

小結

製作的過程,踩到蠻多雷的 (ex: 下載要間隔幾秒、如何處理資料...等),可能與一開始在 Windows 上開發有關,逐一解決遇到的問題,還蠻好玩的,主要是享受開發的過程

上面有寫 upload_to_github 這方法,將抓下來的資料,自動上傳到 GitHub repo,若只是想單純試看看,沒有要自己建 repo 的話,這段可移除

find_latest_transaction_date 中的 DailyQuote 為建立的 Model,這篇可以先略過,後面會有一篇文章說明 DB 的設計

另外也可以到「臺灣證券交易所 OpenAPI」打 API 取資料,這邊就不多闡述了

下一篇會示範如何抓「除權除息計算結果表」的資料


鐵人賽文章連結:https://ithelp.ithome.com.tw/articles/10272778
medium 文章連結:https://link.medium.com/DtL2qRIuTjb
本文同步發布於 小菜的 Blog https://riverye.com/

備註:之後文章修改更新,以個人部落格為主

CATALOG
  1. 1. 前言
  2. 2. 說明
  3. 3. 取得「每日收盤行情」CSV 檔
    1. 3.1. 下載的檔案內容如下
  4. 4. 實作
  5. 5. 小結