上一篇,提到了可以在 tableView(_:willDisplay:forRowAt:) 中发动 URLRequest,这逻辑很正常,但真的不建议这麽做。
先来看上次的 API 接口
https://www.twse.com.tw/en/exchangeReport/STOCK_DAY?response=csv&date=20210928&stockNo=2330
我们要抽换的是 stockNo 後面的数字,而发动的时机,就是每次 tableViewCell 的 willDisplay。就这个画面来说,大概在刚进入画面的时候,就会发动五到六次不等的 API request。
而经过实测,这样子发动 API Request ,会被证交所视为不正常的网路连线,然後会在接下来的一段时间,都无法拿到证交所的资讯,包含网页也打不开。
既然这个方法走不通,那我们就要去找其他来源。去找是否有地方,可以一次性的下载所有上市股票的收盘价。
在政府的开放资料平台,是有这个资讯的。
https://data.gov.tw/dataset/11549
而下载所有资料的载点,也在这边。
https://www.twse.com.tw/exchangeReport/STOCK_DAY_ALL?response=open_data
可拿取的资料如下图
先宣告资料模型 StockDayTick
import Foundation
/// 这个 Data Model 会被公开资讯页 和 开放资料共用,两者的资料有差,如果另一边没有的,会用 default string = "-" 处理掉
struct StockDayTick: Codable {
private var dateUtility: DateUtility {
return DateUtility()
}
private var numberFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}
/// 如果是全市场台股的 dateString, format 为 yyyyMMdd
let dateString: String
let stockCode: String
/// 只有全市场台股的资料才有 stock name
var stockName = ""
/// 成交股数
let volumeString: String
/// 成交金额
let valueString: String
let openString: String
let highestString: String
let lowestString: String
let closeString: String
/// 与前天的涨跌差,但是在大盘的公开资讯,是没有这个栏位的
var change: String = ""
/// 成交笔数,但是在大盘的公开资讯,是没有这个栏位的
var transaction: String = ""
var date: Date? {
return dateUtility.getDate(from: dateString, format: "yyyy/MM/dd")
}
var open: Double? {
return numberFormatter.number(from: openString)?.doubleValue
}
var highest: Double? {
return numberFormatter.number(from: highestString)?.doubleValue
}
var lowest: Double? {
return numberFormatter.number(from: lowestString)?.doubleValue
}
var close: Double? {
return numberFormatter.number(from: closeString)?.doubleValue
}
/// 大盘 K 线的建构式
init(stockCode: String, stockName: String, dateString: String, openString: String, highestString: String, lowestString: String, closeString: String) {
self.stockCode = stockCode
self.stockName = stockName
self.dateString = dateString
self.openString = openString
self.highestString = highestString
self.lowestString = lowestString
self.closeString = closeString
self.volumeString = ""
self.valueString = ""
}
init(dateString: String,
stockCode: String,
stockName: String = "",
volumeString: String,
valueString: String,
openString: String,
highestString: String,
lowestString: String,
closeString: String,
change: String,
transaction: String) {
self.dateString = dateString
self.stockCode = stockCode
self.stockName = stockName
self.volumeString = volumeString
self.valueString = valueString
self.openString = openString
self.highestString = highestString
self.lowestString = lowestString
self.closeString = closeString
self.change = change
self.transaction = transaction
}
}
下载资料的程序码
struct StockDayPriceManager {
private var alamofireAdapter: AlamofireAdapter {
return AlamofireAdapter.shared
}
private var dateUtility: DateUtility {
return DateUtility()
}
private var dateString: String {
return dateUtility.getString(date: Date(), format: "yyyyMMdd")
}
}
/// 这一区的程序码,下载全台股所有个股单日行情,包含开高低收,成交笔数,成交股数,成交金额,价差
extension StockDayPriceManager {
/// 这一道 csv 不用拿掉第一行
func getAllTwMarketStockDayPrice(completion: @escaping ((Result<[StockDayTick], Error>) -> Void)) {
let urlString = "https://www.twse.com.tw/exchangeReport/STOCK_DAY_ALL?response=open_data"
alamofireAdapter.requestForStringWithRepsonseHeader(urlString) { string, allHeaders, error in
if let dateString = getTwMarketDateString(from: allHeaders),
let ticks = getTwMarketDayPriceList(rawString: string, dateString: dateString) {
completion(.success(ticks))
} else {
completion(.failure(GetAllMarketError()))
}
}
}
private func getTwMarketDateString(from responseHeaders: [AnyHashable: Any]?) -> String? {
if let headers = responseHeaders,
let fileName = headers["Content-Disposition"] as? String {
return fileName.slice(from: "STOCK_DAY_ALL_", to: ".csv")
}
return nil
}
private func getTwMarketDayPriceList(rawString: String, dateString: String) -> [StockDayTick]? {
if let csv = CSVAdapter(rawString: rawString) {
var ticks = [StockDayTick]()
for row in csv.namedRows {
let stockCode = row["证券代号"] ?? ""
let stockName = row["证券名称"] ?? ""
let volume = row["成交股数"] ?? ""
let value = row["成交金额"] ?? ""
let open = row["开盘价"] ?? ""
let highest = row["最高价"] ?? ""
let lowest = row["最低价"] ?? ""
let close = row["收盘价"] ?? ""
let change = row["涨跌价差"] ?? ""
let transaction = row["成交笔数"] ?? ""
let tick = StockDayTick(dateString: dateString, stockCode: stockCode, stockName: stockName, volumeString: volume, valueString: value, openString: open, highestString: highest, lowestString: lowest, closeString: close, change: change, transaction: transaction)
ticks.append(tick)
}
return ticks
}
return nil
}
}
extension StockDayPriceManager {
struct GetAllMarketError: LocalizedError {
var errorDescription = "全市场K棒开高低收资料错误"
}
}
而呼叫的时机点,可以自由决定,我目前是放在 AppDelegate didFinishLaunch
StockDayPriceManager().getAllTwMarketStockDayPrice { [weak self] result in
switch result {
case .success(let ticks):
self?.save(twAllMarketTicks: ticks)
case .failure(let error):
Logger.log("拉取台股交易日全市场结果失败: \(error.localizedDescription)")
}
}
<<: 25 - Stylelint - Lint CSS 程序码
是不是该读点书了呢? 参加读书会的好处? 打造社群学习RSC的价值:Reading(共读)、Shar...
Web API -- Application Programming Interface for ...
一开始写code 最常使用的就是注解与断行,着解释为了方便标记説明想法,断行是为了更好阅读。 注解 ...
在第一天完成安装後,就可以使用 Go 来写程序啦! 作为一个工程师,一定要来段 Hello Worl...
torchvision.transforms transforms可以用来改变样本的多样性,例如:旋...