Go
GoDay 4

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

More than 5 years have passed since last update.

こんにちは、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

最後に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