こんにちは、Qiita二回目の投稿です、 yosuke_furukawa と申します、Golang勉強中です。
Qiitaの一回目の投稿もScrapingネタだったのですが、二回目もGolang勉強中ということでQiitaのScrapingネタで行きます。
まず、goqueryの説明に行く前に単純なやり方でscrapingしてみます。
Golangで単純なスクレイピングをするためには、以下のモジュールを利用するとできます。
- net/http httpリクエストを送るためのモジュール
- code.google.com/p/go.net/html html解析をするためのモジュール
超シンプルなサンプルとして、該当の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と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