初めに
M1 ProのMacBook Proを買いました。性能には120%の満足なのですが、デザインだけはどうも受け入れ難いです、どうもこんにちはTOSHです。
でもなんやかんやPS5とは違って、MacBookは抽選販売にならなかったので、よかったです😆
さて、近年はコロナの影響もあり、ネットで物を買うことが多くなってきているのではないでしょうか?
そうなってくると、いつ配達されるのかは気になりますよね?そこで、クロネコヤマトの配達状況のWebページなんかを利用すると思います。しかし、RestAPIのような形式で使えるものが欲しいですよね?
では、そういったAPIはないのでしょうか?
業者用のAPIは用意されているようですが、こちらに関しては法人としての登録が必要だったり使用が難しいです。
また、本家クロネコヤマトのアプリが使用してるAPIはどうでしょう?
クロネコヤマトのセキュリティに関することになってしまうので、あまり詳しく解説はしないのですが、配達IDを暗号化してクエリに追加しているような形式になっているため、鍵を知らない一般ユーザーがこのAPIを使用するのもどうやら難しそうです。
荷物お問い合わせシステム
そうなったら、荷物お問い合わせシステムを使用するしかありません。
このようなシステムを使用して、おり、送り状番号を入力すると、その商品の配達状況を確認することができます。
では、具体的にはこのサービスはどのようにしてリクエストを送信しているのでしょうか?
"endpoint": "https://toi.kuronekoyamato.co.jp/cgi-bin/tneko"
"methodType": "Post"
"query": ["number00": Int, "number01": Int, "number02": Int, "number03": Int, "number04": Int, "number05": Int, "number06": Int, "number07": Int, "number08": Int, "number09": Int, "number10": Int]
このようにリクエストをすると、配達状況のデータを持ったHTMLが帰ってきます。クエリはnumber00には1を入れてあげ、その後number01~10まで最大10個の伝票番号を同時に問い合わせることができます。
また、クエリを付与する際には、number00=1&number01=伝票番号
のような形式に変更してあげる必要があります。
コードで書くとこんな感じです。
extension Dictionary where Key == String, Value == Int {
func equalEncode() -> String {
return map { key, value in
return key + "=" + String(value)
}
.joined(separator: "&")
}
}
レスポンスの処理方法
さて、さっきまでの方法で無事リクエストをすることができるようにはなりましたが、問題はレンポンスの形式です。実際に叩いてみるとわかるのですが、これは、jsonが帰ってくるわけではなく、HTML形式でレスポンスが帰ってきます。
なので、クライアント側でそのHTMLを解析して、使いやすい形に変換してあげる必要があります。
まずはModelの作成です。
public struct Tneko: Codable {
public var deriveryList: [DeliveryList]
public struct DeliveryList: Codable {
public var deliveryID: Int
public var statusList: [DeliveryStatus]
public init(deliveryID: Int, statusList: [Tneko.DeliveryList.DeliveryStatus]) {
self.deliveryID = deliveryID
self.statusList = statusList
}
public struct DeliveryStatus: Codable {
public var status: String
public var date: String
public var time: String
public var shopName: String
public init(status: String, date: String, time: String, shopName: String) {
self.status = status
self.date = date
self.time = time
self.shopName = shopName
}
}
}
}
先ほども述べた通り、JSON形式でレスポンスが帰ってくるわけではないので、Codableに準拠する必要はないのですが、あくまで、Codableで定義できる型以外を定義しないという意味や他のエンドポイントとの統一性、JSON形式でmockを生成した際のことなどを考えてCodableに準拠しておくことをおすすめします。
では、ここからが鬼門です。HTMLをこのような形のModelへと変更する必要があります。
HTMLは容易にattributedStringへと変換できるので、attributedStringへと変換してしまうのが良いでしょう。
let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let attributedString = try? NSAttributedString(data: data!, options[.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
let tneko = Tneko(
idList: request.idList(),
response: attributedString.string
)
completion(.success(tneko))
} else {
completion(.failure(.decodeErrror("This is not HTML")))
}
}
このようにして形式を変更しておくと、TnekoのModelではStringからModelを作成すれば良いということになります。
StringからModelへの変更
先ほどの、HTMLをStringに変更したものを一部抜粋するとこのような形式になっています。
配達完了
このお品物はお届けが済んでおります。
お問い合わせはサービスセンターまでお願いいたします。
お受け取り日時・場所変更をする
※このお荷物は対象外です
お届け完了のメール通知を受け取る
※このお荷物は対象外です
• 商品名:
宅急便
• お届け予定日時:
-
1. 荷物受付
10月27日 11:27
ZOZO支店
2. 発送済み
10月27日 11:27
ZOZO支店
3. 輸送中
10月28日 01:35
〇〇ベース店
4. 配達完了
10月28日 11:30
〇〇センター
詳細印刷
Yahoo!地図からもお荷物の状況が確認できます。
※推奨環境はこちら
▲上部に戻る
上記のStringはすべて一行に収まっているので、\n
で区切ってあげて、Stringの配列へと変更すると良いでしょう。
ここからは力技です。HTMLの中からルールを見つけ出し、Modelの要素へと変換していきます。変換するコードのイメージは以下の通りです。
extension Tneko {
public init(idList: [Int], response: String) {
self.deriveryList = idList.enumerated().map { initialIndex, id in
let stringList = response.components(separatedBy: "\n")
var newStatusList: [Tneko.DeliveryList.DeliveryStatus] = []
var indexCounter = 0
stringList.enumerated().forEach { index, str in
if str.contains("お届け予定日時:") {
if initialIndex == indexCounter {
var counter = 0
let initialStatusGroup = stringList[index + 1].split(separator: "
")
if var statusCode = initialStatusGroup[0].split(separator: "\t")[safe: 1]?.description {
while statusCode.isValidStatusCode {
let newStatusGroup = stringList[index + counter + 1].split(separator: "
")
let date = newStatusGroup[1].split(separator: " ")[0].description.replacingOccurrences(of: "月", with: "/").replacingOccurrences(of: "日", with: "")
let time = newStatusGroup[1].split(separator: " ")[1].description
let shopName = newStatusGroup[2].description
let status = Tneko.DeliveryList.DeliveryStatus(
status: statusCode,
date: date,
time: time,
shopName: shopName
)
newStatusList.append(status)
counter += 1
statusCode = stringList[index + counter + 1].split(separator: "
")[0].split(separator: "\t")[safe: 1]?.description ?? ""
}
}
}
indexCounter += 1
}
}
return DeliveryList(deliveryID: id, statusList: newStatusList)
}
}
}
extension String {
var isValidStatusCode: Bool {
return self == "荷物受付" || self == "発送済み" || self == "輸送中" || self == "配達中" || self == "配達完了" || self == "持戻(ご不在)" || self == "配達完了(宅配ボックス)"
}
}
だいぶ、力技の解析にはなってしまっていますが、このようにすると、Modelへと落とし込むことができます。
まとめ
HTMLを力技で解析して、クロネコヤマトのWebページから情報を持ってくる方法を紹介しました!
実際に、これを使って作成したのでよかったら使ってみてください!すべてSwiftUIで作成しております。
https://apps.apple.com/jp/app/%E3%82%AF%E3%83%AD%E3%83%8D%E3%82%B3%E9%85%8D%E9%81%94%E7%8A%B6%E6%B3%81/id1585504785