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


Goのスクレイピング系ライブラリまとめの中からgoqueryをピックアップして試してみました。
ターゲットはGithubRanking
ちなみに、ただただランキング情報を取得したい場合はGithubAPIを使えばいけます。

ターゲット

利用ライブラリ

  • goquery
    • jQueryに近しい関数が用意されており、セレクタも使えるスーパー便利なライブラリ

いざ、スクレピング!

goqueryの追加

% go get github.com/PuerkitoBio/goquery

DOMの調査

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

プログラム

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関数で指定したセレクタにヒットする要素の中から1番目の要素を取得

func (*Selection) Attr

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

実行

結果

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

参考