1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go言語でAPIを使ってXML形式のリクエストをDBに保存する

Last updated at Posted at 2020-12-14

##経緯
プログラミング歴5ヶ月目にして2つ目の言語を学んでいるが、当方、PHPしか触ったことがなかったので苦労している。
どこを探してもGoでXML形式のデータを処理するやり方を解説している記事やサイトが見つからなかった(探し方が悪い)ので自分で記事を書こうと思った。

##やること
国会議事録検索システムを使ってXML形式のデータを受け取り、DBに保存するだけ。
保存するのは会議録ID(issueID), 院名(nameOfHouse), 開催日付(date)の3つだが特に意味はない。多すぎると大変だから。
XMLの使い方(?)がよくわかっていなかったので探すのに苦労した。

##やってみる
###テーブルの作成
今回は特にデータの操作を想定していない(面倒くさい)ので全て文字列で保存する。

CREATE TABLE kaigi (issueid varchar(255), house varchar(10), date varchar(10))

QiitaってSQL文もマークアップしてくれるんだと思った。普通か。

###構造体の定義
ここでとてもつまづいた。APIを使うのが初めてだったのでデータの扱いがそもそもわかっていなかった。
フィールドのタグをxml:dataとすることでXMLでの<data>を指すことになるらしい。
今回使用するAPIのXMLデータの構造は以下の通り

<?xml version="1.0"?>
<data>
  <records>
    <record>
      <recordData>
        <meetingRecord>
          <issueID> 会議録ID </issueID>
          <nameOfHouse> 院名 </nameOfHouse>
          <date> 開催日付 </date>
        </meetingRecord>
      </recordData>
    </record>
    <record>
      (次の会議録情報)
    </record>
  </records>
</data>

長かったので必要なデータだけにしたが、目的のタグにたどり着くには、
data>records>record>recordData>meetingRecord>issueID
とたどる必要がある。
なので受け取るための構造体は以下のように定義する。

main.go
type RecordList struct {
	XMLName xml.Name `xml:"data"`
	Recs []Record `xml:"records>record"`
}

type Record struct {
	ID string `xml:"recordData>meetingRecord>issueID"`
	House string `xml:"recordData>meetingRecord>nameOfHouse"`
	Date string `xml:"recordData>meetingRecord>date"`
}

これでそれぞれのデータにたどり着ける。

###DBとの接続
外部ライブラリとして環境変数を扱うためのgodotenvとMySQLのドライバを使った。

func Connect() *sql.DB {
	// 環境変数のロード
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}
	// DBとの接続をオープンにする
	db, err := sql.Open("mysql", os.Getenv("DB_ROLE") + ":" + os.Getenv("DB_PASS") + "@/" + os.Getenv("DB_NAME"))
	if err != nil {
		log.Fatal(err)
	}
	return db
}

###リクエスト送信のための関数

main.go
func httpGet(url string) []byte {
	resp, _ := http.Get(url)
	body, _ := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()
	s := string(body)
	return []byte(s)
} 

###func main()
まずはリクエストをGETで送信して取得するところから
検索パラメータを指定して検索条件を設定できる。キーワードをnet/urlでエンコードしてリクエスト送信

main.go
	// キーワードを指定してデータを取得
	word := "コロナ"
	eWord := url.QueryEscape(word)
	data := httpGet("https://kokkai.ndl.go.jp/api/meeting_list?any=" + eWord)

	// 取得したデータをRecord型に変換
	result := RecordList{}
	err := xml.Unmarshal(data, &result)
	if err != nil {
		log.Fatal(err)
	}

DBと接続
DBにはすでに会議情報が存在していることを想定しているため、予めissueIDを取得しておく。

main.go
	// DB接続
	db := Connect()
	defer db.Close()

	// DB内の全ての会議のIDを取得
	recs, err := db.Query("SELECT issueid FROM kokkai")
	if err != nil {
		log.Fatal(err)
	}

	// 取得したIDをスライスに格納
	ids := make([]string, 0)
	for recs.Next() {
		var id string
		err := recs.Scan(&id)
		if err != nil {
			log.Fatal(err)
		}
		ids = append(ids, id)
	}

GETで取得したデータとDBの情報を照合してDBにない場合のみ保存する

main.go
	for _, rec := range result.Recs {
		// DB内に会議情報が存在するかをチェック
		isExists := false
		for _, id := range ids {
			if id == rec.ID {
				isExists = true
			}
		}

		// 会議情報が見つからなければ保存
		if isExists == false {
			// プリペアードステートメントを指定
			stmt, err := db.Prepare("INSERT INTO kokkai (issueid, house, date) VALUES(?, ?, ?)")
			if err != nil {
				log.Fatal(err)
			}
			// SQL実行
			stmt.Exec(rec.ID, rec.House, rec.Date)
		}
	}

##実行結果

go run .
MySQL
mysql> SELECT * FROM kaigi;
+-----------------------+--------+------------+
| issueid               | house  | date       |
+-----------------------+--------+------------+
| 120315254X00420201120 | 参議院 | 2020-11-20 |
| 120305254X00620201119 | 衆議院 | 2020-11-19 |
| 120305254X00520201112 | 衆議院 | 2020-11-12 |
| 120305254X00420201110 | 衆議院 | 2020-11-10 |
| 120315254X00320201030 | 参議院 | 2020-10-30 |
| 120305254X00320201029 | 衆議院 | 2020-10-29 |
| 120315254X00220201029 | 参議院 | 2020-10-29 |
| 120305254X00220201028 | 衆議院 | 2020-10-28 |
| 120305254X00120201026 | 衆議院 | 2020-10-26 |
| 120315254X00120201026 | 参議院 | 2020-10-26 |
| 120105254X03420200617 | 衆議院 | 2020-06-17 |
| 120115254X02520200617 | 参議院 | 2020-06-17 |
| 120115254X02420200612 | 参議院 | 2020-06-12 |
| 120105254X03220200610 | 衆議院 | 2020-06-10 |
| 120105254X03120200608 | 衆議院 | 2020-06-08 |
| 120115254X02320200608 | 参議院 | 2020-06-08 |
| 120115254X02220200605 | 参議院 | 2020-06-05 |
| 120115254X02120200603 | 参議院 | 2020-06-03 |
| 120115254X02020200529 | 参議院 | 2020-05-29 |
| 120115254X01920200527 | 参議院 | 2020-05-27 |
| 120115254X01820200520 | 参議院 | 2020-05-20 |
| 120105254X02520200515 | 衆議院 | 2020-05-15 |
| 120115254X01720200515 | 参議院 | 2020-05-15 |
| 120105254X02420200514 | 衆議院 | 2020-05-14 |
| 120115254X01620200513 | 参議院 | 2020-05-13 |
| 120105254X02320200512 | 衆議院 | 2020-05-12 |
| 120115254X01520200430 | 参議院 | 2020-04-30 |
| 120105254X02220200429 | 衆議院 | 2020-04-29 |
| 120105254X02120200427 | 衆議院 | 2020-04-27 |
| 120115254X01420200427 | 参議院 | 2020-04-27 |
+-----------------------+--------+------------+
30 rows in set (0.01 sec)

ちゃんと取得、保存できた。

##終わりに
Goをさわり始めて2週間、ちょっとずつわかってきた気がする。
今回は超初心者向けに書きましたが、当方もまだまだ超初心者ですので至らない点も多いかと思います。
何かお気づきの点があればご指摘ください。

続き → Go言語 XMLを変換してDB内を検索し、一致した情報をXML形式で返す

##お世話になった文献
GoでXMLをパースする
Goのencoding/xmlを使いこなす
Go言語でMySQL の基本的操作(SELECT、UPDATE、INSERT)を行う
Go言語 - XMLを読んで特定の要素を削って出力

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?