D4-用 Swift 和公开资讯,打造投资理财的 Apps { 下载公司股票代号和股票名称等基本资料 }

第一个要下载的资料,就是台湾所有上市公司的代号 vs. 名称,因为在後续的资料中,可能有些资料只会有代号,也可能有些资料只会有名称,所以这个铁人赛第一步,决定先下载股票代号对照表。而下面所提到的资料表,连公司基本资讯都有。

这些资料放在政府的资料开放平台上,不过我们只需要下载位置就可以了。

上市公司资料

https://mopsfin.twse.com.tw/opendata/t187ap03_L.csv

上柜公司资料

https://mopsfin.twse.com.tw/opendata/t187ap03_O.csv

兴柜公司资料

https://mopsfin.twse.com.tw/opendata/t187ap03_R.csv

决定好目标之後,先分析需要哪些 column。

https://ithelp.ithome.com.tw/upload/images/20210913/2014062280rZcyBSoa.png

下载完 csv 档之後,我会需要[公司代号]、[公司名称]、[公司简称]、[资本额],这些栏位。

那装载资料的 Data Model 就会是这样

import Foundation

struct StockBasicInfo {
    
    let stockCode: String
    let stockName: String
    let companyName: String
    let capital: String
}

这个 csv 档在 iOS 这边也有套件可以处理,我选择用 SwiftCSV

安装方法说明文件在下方 repo 的 readme

https://github.com/swiftcsv/SwiftCSV

和 Alamofire 一样,我们要有个 adapter 来包第三方套件。


import Foundation
import SwiftCSV

struct CSVAdapter {
    
    var header = [String]()
    var namedRows = [[String: String]]()
    var namedColumns = [String: [String]]()
    
    init?(rawString: String) {
        
        if let csv = try? CSV(string: rawString) {
            self.header = csv.header
            self.namedRows = csv.namedRows
            self.namedColumns = csv.namedColumns
        }
    }
}

接下来就是写一个专门处理股票资讯的类别,在基本资料上,因为上市、上柜、兴柜的资料被放在三个不同的地方,所以在 request function 上,会先分成三个 func,後续可以看情况再决定是否要再开一个 func 对这三个 func 进行连续呼叫。

import Foundation

/// 这一个类别主要处理上市上柜兴兴公司相关资料
class StockInfoManager {
    
    private lazy var alamofireAdapter: AlamofireAdapter = {
        return AlamofireAdapter()
    }()
    
    /// 取得上市公司基本资料
    func requestTwStockCodeAndName(completion: @escaping (([StockBasicInfo], Error?) -> Void)) {
        
        let urlString = "https://mopsfin.twse.com.tw/opendata/t187ap03_L.csv"
        
        requestStockInfoBasic(urlString) { list, error in
            completion(list, error)
        }
    }
    
    /// 取得上柜公司基本资料
    func requestOTCCodeAndName(completion: @escaping (([StockBasicInfo], Error?) -> Void)) {
        
        let urlString = "https://mopsfin.twse.com.tw/opendata/t187ap03_O.csv"
        
        requestStockInfoBasic(urlString) { list, error in
            completion(list, error)
        }
    }
    
    /// 取得兴柜公司基本资料
    func requestEmerginCodeAndName(completion: @escaping (([StockBasicInfo], Error?) -> Void)) {
        
        let urlString = "https://mopsfin.twse.com.tw/opendata/t187ap03_R.csv"
        
        requestStockInfoBasic(urlString) { list, error in
            completion(list, error)
        }
    }
    
    private func requestStockInfoBasic(_ urlString: String, completion: @escaping ([StockBasicInfo], Error?) -> Void) {
        
        var companyList = [StockBasicInfo]()
        
        alamofireAdapter.request(urlString, method: .get) { data, response, error in
            
            if let error = error {
                print("tw stock fetch error: \(error.localizedDescription)")
                completion(companyList, error)
                return
            }
            
            if let data = data,
               let string = String(data: data, encoding: .utf8),
               let csv = CSVAdapter(rawString: string) {
                
                for company in csv.namedRows {
                    
                    let stockCode = company["公司代号"] ?? ""
                    let stockName = company["公司简称"] ?? ""
                    let companyName = company["公司名称"] ?? ""
                    let capital = company["实收资本额"] ?? ""
                    
                    let info = StockBasicInfo(stockCode: stockCode, stockName: stockName, companyName: companyName, capital: capital)
                    companyList.append(info)
                }
            }
            
            completion(companyList, nil)
        }
    }
}

当完成後,可以在 VC 里面试着打 request,看有没有回应。可以试着印出 csv.headers,如果有印出下列文字,就表示有拿到资料

["出表日期", "公司代号", "公司名称", "公司简称", "外国企业注册地国", "产业别", "住址", "营利事业统一编号", "董事长", "总经理", "发言人", "发言人职称", "代理发言人", "总机电话", "成立日期", "上市日期", "普通股每股面额", "实收资本额", "私募股数", "特别股", "编制财务报表类型", "股票过户机构", "过户电话", "过户地址", "签证会计师事务所", "签证会计师1", "签证会计师2", "英文简称", "英文通讯地址", "传真机号码", "电子邮件信箱", "网址"]

而使用 csv.namedRows 就可以操作 csv 档里面的 row。如果要操作 column,就用 csv.namedColumns。

试着印出第 0 个 namedRows,资讯应该如下。

["过户电话": "66365566", "签证会计师事务所": "勤业众信联合会计师事务所", "发言人": "黄健强", "过户地址": "台北市重庆南路一段83号5楼", "住址": "台北市中山北路2段113号", "外国企业注册地国": "- ", "总机电话": "(02)2531-7099", "实收资本额": "61574403270", "成立日期": "19501229", "签证会计师1": "邵志明", "公司代号": "1101", "编制财务报表类型": "1", "营利事业统一编号": "11913502", "传真机号码": "(02)2531-6529", "特别股": "200000000", "电子邮件信箱": "[email protected]", "股票过户机构": "中国信托商业银行代理部", "总经理": "李钟培", "上市日期": "19620209", "产业别": "01", "公司名称": "台湾水泥股份有限公司", "网址": "http://www.taiwancement.com", "出表日期": "1100903", "英文简称": "TCC", "公司简称": "台泥", "普通股每股面额": "新台币 10.0000元", "签证会计师2": "黄惠敏", "发言人职称": "资深副总经理", "代理发言人": "赖家柔", "董事长": "张安平", "英文通讯地址": "No.113, Sec.2, Zhongshan N. Rd.,Taipei City 104,Taiwan (R.O.C.)", "私募股数": "0"]


<<:  Day 1 | 前言与大纲

>>:  Day 2 公告吧!

17 程序竞赛前中後准备技巧

准备程序竞赛的技巧大多都和平常准备考试的方法相同,但因为程序竞赛有许多规定,在第一次入门时会被很多规...

成员 20 人:

撰写中 在求发展的道路上,又过了一日...... 这时,成员 20 人。 ...

【零基础成为 AI 解梦大师秘笈】Day29 - 周易解梦之人工智慧(10)

LSTM 前言 系列文章简介 大家好,我们是 AI . FREE Team - 人工智慧自由团队,这...

[day27][後端][实作] 引入Typescipt,webpack的loader(下)

同步发表到驴形笔记 前情题要 在上这一篇中我们成功让webpack可以吃下".ts&qu...

Day26 指派角色给使用者

昨天角色的 CRUD 功能都完成了,接着就是要把角色指派给使用者了,先建立一个 ViewModel ...