Go+goqueryでGithubRankingにWebスクレイピングを試みる

[Goのスクレイピング系ライブラリまとめ](http://qiita.com/okataitai/items/db6999ea1ab39ec0bd3e)の中からgoqueryをピックアップして試してみました。
ターゲットは[GithubRanking](https://github-ranking.com)。
ちなみに、ただただランキング情報を取得したい場合は[GithubAPI](https://developer.github.com/v3/search/)を使えばいけます。

# ターゲット
* [Github Repositories Ranking](https://github-ranking.com/repositories)
* ページングをたどりながら上位1000件のリポジトリ名とstarの数を取得する

![スクリーンショット 2016-03-03 23.38.36.png](https://qiita-image-store.s3.amazonaws.com/0/60915/91799e73-ca8b-f0eb-e568-368ef9bd5fc6.png)


# 利用ライブラリ
* [goquery](https://github.com/PuerkitoBio/goquery)
	* [jQuery](http://api.jquery.com/)に近しい関数が用意されており、セレクタも使えるスーパー便利なライブラリ

# いざ、スクレピング！
## goqueryの追加
```
% go get github.com/PuerkitoBio/goquery
```

## DOMの調査
* ブラウザのデベロッパーコンソールとか、Firebugとか使ってDOMを調査する
* goqueryはjQueryのセレクタがほぼ使えるので、コンソールでセレクタに当てをつけてjs書いて取得要素を確認してくのが効率が良い
 * 例）コンソールに`$("a.list-group-item")`を入力して取得要素を見てみる

![スクリーンショット 2016-03-03 23.59.57.png](https://qiita-image-store.s3.amazonaws.com/0/60915/5af6c5d7-cd6c-0aa7-5803-bc406b43d566.png)

## プログラム

```go
package main

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
)

const (
	targetfqdn = "github-ranking.com"
)

func IsRelativePath(url string) bool {
	if strings.Index(url, "//") == 0 {
		return false
	} else if strings.Index(url, "/") == 0 {
		return true
	} else {
		return false
	}
}

func GetAbsoluteURLFromRelativePath(scheme string, fqdn string, relativePath string) string {
	return scheme + "://" + fqdn + relativePath
}

func hasNextPageURL(doc *goquery.Document) (string, bool) {
	nexturl, exists := doc.Find("ul > li.next > a").First().Attr("href")
	if exists == true && IsRelativePath(nexturl) {
		return GetAbsoluteURLFromRelativePath("https", targetfqdn, nexturl), true
	}
	return nexturl, false
}

func outputRepoAndStar(groupItemSelection *goquery.Selection) {
	groupItemSelection.Each(func(i int, s *goquery.Selection) {
		repositorie := s.Find("span.name span.hidden-lg").Text()
		stars := s.Find("span.stargazers_count").Text()
		fmt.Printf("★ %s : %s\n", strings.TrimSpace(stars), strings.TrimSpace(repositorie))
	})
}

func main() {
	doc, err := goquery.NewDocument(GetAbsoluteURLFromRelativePath("https", targetfqdn, "/repositories"))
	nexturl, hasNext := "", true
	if err != nil {
		log.Fatal(err)
	}
	for hasNext {
		outputRepoAndStar(doc.Find("a.list-group-item"))
		nexturl, hasNext = hasNextPageURL(doc)
		doc, err = goquery.NewDocument(nexturl)
		time.Sleep(3000)
	}
}
```

## ポイント
### func NewDocument
* `	doc, err := goquery.NewDocument(GetAbsoluteURLFromRelativePath("https", targetfqdn, "/repositories"))`のように利用
* 引数のURLにリクエスト送付してレスポンス取得&DOMのロード&解析までしてくれる

### func (*Selection) Find
* `doc.Find("a.list-group-item")`のように引数にセレクタを指定


### func (*Selection) First
* `nexturl, exists := doc.Find("ul > li.next > a").First()`のようにFind関数で指定したセレクタにヒットする要素の中から１番目の要素を取得

### func (*Selection) Attr
* `nexturl, exists := doc.Find("ul > li.next > a").First().Attr("href")`のように対象要素の属性値を取得する


## 実行
* GoSublimeを利用しているので`Command + 9`でSublimeのコンソールを開く
	* GoSublimeの入れ方は[10分で終わるGo言語の開発環境構築](http://qiita.com/okataitai/items/a6f966d2075d69d21e74)を参照
* コンソールで`run`

### 結果
1000件取得完了

```
[ `run` | done: 1.188862586s ]
	★ 77733 : bootstrap
	★ 35984 : free-programming-books
	★ 35311 : angular.js
	★ 34715 : node
	★ 34492 : d3
	★ 33394 : jquery
	★ 30484 : Font-Awesome
	★ 28736 : html5-boilerplate
	★ 25849 : awesome
	★ 24954 : rails
	★ 23174 : impress.js
	★ 22520 : meteor
	
	......省略......

	★ 2763 : generator-angular-ful...
	★ 2762 : JazzHands
	★ 2762 : bottle
	★ 2760 : serf
	★ 2757 : nock
	★ 2755 : scraperjs
	★ 2755 : cool-retro-term
	★ 2750 : passenger
	★ 2748 : heatmap.js
	★ 2747 : peerjs
```

# 参考
https://godoc.org/github.com/PuerkitoBio/goquery