230
246

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.

クローラー/WebスクレイピングAdvent Calendar 2017

Day 5

【毎秒1万リクエスト!?】Go言語で始める爆速Webスクレイピング【Golang】

Last updated at Posted at 2017-12-04

# はじめに
Webスクレイピング、みなさん大好きですよね。私は大好きです。
今回は秒間1~10万リクエストを送る方法をご紹介したいと思います。

だだし注意していただきたい所があります。
過剰なリクエストは相手のサーバーに負担をかけ、最悪訴訟問題となります。
有名なものだとLibrahack事件とも呼ばれる岡崎市立中央図書館事件と言うものがあります。
岡崎市立中央図書館事件 - wikipedia
Webスクレイピングの注意事項一覧

また、相手のサーバだけではなく、通信業者(プロバイダ)等から目をつけられる可能性もあります。
最悪の場合通信業界のブラックリストに入り一生ネット契約ができなくなるかもしれません。(存在するかは知りません)

それらを考えた上で、スクレイピングを行いましょう。
(もちろん許可取ったり、著作権とかも考えてね!)

あくまで技術的にこうすればできるよという知見と考えてください。

流れ

・環境情報
・Githubリポジトリ
・Go言語について
・プログラムの流れ
・やってみよう
・実際に手を動かしてやってみよう
・こちらもどうぞ

環境情報

Macbook Air 2015
Go言語:go1.9.1
ソースコード:GitHub

Go言語について

Googleが作ったすごい言語
スクリプト言語みたいな書き方をするくせにクソ早い(Rubyと比較して実測で20倍~50倍とか・・・)
スクリプト言語みたいな書き方をするくせにコンパイル(ビルド)できる(WindowsでもMacでも同じ動きができたりする)
並列処理が得意で最小二文字で並列化できる

MACにGolangをインストールしてみる〜HomeBrew編〜
はじめての Go 言語 (on Windows)

プログラムの流れ

外部のサーバーに数万リクエストを送るのはあまりよろしく無いので、ローカルで仮のサーバーを立てて、それにリクエストを送る形にします。

普通のWebスクレイピングはこんな感じ

main.go  (Webスクレイピングするプログラム)
↓(←アクセスの矢印)
localserver.go(http://localhost:8080/)(Webスクレイピングする対象のサーバ)

これをGoが得意な並列処理を行い大量のリクエストを同時に送信します

こんな感じ

main.go  (Webスクレイピングするプログラム)
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓(←アクセスの矢印)
localserver.go(http://localhost:8080/)(Webスクレイピングする対象のサーバ)

やってみよう

ローカルサーバーを立てよう

まずはローカルサーバーを立てます

localserver.go
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World")		// Hello, Worldってアクセスした人に返すよ!
}

func main() {
	http.HandleFunc("/", handler) 		// http://localhost:8080/にアクセスしてきた人はhandlerを実行するよ!
	http.ListenAndServe(":8080", nil)	// サーバーを起動するよ!
}

実はこれだけでローカルサーバーが立ち上がります(すごい!)
実行して立ち上げます

.sh
$ go run localserver.go

ブラウザでhttp://localhost:8080/にアクセスするとHello, Worldと表示されるはずです。

スクリーンショット 2017-12-01 17.55.33.png

[Go言語でhttpサーバーを立ち上げてHello Worldをする]
(https://qiita.com/taizo/items/bf1ec35a65ad5f608d45)

Webスクレイピングしよう

次にWebスクレイピングします

main.go
package main

import (
	"net/http"
	"io/ioutil"
	"fmt"
)


func main() {
	url := "http://localhost:8080"	// アクセスするURLだよ!

	resp, err := http.Get(url)		// GETリクエストでアクセスするよ!
	if err != nil {					// err ってのはエラーの時にエラーの内容が入ってくるよ!
		panic(err)					// panicは処理を中断してエラーの中身を出力するよ!
	}
	defer resp.Body.Close() 		// 関数が終了するとなんかクローズするよ!(おまじない的な)
	
	byteArray, err := ioutil.ReadAll(resp.Body) // 帰ってきたレスポンスの中身を取り出すよ!
	if err != nil {
		panic(err)
	}
	fmt.Println(string(byteArray)) 	// 取り出したリクエスト結果をバイナリ配列からstring型に変換して出力するよ!
}

これでサーバーlocalhost:8080にGetリクエストを行い、HTMLを頂きます。

実行するとコンソールとかに以下のようにHello, Worldが帰ってきます

.sh
$ go run main.go
Hello, World

Goでhttpリクエストを送信する方法

並列化して1万リクエスト送ろう!

ここからが本題です。上記のリクエストをGOが得意な並列化処理を加えて実行します。
Go言語で並列処理を行う方法は色々ありますが、今回は同時にアクセスを行う数を制御しやすいように
チャンネルと呼ばれるものを使って並列処理します。
また、全てのリクエストが終了するのを持つためにWaitGroupを使用してます。
各所にそれっぽいことをコメントアウトで説明しています。
挙動を詳しく説明すると長くなるのでgolang WaitGroup golang channel で検索をかけてみましょう。

main.go
package main

import (
	"net/http"
	"sync"
	"log"
	"time"
)


func main() {
	url := "http://localhost:8080"			// アクセスするURLだよ!

	maxConnection := make(chan bool,200)	// 同時に並列する数をしていできるよ!(第二引数)
	wg := &sync.WaitGroup{}					// 並列処理が終わるまでSleepしてくれる便利なやつだよ!

	count := 0								// いくつアクセスが成功したかをアカウントするよ!
	start := time.Now()						// 処理にかかった時間を測定するよ!
	for maxRequest := 0; maxRequest < 10000; maxRequest ++{		// 10000回リクエストを送るよ!
		wg.Add(1)						// wg.add(1)とすると並列処理が一つ動いていることを便利な奴に教えるよ!
		maxConnection <- true				// ここは並列する数を抑制する奴だよ!詳しくはググって!
		go func() {							// go func(){/*処理*/}とやると並列処理を開始してくれるよ!
			defer wg.Done()					// wg.Done()を呼ぶと並列処理が一つ終わったことを便利な奴に教えるよ!

			resp, err := http.Get(url)		// GETリクエストでアクセスするよ!
			if err != nil {					// err ってのはエラーの時にエラーの内容が入ってくるよ!
				return						// 回線が狭かったりするとここでエラーが帰ってくるよ!
			}
			defer resp.Body.Close() 		// 関数が終了するとなんかクローズするよ!(おまじない的な)

			count++							// アクセスが成功したことをカウントするよ!
			<-maxConnection					// ここは並列する数を抑制する奴だよ!詳しくはググって!
		}()
	}
	wg.Wait()								// ここは便利な奴が並列処理が終わるのを待つよ!
	end := time.Now()						// 処理にかかった時間を測定するよ!
	log.Printf("%d 回のリクエストに成功しました!\n", count)	// 成功したリクエストの数を表示してくれるよ!
	log.Printf("%f 秒処理に時間がかかりました!\n",(end.Sub(start)).Seconds())			//何秒かかったかを表示するよ!
}

これを実行します

go run main.go
2017/12/01 18:47:00 9816 回のリクエストに成功しました!
2017/12/01 18:47:00 0.987664 秒処理に時間がかかりました!

スペック的な問題で私は並列処理は200同時が限界でした。。。
ハイスペックなデスクトップなどで並列処理1万同時に行うと10万リクエストを5秒切るレベルでできるかもしれません。

手を動かしてやってみよう

プログラムを打ち込んだりするのが面倒な方にソースコードとか色々をGitHubに公開しています。
※ Go言語でHello Worldできる環境が必要です。
Azunyan1111 - web-scraping-go - GitHub

.sh
$ git clone https://github.com/Azunyan1111/web-scraping-go
$ cd web-scraping-go/
$ make setup
go get ./...
$ make start
go run main/localserver.go &
go run main.go
2017/12/01 18:59:33 9887 回のリクエストに成功しました!
2017/12/01 18:59:33 1.596906 秒処理に時間がかかりました!
killall localserver
signal: terminated

こちらもどうぞ

Python Webスクレイピング 実践入門
Python Webスクレイピング テクニック集「取得できない値は無い」JavaScript対応
【初心者向け】Re:ゼロから始める遺伝的アルゴリズム【人工知能】

間違っていたり、変な部分があれば編集リクエストバシバシ送ってください。

230
246
5

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
230
246

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?