[Swift] 49 BBS閲覧アプリiOS版製作 その3 掲示板一覧表示 / kanna(libxml2)でスクレイピング

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

BBS閲覧アプリiOS版の初期画面として掲示板一覧をツリー表示させました。カテゴリーのリストを登場順に並べるのに少し手間取りました。

ChatGPT(gpt-4)をうまく誘導しながら完成させました。構造体で解決しなくて安易にクラスにしようとするなど一々大げさな反応をするので、こちらで手綱を引かないと無駄に大掛かりなコードになります。しかもどんどん的外れな方向に進んでいくので要注意です。

今日ここまでのgpt-4利用料は330円($2.19)ほど。新しい言語なのでgpt-3.5はなるべく避けたいところですが、やはりgpt-4はここぞという時に使うようにします。

import Foundation
import Kanna

struct Board : Identifiable {
    let id = UUID()
    let url: String
    let name: String
    let category: String
    let categoryNum: Int
}

struct BoardCategory : Identifiable {
    let id = UUID()
    let category: String
    let boards: [Board]
    let categoryNum: Int
}


func getBoards() async -> [BoardCategory] {
    var boards: [Board] = []
    var categoryNumDict: [String: Int] = [:]
    var categoryNum = 0
    guard let url = URL(string: "掲示板一覧のURL") else { return [] }

    do {
        let htmlString = try String(contentsOf: url, encoding: .shiftJIS)
        guard let doc = try? HTML(html: htmlString, encoding: .shiftJIS) else { return [] }

        for link in doc.xpath("//*[name()='a']") {
            guard let url = link["href"], let name = link.text else { continue }

            var category = "XXX" // カテゴリー名の初期値
            var prevElement = link.previousSibling
            while prevElement != nil && prevElement?.tagName != "b" {
                prevElement = prevElement?.previousSibling
            }
            if let prevElement = prevElement {
                category = prevElement.text ?? "XXX"
            }

            if categoryNumDict[category] == nil {
                categoryNum += 1
                categoryNumDict[category] = categoryNum
            }

            boards.append(Board(url: url, name: name, category: category, categoryNum: categoryNumDict[category]!))
        }
    } catch {
        print("Error: \(error)")
    }
    
    let groupedBoards = Dictionary(grouping: boards, by: { $0.category })
    
    var boardCategories: [BoardCategory] = groupedBoards.map { BoardCategory(category: $0.key, boards: $0.value, categoryNum: categoryNumDict[$0.key]!) }
    boardCategories.sort { $0.categoryNum < $1.categoryNum }

    return boardCategories
}