Tana Gone
Tana Gone
3 min read

Categories

Package.swiftとはAppleの開発環境でLibrary, App作成のためのProjectファイルっぽいものである。

  • Package.swiftの生成方法

    $swift package init --name Foo
    

    以下の画像はKanna(Scrapping Library)に依存するPackage.swiftの例である。実行ファイルとLibraryを生成するための設定が記載されている。KannaのRepoに記載のPackage.swift記載例は古くて役に立たない。

- .package(url: "https://github.com/tid-kijyun/Kanna.git", from: "5.3.0")

+ .package(url: "https://github.com/tid-kijyun/Kanna.git", .upToNextMajor(from: "5.3.0"))
  • Kanna

Kannaの使用例である。

//  main.swift
//  Crawl
import Foundation
import Kanna
//let url = "https://finance.yahoo.co.jp/quote/1301.T/history"
let url = "https://finance.yahoo.co.jp/quote/6085.T/history"
let str: String = try! .init(contentsOf: URL(string: url)!, encoding: .utf8)
let doc = try! HTML(html: str, encoding: .utf8)
print(doc.title!)
//let csssel = "div#term_chg ~ table tr>th, div#term_chg ~ table tr>td"
let csssel = "table.HistoryTable__1aNP tr>th, table.HistoryTable__1aNP tr>td"
//let csssel = "th.HistoryTable__head__1uIP"
var lines: [String] = []
for e in doc.css(csssel) {
  lines.append(e.text ?? "")
//  print(e.text ?? "")
//  print(e.parent?.parent?.parent?.className ?? "") // 日付
//  print(e.toHTML ?? "")
}
// ref: scrapeStock.rb

// メイン処理
let result = linesToOHLCV2(lines).filter { $0.count == 7 }
for e in result {
  print(e)
} //2025年4月9日 ,分割:1株→3株  => ["2025-04-09", 0.0]

//  printSections(result)
//アーキテクツ・スタジオ・ジャパン(株)【6085】:株価時系列・信用残時系列 - Yahoo!ファイナンス
//["2025-04-16", 158.0, 208.0, 154.0, 161.0, 6439500.0, 161.0]
//["2025-04-15", 165.0, 168.0, 157.0, 159.0, 37500.0, 159.0]
//["2025-04-14", 168.0, 170.0, 154.0, 164.0, 110000.0, 164.0]
//["2025-04-11", 141.0, 173.0, 141.0, 158.0, 333500.0, 158.0]
//["2025-04-10", 149.0, 149.0, 141.0, 144.0, 21100.0, 144.0]
//["2025-04-09", 141.0, 146.0, 137.0, 142.0, 18900.0, 142.0]
//["2025-04-08", 432.0, 445.0, 432.0, 439.0, 7900.0, 146.33]
//["2025-04-07", 440.0, 452.0, 423.0, 423.0, 25200.0, 141.0]
//["2025-04-04", 460.0, 464.0, 449.0, 460.0, 27600.0, 153.33]
//["2025-04-03", 470.0, 485.0, 470.0, 478.0, 15000.0, 159.33]
//["2025-04-02", 479.0, 489.0, 475.0, 489.0, 5100.0, 163.0]
//["2025-04-01", 482.0, 488.0, 479.0, 479.0, 6100.0, 159.67]
//["2025-03-31", 480.0, 497.0, 466.0, 488.0, 12700.0, 162.67]
//["2025-03-28", 483.0, 495.0, 474.0, 485.0, 16800.0, 161.67]
//["2025-03-27", 475.0, 490.0, 472.0, 489.0, 7100.0, 163.0]
//["2025-03-26", 475.0, 483.0, 473.0, 480.0, 7300.0, 160.0]
//["2025-03-25", 479.0, 481.0, 472.0, 472.0, 11500.0, 157.33]
//["2025-03-24", 485.0, 494.0, 468.0, 474.0, 22700.0, 158.0]
//["2025-03-21", 490.0, 494.0, 480.0, 480.0, 40300.0, 160.0]
//["2025-03-19", 540.0, 540.0, 486.0, 498.0, 190300.0, 166.0]
  • helper method
// helper.swift
import Foundation
// OHLCVデータを表す型
typealias OHLCVSection = [Any]

func linesToOHLCV2(_ lines: [String]) -> [OHLCVSection] {
  var stockData: [OHLCVSection] = []
  var currentSection: [Any]? = nil

  let inputFormatter = DateFormatter()
  inputFormatter.dateFormat = "yyyy-M-d"

  let outputFormatter = DateFormatter()
  outputFormatter.dateFormat = "yyyy-MM-dd"

  for var line in lines {
    line = line.trimmingCharacters(in: .whitespacesAndNewlines)

    // "YYYY年MM月DD日" にマッチ
    if let _ = line.range(of: #"^\d{4}年\d{1,2}月\d{1,2}日$"#, options: .regularExpression) { // 1. 日付の場合
      if let section = currentSection { // 2度目の日付に当たると保存
        stockData.append(section)
      }
      let rawDate = line.replacingOccurrences(of: "年", with: "-")
        .replacingOccurrences(of: "月", with: "-")
        .replacingOccurrences(of: "日", with: "")
      if let date = inputFormatter.date(from: rawDate) {
        let formattedDate = outputFormatter.string(from: date)
        currentSection = [formattedDate] // 初期化
      } else {
        currentSection = [rawDate] // Fallback in case of invalid date
      }
    } else if var section = currentSection { // 2. 日付でない場合
      let number = Double(line.replacingOccurrences(of: ",", with: "")) ?? 0.0
      section.append(number)
      currentSection = section
    }
  }

  if let section = currentSection {
    stockData.append(section)
  }

  return stockData
}

func linesToOHLCV2(_ lines: [String]) -> [OHLCVSection] {
  var stockData: [OHLCVSection] = []
  var currentSection: [Any]? = nil

  let inputFormatter = DateFormatter()
  inputFormatter.dateFormat = "yyyy-M-d"

  let outputFormatter = DateFormatter()
  outputFormatter.dateFormat = "yyyy-MM-dd"

  for var line in lines {
    line = line.trimmingCharacters(in: .whitespacesAndNewlines)

    // "YYYY年MM月DD日" にマッチ
    if let _ = line.range(of: #"^\d{4}年\d{1,2}月\d{1,2}日$"#, options: .regularExpression) {
      if let section = currentSection {
        stockData.append(section)
      }
      let rawDate = line.replacingOccurrences(of: "年", with: "-")
        .replacingOccurrences(of: "月", with: "-")
        .replacingOccurrences(of: "日", with: "")
      if let date = inputFormatter.date(from: rawDate) {
        let formattedDate = outputFormatter.string(from: date)
        currentSection = [formattedDate]
      } else {
        currentSection = [rawDate] // Fallback in case of invalid date
      }
    } else if var section = currentSection {
      let number = Double(line.replacingOccurrences(of: ",", with: "")) ?? 0.0
      section.append(number)
      currentSection = section
    }
  }

  if let section = currentSection {
    stockData.append(section)
  }

  return stockData
}

なおlines(the results of scrapping)の中身は

日付
始値
高値
安値
終値
出来高
調整後終値*
2025年4月16日
158
208
154
161
6,439,500
161
2025年4月15日
165
168
157
159
37,500
159
2025年4月14日
168
170
154
164
110,000
164
2025年4月11日
141
173
141
158
333,500
158
2025年4月10日
149
149
141
144
21,100
144
2025年4月9日
分割:1株→3株
2025年4月9日
141
146
137
142
18,900
142
... omitted ...