Goとgoqueryでスクレイピング

一億番煎じ:tea:記事でございます:pensive:
最近一億番煎じというフレーズがマイブームになりつつある:raised_hands:
自分の整理のための記事ですが、コメントとかいいねしてくれたら泣いて喜びます:joy:

公式
https://github.com/PuerkitoBio/goquery
GoDoc
https://godoc.org/github.com/PuerkitoBio/goquery

# 概要とかインストールとか
公式Githubに書いてあるとおりですが、jQuery的にスクレイピングやクローリングができるライブラリです。
インストールはgetで。glideを使いたければそれでも。

```
$ go get github.com/PuerkitoBio/goquery
```

# ドキュメントを取得
なにはともかくドキュメントを取得しましょう。

## Getで取得
ごく普通のページは`func NewDocument(url string) (*Document, error)`を使います。

```go:NewDocumentを使う
doc, err := goquery.NewDocument("対象のURL")
if err != nil {
    panic(err)
}
```
これで対象のURLからGetでドキュメントを取得してくれます。

## Postなどリスエストをカスタマイズして取得
ページによってはPostパラメータを付けなければならないとか、リクエストヘッダーを弄りたいということがあります。
その場合は、まず`net/http`と`net/url`を使ってレスポンスを取得し、それを`func NewDocumentFromResponse(res *http.Response) (*Document, error)`に渡してやります。

```go:NewDocumentFromResponseを使う
// パラメータを作る
values := url.Values{}
values.Add("key", "value")
...

// リクエストを作る
// 例えばPOSTでパラメータつけてUser-Agentを指定する
req, err := http.NewRequest("POST", "対象のURL", strings.NewReader(values.Encode())) 
if err != nil {
    panic(err)
}
req.Header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1")

// リスエスト投げてレスポンスを得る
client := &http.Client{}
resp, err := client.Do(req)

// レスポンスをNewDocumentFromResponseに渡してドキュメントを得る
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
    panic(err)
}
```

ここらへんgoqueryはサポートしてないんだなーと思ったりもしましたが、下手にサポートして制限があるよりかは一切手を出さないから好きにやってね、の方がいいような気もします。

## Readerから取得
あんまり頻繁にページアクセスするのは申し訳ないと思うときは、一旦HTMLをローカルにダウンロードし、ローカルのHTMLから`func NewDocumentFromReader(r io.Reader) (*Document, error)`でドキュメントを取得することもできます。

```go:Readerを使う
fileInfos, _ := ioutil.ReadFile("HTMLのファイルパス")
stringReader := strings.NewReader(string(fileInfos))
doc, err := goquery.NewDocumentFromReader(stringReader)
if err != nil {
    panic(err)
}
```

# セレクションを取得
ドキュメントが取得できたら、そこからほしいデータがあるタグをなり塊なりを`func (s *Selection) Find(selector string) *Selection`で取得しましょう。
`Find`はドキュメントに対してもセレクションに対しても使えます。
セレクタについては↓らへんを見たらいいんじゃないでしょうか(雑)
[セレクタの種類](http://www.htmq.com/csskihon/005.shtml)

```go:Findでセレクタを取得
// ドキュメントから
selection := doc.Find("div#some_id > div.some_class")

// セレクションから更にセレクションを取得
innerSeceltion := selection.Find("a")

// セレクションが複数ある場合はEachで回せます
innerSeceltion.Each(func(index int, s *goquery.Selection) {
    // なんかする
})
```

# セレクションをたどる
以下などがあります。一通りできますね！(雑)
`func (s *Selection) Children() *Selection`
`func (s *Selection) First() *Selection`
`func (s *Selection) Last() *Selection`
`func (s *Selection) Next() *Selection`
`func (s *Selection) Parent() *Selection`
`func (s *Selection) Prev() *Selection`
`func (s *Selection) Siblings() *Selection`

# データを抜く
目的のセレクションに到達したら、ほしいデータを抜き取りましょう。
以下が使えそうな関数です。
`func (s *Selection) Attr(attrName string) (val string, exists bool)`
`func (s *Selection) AttrOr(attrName, defaultValue string) string`
`func (s *Selection) Html() (ret string, e error)`
`func (s *Selection) Text() string`

```go:データを抜く
// セレクションより下のタグ入りの文字列を取得
html, err := selection.Html()

// セレクションより下の全てのテキストを取得
text := selection.Text()

// 属性を取得、existsはその属性が存在するか
attr, exists := selection.Attr("href")

// 属性が存在しない場合はdefaultValueを返す
attrWithDefault := selection.AttrOr("src", "default")
```

# おわりに
~queryがたくさんありますが、どれか一つ覚えておけば言語が変わっても使えるという安心感があります:blossom:
Goは始めたばかりですが、goqueryに関してはあまり詰まること無く使えました:confetti_ball:
この記事では扱ってないですが、ノードやクラスを追加したりもできるみたいだし、あるHTMLを元に別のHTMLを生成するみたいなこともできそうですね:eyes: