golangでagoutiとgoqueryを使ってスクレイピングする

jsを使うサイトをスクレイピングしたいと思って調べた結果、pythonを使う記事は日本語でたくさんあったが、goを使ったものはあまり見かけなかったのでメモを作成。
※以下は2018年6月下旬時点の情報

## 準備
### agoutiのインストール
- 参考にさせていただきました→https://qiita.com/tenten0213/items/1f897ff8a64bd8b5270c

#### Web Driverのインストール
- Chrome用をインストール

```
$ brew install chromedriver
```
- selenium-server-standaloneもインストール

```
$ brew install selenium-server-standalone
```

#### agoutiのインストール
```
$ go get github.com/sclevine/agouti
```

###goqueryのインストール
- 参考にさせていただきました→https://qiita.com/Yaruki00/items/b50e346551690b158a79

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

##Document
- [agoutiの公式ドキュメント](https://godoc.org/github.com/sclevine/agouti)

- [goqueryの公式ドキュメント](https://godoc.org/github.com/PuerkitoBio/goquery)

##前提
自分の場合は、ブラウザを動かして

1.　ブラウザを操作して狙った情報を出す
2.　その結果をクロールする
（3.　再び別の情報を出す）

の３段階をループしたかったので、以下のようなコードを書いた。

###１．　ブラウザを操作して狙った情報を出す
#### サンプルコード

- ブラウザを起動して目的のページを開く

```go:sample.go
url = "https://example.com" // urlを指定
driver := agouti.ChromeDriver() // ドライバの起動

err := driver.Start() 
if err != nil {
  log.Printf("Failed to start driver: %v", err)
  }
defer driver.Stop()

page, err := driver.NewPage(agouti.Browser("chrome"))　// クロームを起動。page型の返り値（セッション）を返す。
if err != nil {
  log.Printf("Failed to open page: %v", err)
  }

err = page.Navigate(url) // 指定したurlにアクセスする
if err != nil {
  log.Printf("Failed to navigate: %v", err)
  }
```

- 目的の場所をクリックして狙った情報を出す（例：セレクトボックスをクリックしてその中の選択肢のすべてを取る）

```go:sample.go
curContentsDom, err := page.HTML()
  if err != nil {
    log.Printf("Failed to get html: %v", err)
    }

readerCurContents := strings.NewReader(curContentsDom)
contentsDom, _ := goquery.NewDocumentFromReader(readerCurContents) // 現状ブラウザで開いているページのDOMを取得
listDom := contentsDom.Find(" selector ").Children() // selector 　部分にセレクトボックスのセレクタを入れる。セレクトボックス内の子要素を全部取得
listLen := listDom.Length() // セレクトボックスの子要素の個数を取得

for i:= 1; i <= listLen ; i++ {
  iStr := strconv.Itoa(i)
  page.Find(" selector > option:nth-child(" + iStr + ")").Click() // セレクタの属性（ここではoption）のバリューで繰り返しクリックする
  time.Sleep(2 * time.Second) //適宜ブラウザが反応するための間を取る
  }
```

###2.　その結果をクロールする
#### サンプルコード

- 以下のコードは上記のfor iteration内で実施

```go:sample.go
curContentsDom, err := page.HTML()
  if err != nil {
    log.Printf("Failed to get html: %v", err)
    }

readerCurContents := strings.NewReader(curContentsDom)
contentsDom, _ := goquery.NewDocumentFromReader(readerCurContents) // ページ内容が変化しているので再取得

contentsDom.Find(" selector ").Each(func(_ int, s *goquery.Selection) {  // 繰り返し取得したい部分までのセレクタを入れる
  data := s.Find(" selector ").Text() // テキスト要素を取得したい場合
  link, _ := s.Find(" selector ").Attr("href") // アンカーリンク要素を取得したい場合
  alt, _ := s.Find(" selector ").Attr("alt") // alt要素を取得したい場合

  time.Sleep(1*time.Second) //サーバに負荷を与えないように。
})
```

###（3.　再び別の情報を出す）
- 1.内のforループ内で処理。

##よく使いそうなagoutiのメソッド
 - [公式Document](https://godoc.org/github.com/sclevine/agouti)から引用

```method.go
func (s *Page) Find(selector string) *Selection　// Page型のメソッド。セレクタを入れるとそのセレクタの箇所を特定して Selection型を返す

func (s *Selection) Click() error // Selection型のメソッド。引数のSelectionのすべての要素をクリックする。返り値のerrと、for文のcontinue処理を組み合わせて使える

func (s *Selection) DoubleClick() error // Selection型のメソッド。引数のSelectionのすべての要素をダブルクリックする。

func (p *Page) Refresh() error // Page型のメソッド。ページ全体をリロードする
```
- 私は使わなかったが、ブラウザの戻る、進む、マウス操作もできるので、一通りのブラウジングの再現はできそう。

 

##終わりに
英語Documentsしか自分は見つけられなかったので、agoutiを使う人の足しになればと思ってます。