Echo ( GoのWebフレームワーク ) on GAE で貴金属 ( 金 & プラチナ )の価格を返すAPIを作成した
はじめに
以前に 貴金属の価格を取得して可視化する内容で記事を作成しました。 今回は Echo( Goフレームワーク ) でデータを取得する部分についてアップデートした部分についてです。 金融資産のポートフォリオを可視化するために必要な情報を取得するためにデータ取得先、取得データ等を変更し、Echoのバージョンをアップデートしました。
データの取得先
価格を取得するために、マイゴールドパートナー を利用しました。
スクレイピング
スクレイピングは対象サイトに迷惑がかからないようにする必要があります。 価格は基本的に日時で更新されるので1日1回定期アクセスするようにしており、APIを叩く際はベーシック認証をかけて 外部から不用意にアクセスされないように制御しています。
Goでスクレイピングするために、 goquery
を利用しました。
APIについて
リクエストについて
${ENDPOINT}/metal
- リクエストパスは
/metal
と設定
- リクエストパスは
レスポンスについて
- jsonで以下の情報を返す
- 金のweb買取価格
- 金のweb前日比価格
- プラチナのweb買取価格
- プラチナのweb前日比価格
- タイムスタンプ
- jsonで以下の情報を返す
■ レスポンス結果(例)
{ "gold": 7697, "gold_day_before_price": 28, "platinum": 4301, "platinum_day_before_price": -71, "time": "2022/02/2-21:14:28" }
ソースコード
参考までにディレクトリ構造は以下の通り
. ├── README.md ├── api │ ├── base.go │ ├── client.go │ ├── crypto_coincheck.go │ ├── crypto_coincheck_test.go │ ├── crypto_gmo.go │ ├── crypto_gmo_test.go │ ├── healthcheck.go │ ├── investment_trust.go │ ├── metal.go │ └── staticcheck.conf ├── app.yaml ├── authentication │ └── basic_auth.go ├── go.mod ├── go.sum ├── main.go └── secret.yaml
■ main.go
package main import ( "net/http" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "google.golang.org/appengine" "github.com/yhidetoshi/apiEchoGAE-private/api" "github.com/yhidetoshi/apiEchoGAE-private/authentication" ) var e = createMux() func createMux() *echo.Echo { e := echo.New() return e } func main() { // Echoインスタンス作成 // http.Handle("/", e) e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.Gzip()) e.Use(authentication.BasicAuth()) // Basic Auth cGmo := api.ClientCryptoGMO{} cGmo.NewClientCryptoGMO() cCc := api.ClientCryptoCoincheck{} cCc.NewClientCryptoCoincheck() // ルーティング // // ヘルスチェック e.GET("/api/healthcheck", api.Healthcheck) // 貴金属の価格を取得 e.GET("/api/metal", api.FetchMetal) // ISINコードを引数に基準価格を取得 // curl -sS localhost:1323/investment-trust/JP90C000GKC6 e.GET("/api/investment-trust/:isin", api.FetchInvestTrust) // 仮想通貨の価格を取得 e.GET("/api/crypto/gmo", cGmo.FetchCryptoGMO) e.GET("/api/crypto/coincheck", cCc.FetchCryptoCoincheck) // LocalとGAEの切り替え appengine.Main() // GAEの場合に有効化 //e.Start(":1323") // Localの場合に有効化 }
■ base.go
package handler import ( "log" "strconv" "time" ) const ( timezone = "Asia/Tokyo" offset = 9 * 60 * 60 timeFormat = "2006/01/02-15:04:05" ) var ( jst = time.FixedZone(timezone, offset) nowTime = time.Now().In(jst).Format(timeFormat) ) func convertStringToInt(value string) int { intValue, err := strconv.Atoi(value) if err != nil { log.Println("Error", err) } return intValue }
■ metal.go
package api import ( "encoding/json" "log" "net/http" "os" "strings" "github.com/PuerkitoBio/goquery" "github.com/labstack/echo/v4" ) /* Response { "gold": 7697, "gold_day_before_price": 28, "platinum": 4301, "platinum_day_before_price": -71, "time": "2022/02/21-21:14:28" } */ const ( targetURLMetal = "https://gold.mmc.co.jp/" goldPriceCount = 8 goldDayBeforePriceCount = 9 platinumPriceCount = 48 platinumDayBeforePriceCount = 49 ) type Metal struct { GoldPrice int `json:"gold"` GoldDayBeforePrice int `json:"gold_day_before_price"` PlatinumPrice int `json:"platinum"` PlatinumDayBeforePrice int `json:"platinum_day_before_price"` Date string `json:"time"` } func FetchMetal(c echo.Context) error { var goldPrice, goldDayBeforePrice, platinumPrice, platinumDayBeforePrice string var tdCount int res, err := http.Get(targetURLMetal) if err != nil { log.Println(err) os.Exit(1) } defer res.Body.Close() doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { log.Println(err) } // Fetch gold and platinum price doc.Find("div.p-table-scroll--sticky > table > tbody > tr > td").Each(func(_ int, s *goquery.Selection) { if tdCount == goldPriceCount { //Gold Price goldPrice = s.Find("span.c-table__text--xl").Text() } if tdCount == goldDayBeforePriceCount { //Gold Price Day Before Price Count goldDayBeforePrice = s.Find("span.c-table__text--xl").Text() } if tdCount == platinumPriceCount { // Platinum Price platinumPrice = s.Find("span.c-table__text--xl").Text() } if tdCount == platinumDayBeforePriceCount { // Platinum Day Before Price Count platinumDayBeforePrice = s.Find("span.c-table__text--xl").Text() } tdCount++ }) // Format strGoldPrice := strings.Replace(goldPrice, ",", "", -1) strGoldDayBeforePrice := strings.Replace(goldDayBeforePrice, "+", "", -1) strPlatinumPrice := strings.Replace(platinumPrice, ",", "", -1) strPlatinumDayBeforePrice := strings.Replace(platinumDayBeforePrice, "+", "", -1) // Convert string to int intGoldPrice := convertStringToInt(strGoldPrice) intGoldDayBeforePrice := convertStringToInt(strGoldDayBeforePrice) intPlatinumPrice := convertStringToInt(strPlatinumPrice) intPlatinumDayBeforePrice := convertStringToInt(strPlatinumDayBeforePrice) // Set value to json responseJSON := Metal{ GoldPrice: intGoldPrice, GoldDayBeforePrice: intGoldDayBeforePrice, PlatinumPrice: intPlatinumPrice, PlatinumDayBeforePrice: intPlatinumDayBeforePrice, Date: nowTime, } response, err := json.Marshal(responseJSON) if err != nil { log.Println(err) } return c.String(http.StatusOK, string(response)) }
■ 確認
❯ curl -sSu ${ID}:${PW} ${ENDPOINT}/metal | jq { "gold": 7698, "gold_day_before_price": 1, "platinum": 4338, "platinum_day_before_price": 37, "time": "2022/02/22-21:19:14" }
デプロイについて
今回のGoアプリはGoogle App Engineにデプロイしているので、Github Actionsで自動デプロイするようにしています。 そちらについては以前に記事でまとめています。