こんにちは、spotチームの名嘉眞です。spotチームはキャンプ場検索サービス(hinata spot)を開発しております。私はspotチームのバックエンド担当として日々Goを書いてます。
今回はsitemap.xmlをGoで生成する方法についてまとめてみました。標準パッケージで割と簡単に出来るかなと思います。
始めに
hinata spotでは、sitemap.xmlの生成もGoで書いています。ちなみにsitemap.xmlとは、ウェブサイト内の各ページのURLや優先度、最終更新日、更新頻度などを記述したXML形式のファイルのことです。
railsだとsitemap_generatorというgemを使って生成したりするかもしれません。Goにもそのようなライブラリがあるかもしれないですが、標準packageのencoding/xmlで十分実装できると考えました。
処理の流れ
私の担当するサービスの場合、sitemap.xmlに記載されるurlを構成する要素はDBから取得する必要があります。そのため下記のような処理の流れで生成します。
この記事では主に、sitemap.xmlの生成部分について紹介していきます。
実装部分
sitemap.xmlは以下のようなファイルです。sitemap固有のタグなど生成ルールが決まっています。
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"> <url> <loc>https://hinata-spot.me/spots/taito-beach</loc> <changefreq>2020-10-20T14:56:46+09:00</changefreq> </url> <url> <loc>https://hinata-spot.me/spots/tanpopomura</loc> <changefreq>2020-10-20T14:56:46+09:00</changefreq> </url> </urlset>
まず、sitemapxmlをGoのstructで表現するために、structを以下のように定義しています。
encoding/xmlの場合、structの各フィールドにxmlタグと付与したいメタ情報を適用すると、適用したメタ情報をもとにxml生成時にタグで値を囲います。
attr
と記載すると上位のタグの中にattr
で指定した値を埋め込みます。
実際に生成したいsitemap.xml(上の例)と以下のstructでマッチする部分としては、 XMLName、Version、Xhtmlフィールドが実際に生成したいsitemap.xmlのurlsetタグの部分になります。 SiteListフィールドがsitemap.xmlに設定したいサイトのページのURLになります。
package models import "encoding/xml" type SiteMapXML struct { XMLName xml.Name `xml:"urlset"` Version string `xml:"xmlns,attr"` Xhtml string `xml:"xmlns:xhtml,attr"` SiteList []*Site `xml:"url"` } type Site struct { URL string `xml:"loc"` UpdatedAt string `xml:"changefreq"` }
サイトマップ生成の関数が実行されると、DBから対象のデータを取得し上記のSite structを生成するようにします。
以下のような関数を使ってsitemapに設定したいURLを生成しています。
最終的に、[]*Site型
を生成します。
func CreateSite(path, updatedAt string) *models.Site { u := url.URL{Scheme: "https", Host: spotHost, Path: path} site := &models.Site{ URL: u.String(), UpdatedAt: updatedAt, } return site } // dbResultはDBから取得したデータとする siteList := make([]*models.Site, len(dbResult)) var index int for i := range dbResult { path := "spots/" + dbResult[i] site := CreateSite(path, dbResult[i].UpdatedAt) siteList[index] = site index++ }
sitemap.xml生成するパッケージは以下のように定義しています。このパッケージに定義したCreateXMLgzip
関数の引数に、上の例で生成した[]*models.Site
を渡すことで、sitemap.xmlの生成とgzip化を行います。
sitemap.xml固有の値は定数にて定義しています。
package sitemap import ( // 省略 ) const ( version = "http://www.sitemaps.org/schemas/sitemap/0.9" xhtml = "http://www.w3.org/1999/xhtml" spotHost = "hinata-spot.me" ) // CreateXMLgzip creates site map xml from struct. func CreateXMLgzip(siteList []*models.Site) (io.ReadWriter, error) { ss := &models.SiteMapXML{ Version: version, Xhtml: xhtml, SiteList: siteList, } data, err := xml.MarshalIndent(ss, " ", " ") if err != nil { return nil, err } // xml.Header は、encoding/xmlパッケージで以下のようにconstで宣言されています。 // <?xml version="1.0" encoding="UTF-8"?> bss := [][]byte{[]byte(xml.Header), data} bs := (bytes.Join(bss, []byte(""))) var result bytes.Buffer zw := gzip.NewWriter(&result) _, err = zw.Write(bs) if err != nil { return nil, err } if err := zw.Close(); err != nil { return nil, err } return &result, nil }
CreateXMLgzip
関数は、戻り値をインターフェース io.ReadWriter型にしています。理由はGCSにアップロードするオブジェクトをインターフェースのio.Reader型で受け取るようにしているからです。
io.Reader
で受け取れるようにすることで、ストレージへのアップロードなど共通で使うような関数を使いやすくしています。
結構簡単に実装できたと感じるのではないでしょうか。サービスの内容によるかもしれないですが、SEOを考えるとXMLサイトマップを生成し管理することがあると思いますので、その際に役に立つことができたら嬉しいです。