Day20 - 用 Ruby on Rails 抓台湾证券交易所资料-每日收盘行情

前言

这篇开始会有几篇是与「台湾证券交易所」有关,示范如何用 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

# 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 时才会用到,因此可先忽略

# 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 是一个方法

# 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/

备注:之後文章修改更新,以个人部落格为主


<<:  Day24函数(JavaScript)

>>:  [Day19] Flutter with GetX something else

Day22 - 使用者身份验证

今天的实作内容主要根据教学网站进行。 Django提供了身份认证与授权系统,不论是登入、登出、密码管...

Day13: 【TypeScript 学起来】只有 TS 才有的型别: Enum (列举)

是说TS针对型别的类型也太讲究,写好多天还没写完(其实是我30篇不够XDD),哈哈不罗嗦, 今天继...

[Android Studio] 每日小技巧 - 在 Android Studio 中快速向 Google 作关键字搜索

身为开发时程紧凑的工程师 遇到问题或是疑惑时必须要能快速的排除 通常在专案中遇到不熟悉的物件,想到 ...

CSS Box-Shadow

前言 写CSS还蛮常会用到阴影,使元素更具有立体感,今天就来认识一下阴影有什麽属性吧。 首先我们要先...

DAY10-EXCEL统计分析:信赖区间

平均数的信赖区间 若想知道一个班级学期成绩的90%信赖区间 首先建立学生学期成绩的资料 点选工具表中...