290
264

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GoAdvent Calendar 2013

Day 4

goqueryでお手軽スクレイピング!

Last updated at Posted at 2013-12-04

こんにちは、Qiita二回目の投稿です、 yosuke_furukawa と申します、Golang勉強中です。

Qiitaの一回目の投稿もScrapingネタだったのですが、二回目もGolang勉強中ということでQiitaのScrapingネタで行きます。

まず、goqueryの説明に行く前に単純なやり方でscrapingしてみます。

Golangで単純なスクレイピングをするためには、以下のモジュールを利用するとできます。

超シンプルなサンプルとして、該当のurlのaタグのhrefにある値だけ取得する場合は以下の様な感じになるかと思います。

go.net/htmlで頑張る

package main

import (
     "code.google.com/p/go.net/html"
     "fmt"
     "io"
     "net/http"
)

type Result struct {
     Url string
}

func ParseItem(r io.Reader) []Result {
     results := []Result{}
     doc, err := html.Parse(r)
     if err != nil {
          fmt.Println(err)
     }

     var result Result
     var f func(*html.Node)
     f = func(n *html.Node) {
          // n.Typeでノードの型をチェックできる、ElementNodeでHTMLタグのNode。
          // n.Dataでノートの値をチェックする、aタグをチェックしている
          if n.Type == html.ElementNode && n.Data == "a" {
               // n.Attrで属性を一覧する
               // ここでもう少し頑張るとparseできる
               for _, a := range n.Attr {
                    if a.Key == "href" {
                         result.Url = a.Val
                         results = append(results, result)
                    }
               }
          }
          for c := n.FirstChild; c != nil; c = c.NextSibling {
               f(c)
          }
     }
     f(doc)
     return results
}

func GetPage(url string) []Result {
     //http.GetでGetリクエストを発行する
     res, err := http.Get(url)
     if err != nil {
          fmt.Println(err)
     }
     // deferでやるとReaderを関数の終わりで必ずCloseしてくれる。便利!!
     defer res.Body.Close()
     results := ParseItem(res.Body)
     return results
}

func main() {
     url := "http://qiita.com/advent-calendar/2013/"
     results := GetPage(url)
     for _, result := range results {
          fmt.Println(result.Url)
     }
}

go-html-transformを使う

また、単純にページから情報を抽出する、というよりもDOMの中から不要なものを削ったり、ページを加工するならcode.google.com/p/go.net/htmlよりもcode.google.com/p/go-html-transform/html/transformを使うのがいいかと思います。
CSSセレクタが使えるので、いい感じにfilterを書けます。(ただ名前の通りhtmlの加工目的なので、htmlから必要な情報を抽出するのには向かない気もします。)

package main

import (
     "code.google.com/p/go-html-transform/html/transform"
     "fmt"
     "io"
     "net/http"
)

type Result struct {
     Url string
}

func ParseItem(r io.Reader) {
     // transformのインスタンスを作る
     t, _ := transform.NewFromReader(r)
     // Applyメソッドで自分のDOMに反映する。
     // Applyメソッド内では、TransformFuncを受け付けるようになっており、
     // Replaceの他にもDOMを追加するAppendChildrenやPrependChildrenなどもある。
     // ページを加工するならこっちのが便利。
     // ちなみに以下の処理で不要なページを削っている
     t.Apply(transform.Replace(), "script")
     t.Apply(transform.Replace(), "footer")
     t.Apply(transform.Replace(), "meta")
     t.Apply(transform.Replace(), "ul")
     t.Apply(transform.Replace(), "li")
     t.Apply(transform.Replace(), "link")
     t.Apply(transform.Replace(), "div.day")
     t.Apply(transform.Replace(), "div.advent-calendar-breadcrumb")
     t.Apply(transform.Replace(), "a.post-user-icon")
     fmt.Println(t.String())
}

func GetPage(url string) {
     //http.GetでGetリクエストを発行する
     res, err := http.Get(url)
     if err != nil {
          fmt.Println(err)
     }
     // deferでやるとReaderを関数の終わりで必ずCloseしてくれる。便利!!
     defer res.Body.Close()
     ParseItem(res.Body)
}

func main() {
     url := "http://qiita.com/advent-calendar/2013/"
     GetPage(url)
}

goqueryを使う

と、色々と書きましたが、jQueryライクなセレクタを持ち、httpのリクエストの処理も兼ね備えているgoqueryを使うのがスクレイピングには一番向きます。Golangの勉強ということで、なるべくモジュール組み合わせて、試したかったのです。すいません。

package main

import (
     "fmt"
     "github.com/PuerkitoBio/goquery"
)

func GetPage(url string) {
     doc, _ := goquery.NewDocument(url)
     doc.Find("a").Each(func(_ int, s *goquery.Selection) {
          url, _ := s.Attr("href")
          fmt.Println(url)
     })
}

func main() {
     url := "http://qiita.com/advent-calendar/2013/"
     GetPage(url)
}

めちゃくちゃ短くできる!!!!!!!!
しかも直感的!!!!!!!!!

goqueryは既にmattnさんが記事にしてくれてるので、それも参考になると思います。
Go言語で jQuery ライクな操作が出来る goquery を試した。

goquery

最後にgoqueryとgoroutine、channelで並列処理でQiitaのこれまでのエントリをscrapingして出力して終わります。コードはコチラ。

package main

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"os"
	"sync"
)

type Result struct {
	CalTitle string
	Title    string
	Url      string
}

func GetPage(url string) []Result {
	results := []Result{}
	doc, _ := goquery.NewDocument(url)
	doc.Find("a.calendar-name").Each(func(_ int, s *goquery.Selection) {
		url, exists := s.Attr("href")
		if exists {
			caltitle := s.Text()
			entryPage, _ := goquery.NewDocument("http://qiita.com" + url)
			entryPage.Find("div.body h1>a").Each(func(_ int, s *goquery.Selection) {
				url, exists := s.Attr("href")
				if exists {
					result := Result{caltitle, s.Text(), url}
					results = append(results, result)
				}
			})
		}
	})
	return results
}

func GoGet(urls []string) <-chan []Result {
	var wg sync.WaitGroup
	ch := make(chan []Result)
	go func() {
		for _, url := range urls {
			wg.Add(1)
			go func(url string) {
				ch <- GetPage(url)
				wg.Done()
			}(url)
		}
		wg.Wait()
		close(ch)
	}()
	return ch
}

func main() {
	args := os.Args
	if len(args) < 2 {
		panic("usage : goquery <url>")
	}

	urls := []string{}
	for index, arg := range args {
		if index != 0 {
			urls = append(urls, arg)
		}
	}

	ch := GoGet(urls)
	for {
		results, ok := <-ch
		if !ok {
			return
		}
		calTitle := ""
		for _, result := range results {
			if calTitle != result.CalTitle {
				fmt.Println("#" + result.CalTitle)
				calTitle = result.CalTitle
			}
			fmt.Println("[" + result.Title + "](" + result.Url + ")")
		}
	}
}

リポジトリはコチラ:
https://github.com/yosuke-furukawa/goquery_sample

使い方:

$ goquery http://qiita.com/advent-calendar/2013 http://qiita.com/advent-calendar/2012

Qiita アドベントカレンダー一覧(2013一覧):

#1分で実現できる有用な技術
1分でPAGER環境変数を設定したあとページャーを知る
1分で実現できるtmuxのTips x3 (ついでにinstall to Mac,CentOS,Debian/Ubuntu)
1分で実現できるjavascriptにおけるconsoleまわりの有用なテクニック4つ
#Amazon Web Service
AWSクレジットの引き換えエラーメッセージまとめ
AWS CLIの --query オプションが便利。
Cross-Zone Load Balancing を有効にしない理由がない件
AWSを便利に使う為の情報・ツール
#AngularJS Startup
AngularJSを使ったWebアプリのアーキテクチャ設計
AngularJSのTutorialのstep-1とstep-2よりData Bindingの仕組みをレポート
AngularJSのはじめの一歩(詳細)
AngularJSを選択する見解
#Ansible
Ansibleちょっとしたメモ
Ansibleの事例とちょっとしたTips
#Ceylon
Ceylonモジュール作成やクラスなどの基本コード
Ceylonの良いとこ説明コードその1
Ceylon開発環境セットアップ!
Java大好きな人が作ったCeylon言語ってどんなもの?
#Civic Tech
Open Government Licenceの紹介
21世紀型の都市が持つべき7つの戦略とCivic Hackerへのマインドシフト
Beyond Transparency の紹介
地域課題解決の新しい形、Civic Tech と Code for Japan
#Clojure
Clojureで音楽組織プログラミングについて
Clojureを練習するためのオンライン問題集
clj-webdriverの紹介
#cocos2d-x
【まとめ】cocos2d-Xmas Special by #gumistudy〜 Chukong Technologies(coco2d-x開発元)他登壇!
簡単なカスタムボタンの作り方(2)
透過画像に対応したコリジョン判定
簡単なカスタムボタンの作り方
cocos2d-xでのJSON利用方法
#CodeIgniter
自作クラスでCodeIgniterオブジェクトを使用する
【基礎】CodeIgniterでライブラリを作成する
【基礎】CodeIgniterでコアクラスを作成する
#Corona
Corona SDK + Parse.com でプッシュ通知をする
Kwikを買いました。
今年のCoronaSDKの振返り
#Delphi
カメラの画角を変える方法
#Doc-ja
Translate Toolkitで翻訳ツールを作る
続・フリーなオフィススィートを翻訳すること
WordPressプラグインを題材にGettextでの翻訳方法を紹介
フリーなオフィススィートを翻訳すること
#D言語
D言語で、コンパイル時単体テスト
DustMiteで、バグ再現最小コードを作る
D言語 Language Update 2013
#Elixir
mix newで作られるファイル探索
elixir on Android
fluent-logger-elixirのはなし
#Fluentd
Fluentd のベンチマークテストに使える dummy_log_generator の紹介
#G*(Groovy, Grails ..)
Cucumber-Groovyでfeatureの書き方を手順を追ってみる
Cucumber-groovy設定編
#Git
Gitのコミットログ再考
プルリクエストを自動補完してcheckoutする
#Google Apps Script
Google Formでクイズして、自動で答え合わせして、メールまで送っちゃうお
公式ガイドの「Dialogs and Sidebars in Google Apps」を制覇する!(その1)
2013年度版 5分で始めるWebアプリケーション(Google Apps Script)
#GorillaScript
GorillaScript簡易ガイドブック
#Grunt Plugins
CSSプロパティの重複を解析してくれるgrunt-csscssについて紹介するよ
CSS書く人なら絶対入れとけのgrunt-contrib-csslintについて紹介するよ
タスクが正常に終わったらチャットワークにメッセージを通知する grunt-chatwork を紹介するよ
散乱した@mediaをまとめてくれるgrunt-combine-media-queriesを紹介するよ
フロントエンドだけじゃない! サーバサイドの開発も手助けしてくれる grunt-connect-proxy を紹介するよ
JSONファイルの管理をちょっとだけ楽にしてくれるgrunt-sync-versionを紹介するよ
なんでも開ける grunt-open について紹介するよ
イケてるスタイルガイドを簡単に作れるgrunt-kssについて紹介するよ
CSSプロパティをソートしてくれるgrunt-csscombについて紹介するよ
高いCSS圧縮率を誇るgrunt-cssoについて紹介するよ
JSコードの品質チェックをしてくれるgrunt-platoについて紹介するよ
ページ表示速度の問題チェックをしてくれるgrunt-pagespeedについて紹介するよ
Gitフックを仕込むgrunt-githooksについて紹介するよ
#Haxe
Haxeの関数値について雑多なこと
Haxeの文字列
知ってると色々と世界が広がるかもしれないHaxeのコンパイルオプション
#HDL
Verilog HDLでの数値リテラル(定数)の書き方
SFLのstageとNSLのseqを攻略する
[SystemVerilog]即時アサーションでコネクティビティをチェックする。
[HDL][雑記] 言語トレンドを見てみよう
#hubot-scripts
Hubot-scriptsでサイコロを投げる
Hubot-scriptsのmsg.randomを学ぶ
はじめてのHubot
Hubot-scriptsの学びかた
#iBeacon
iBeacon開発ハマリどころポイントまとめ
iBeacon で忍者が密会する
iBeaconを利用したアプリ開発でチェックしておきたい!良記事・ソースコードまとめ
1行もコードを書かずにiBeaconで遊んでみる
#IntelliJ IDEA
IntelliJ IDEA 13がリリースされました!
設定、プラグイン導入した後にこれだけはやっとけってやつ
IntelliJ IDEAをチームで導入するために私が行ったこと
IntelliJ IDEAをインストールしたら設定してること(Java/Groovy編)
#Jenkins CI
ALMiniumをVirtualBox上のCentOSにインストールするのにJenkinsを使ってみたお話
Jenkinsビルド後の処理でRedmineにチケット登録ができるプラグインを作った話
#JSX
JSX + npm
ライブラリからみるJSXの特徴
#Laravel
Model で Validation したい? それならば Ardent だ!
Laravel の現状と拡張の話(読み物)
最小構成で始めるLaravel
日本人に優しいLaravel
#Lisp
'`'と','
#Max/MSP/Jitter
Max/MSPの様々なGUIパーツ
Max/MSPの便利な操作方法
Max/MSPの入門レシピいろいろ
Max/MSP/Jitterをはじめるメモ
#Mojolicious
Mojoliciousのテンプレートでレイアウトを自在に操る
Mojoliciousのセッションの話 2013年年末版
Mojoliciousの様々な立ち上げ方
#MongoDB
Mongo shellの裏技色々
MongoEngineでMongoDBを触ってみる基礎編
Ruby初心者がRuby on Rails + Mongoidを試してみた
MongoDBとブラウザだけで旅の記録をつけてみるか その1
#mruby
GUIアプリケーションなどが保持するmrubyのオブジェクトのGC対策
mruby-redisでランキングを実装
mrubyのビルド方法
#NEET
Cryptocurrency Mining、始めてみませんか?
NEET Advent Calendar作りました
NEETでGeekな情報収集術
全国のNEET達に送る、プログラミングの始め方。
#openFrameworks
ofxFaceTracker で顔をリアルタイムにトラッキングする
openFrameworks for iOS のサンプル一覧
openFrameworks for iOS ことはじめ
#OpenStreetMap
ライセンス表記に注意しよう(自戒の念を込めて)
ライトマッパーが1年を振り返るよ
Surveyorジャケットの日本版作成とか
#Perl
普通のデーモンを 1) Server::Starterでホットデプロイ+ 2) slow-restart対応にする
Twiggy で応答の遅いサーバーを模倣する
HTTPクライアントとStream::Bufferedの合わせ技
HTTP::Message::PSGIでPSGIアプリのテストを書く
Log::StringFormatter でログ文字列のフォーマット
#PostgreSQL
PostgreSQLのあまり知られていない型3種
#Python
LXCをPythonから操作する
テキストファイルから指定した文字列を含む行を出力する
#Ruby on Rails
てめえらのRailsはオブジェクト指向じゃねえ!まずはCallbackクラス、Validatorクラスを活用しろ!
websocket-railsで簡単なPush通知を実装する
Rails4に対応したgem doorkeeperついて調べてみた。
Railsのコンソールをより便利にするpry-rails gem
#RubyMotion
Parse.com で Push Notification with Rubymotion
motion-modeの紹介とruby-modeについて
RubyMotionアプリ開発に、motion-mode + Rubocop を導入
Rubyist が RubyMotion で iOS アプリの開発を始める方法
#Rust
Rust Advent Calendar 12/2分を20分ででっちあげる
#TDD
ノーマルにMSTestを使おう
#WebPay
WebPayLiteを使ってiOSから簡単にクレジットカード決済する
少しのコードでWebPayを導入する PHP Ver.
少しのコードでWebPayを導入する
#Windows Azure
Windows Azure Cloud ServiceでPython/Djangoを使おう!
Windows Azure仮想マシンのディスクをPowerShellでバックアップする
私が Windows Azure Web サイトを大好きな 7 つの理由
#Windows Store App
スタート画面っぽくいろんなサイズのタイルを GridView に表示しちゃうやり方
WinJS.Bindingについて
ストアアプリで手軽に画像を加工しよう
markSupportedForProcessingについて調べたかった
#Xamarin
Xamarin.iOSでUITableViewの罠 / Xamarinの進化に望む事
XamarinでParse SDKを利用する
Xamarin(ザマリン) とはなんぞや
#zsh
zshにオプションや引数を補完できるキーバインドを設定しよう
"select-word-style bash"の不満を解消する
bundle exec を打たなくて良くなる zsh プラグイン書いた
zsh で find を使わずに簡単にファイルを絞り込む
#カーネル/VM
カーネル/VM Advent Calendar 2013 3日目:LinuxカーネルのlockrefというLock機能を試してみよう
カーネル/VM Advent Calendar 2013 1日目
#ディストリビューション/パッケージマネージャー
aptitude検索パターンの紹介
debian-goodies
抄訳 games-misc/fortune-mod-gentoo-dev
Gentoo Wikiの翻訳
#どう書く過去問
等差数列(2013.5.17の過去問)
ボールカウント:野球(2012.8.8の過去問)
のんびり座りたい (2013.2.2の過去問)
#ドキュメンテーション
プロジェクト管理WebアプリBrabio!のガントチャートをプレゼン用に綺麗に出力する
#マイナー言語
明日から使えるasm.js - Low Level JavaScript - 「LLJS」 マイナー言語アドベントカレンダー・一日目
#みんなでやるRiak
Riak2を複数ノードで動かしてみる
Riak2にデータを出し入れしてみる
Riak2をインストールしてみる
#ラテン語プログラミング
あらこんな所にラテン語が。いつも見るあの語が実は・・・
自由に使えるラテン語辞書データ (I)
ラテン語文解析プログラムを書くことを目的としたラテン語学習(前編)
VB5で作ったラテン語アプリを発掘
#全文検索エンジンGroonga
リポジトリを横断してコミットメッセージを眺めるツールgglogを使ってみた
GObject Introspectionを使っていろいろな言語からGroongaを使う方法
#設定ファイル「dotfiles」の作り方
vim-powerline (lightline.vim)
zsh-powerline
tmux-powerline

iOS Second Stage

iOS 7でバーコード・QRコードを読み取る方法と生成する方法(おまけもあるよ)
複数のStoryboardを使ってタブの遷移を作成する
iOSで使える日本語OKな音声読み上げエンジン8種(TTS,音声合成)
Xcodeと自動化
#.emacs
emacs のリージョンや編集中のバッファを DayOne の Dropbox に保存する拡張をつくった
自分流の .emacs管理
anzu.elの紹介
ぼくの.emacs 2013
#Android
BeagleBone BlackにAndroidをインストールする
Gradleことはじめ
#Go
Androidでʕ ◔ϖ◔ʔGo~
goyaccを使う
Go言語で顔認識してみた
#iOS
XIBからもコードからもカスタムViewインスタンスを生成する方法
「顔以外」のものを画像認識する
IB/Storyboard使わない派のlayoutSubviewsによるレイアウト調整
#JavaScript - Client Side -
BakboneJSだってDOMとModelをBindingしたい!
なぜクライアントサイドJavaScriptにはHTML/CSSの知識が必要か?
Ember.jsのコアな機能を学ぶためのサンプルプロジェクト
#Machine Learning
Random Forest とその派生アルゴリズムの紹介
Dropoutの実装と重みの正則化(MLAC 2013 3日目)
たぶん1分くらいでできる形態素解析とtfidf(テストコードつき)
ベイズ線形回帰(PRML§3.3)の図版再現
#PHP
PHPの開発に使えるVagrantfileのまとめ
#PhpStorm
コードフォーマットを使いこなす
PhpStorm EAPを使ってみる
PhpStormをVimキーマップで使う
PhpStormとは?
#Ruby
websocket-client-simple でPush通信を使ってJenkinsさんと会話
Ruby 2.1.0-preview2で追加されたException#causeの紹介
RailsとGrapeで行う最高のWeb API開発
#Scala
Lift の RestHelper でサクサクAPI 開発
2014年こそScalaを始めよう
#Unity
Unity 途別2Dアセット
Transform拡張
MoveAssetToTrashでUndo/Redo

290
264
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
290
264

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?