GIGAZINEのアニメまとめ記事をパースするコマンドgiganiを作った

github.com

GIGAZINEが毎クールごとに出している新作アニメ一覧ページ(ex: http://gigazine.net/news/20170917-anime-2017autumn/ )をパースするコマンドを作りました。

動機

毎クールごとにそのクールのアニメ総括メモを書いていて、ついでにgistに録画するリストを毎回手作業で作っていたのですが、これが面倒になったので自動的にパースするようにしました。

以下のようなstructを提供しています。

type Anime struct {
    Name        string
    URL         string
    StartDate   string
    BloadCaster string
}

https://github.com/whywaita/gigani/blob/f272572a6bf98fc2d432eb6727f475b5357bb426/main.go#L8-L13

現状だとMarkdownの表を出力するようになっていますが、他に出力したいものがあれば適当に弄ってください。 出力例はこんな感じです。

気づき

HTML パース

HTMLパーサといえばRubyのNokogiriやPythonのBeautiful Soupなんかが有名ですが、軽く見たところ一条件の要素をささっと取るのは楽なものの、今回のGIGAZINEのサイトを見ていると

  • そもそも正しいタグ構造になっていない
  • 法則性はあるものの放送局や特番の関係で微妙にズレていることがある

などなど結構お粗末で、上記のライブラリ群だと上手く動かなさそうだったので、Golangでシュッと書くことにしました。

現状のGolang標準パッケージにあるトークナイザーは x/net/html があります。が、これもいまいち使いづらく、特に

<h2><a href="http://whywaita.hateblo.jp/">なぜにぶろぐ</a></h2>

のようなHTMLがあった場合に「なぜにぶろぐ」という文字列を取得することが出来ないように感じたので、大体フルスクラッチかなと思っていたところ以下のページを見つけました。

stackoverflow.com

HTML系パッケージではなくxmlパッケージを使うことで良い感じにパース出来るのでは、という記載があったので参考に書いてみました。

type htmlTitle struct {
    H2 Value `xml:"h2"`
    A  Value `xml:"a"`
}

type Value struct {
    Content string `xml:",innerxml"`
}

func trimTitle(sentence string) (title string, err error) {
    s := strings.TrimPrefix(sentence, `</p><hr><p class="preface"></p>`)
    h := htmlTitle{}

    err = xml.NewDecoder(strings.NewReader(s)).Decode(&h)
    title = h.A.Content

    return title, nil
}

https://github.com/whywaita/gigani/blob/f272572a6bf98fc2d432eb6727f475b5357bb426/parse.go

このように書くと安全に値が取得出来るようになって最高です。

終わりに

まだもう少しバグはありますがとりあえず動いているので満足しています。 何かアレな部分があればご連絡ください。

良いアニメライフを。

私はこれを作っていたらアニメ録画出来ていなかったので急いで決めます。