こんにちは。現役エンジニアの”はやぶさ”@Cpp_Learningです。
Goのスクレイピングフレームワーク Colly を活用したWebスクレイピングをソースコード付きで紹介します。
Contents
Collyとは
Collyとは、高機能かつ高速なWebスクレイピングを実現できるGophers向けフレームワークです。詳細については公式サイトをご確認ください。
Webスクレイピング入門
環境構築やWebスクレイピング前に確認すべきことを紹介します。
Go開発環境構築
はじめにgolang:1.14の開発環境構築をします。色んな構築方法がありますが、Dockerを使う場合は、以下の記事を参考にして下さい。
続いて、公式 README.md にある go.mod をダウンロードします。お好みですが、今回は一部変更した以下の go.mod を用意します。
1 2 3 4 5 6 7 8 |
module example.com/myapp go 1.14 require ( github.com/gocolly/colly/v2 v2.1.0 ) |
以下のコマンドでCollyをインストールします。
go mod download
以上で環境構築完了です。
事前確認【robots.txt】
スクレイピング対象のサイトが、クローラを禁止しているか否かを robots.txt で確認します。本サイト:はやぶさの技術ノートの場合は、以下のURLから確認できます。
robots.txt の説明は割愛しますが、中身を理解できない内は、クローラやスクレイピングなどを活用したサイトの情報抽出は控えた方が良いかと。
CollyによるWebスクレイピング入門【Step1】
以降からGoのコードを書いていきます。スクレイピング対象のURLを指定して、HTMLから<title>要素のコンテンツを抽出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main import ( "fmt" "github.com/gocolly/colly/v2" ) func main() { // Target URL url := "https://cpp-learning.com" // Instantiate default collector c := colly.NewCollector() // Extract title element c.OnHTML("title", func(e *colly.HTMLElement) { fmt.Println("Title:", e.Text) }) // Before making a request print "Visiting URL: https://XXX" c.OnRequest(func(r *colly.Request) { fmt.Println("Visiting URL:", r.URL.String()) }) // Start scraping on https://XXX c.Visit(url) } |
このソースコードを app.go という名前で保存し、以下のコマンドを実行すると、「訪問先のURL」と「タイトル」が表示されます。
$ go run app.go
Visiting URL: https://cpp-learning.com
Title: はやぶさの技術ノート|理系に役立つ情報
以上が Colly の基本的な使い方です。
html-css-cheat-sheet が最高なので、スクレイピング前に確認しておくと良い
CollyによるWebスクレイピング実践
スクレイピングの主な目的は、情報収集だと思うので、サイトから抽出した情報をデータベースやファイルに保存して、後から確認できるようにします。本記事ではスクレイピング結果をJSONファイルに保存する方法を紹介します。
スクレピング結果をJSONファイルに保存【Step2】
app.go を改良して、以下の機能を追加します。
- HTTP のレスポンスステータスコード確認
- スクレイピング結果をJSONファイルに保存
完成したソースコードが以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
package main import ( "fmt" "os" "log" "encoding/json" "github.com/gocolly/colly/v2" ) type pageInfo struct { StatusCode int `json:"statusCode"` URL string `json:"url"` Title string `json:"title"` } func savePageJson(fName string, p *pageInfo) { // Create json file file, err := os.Create(fName) if err != nil { log.Fatalf("Cannot create file %q: %s\n", fName, err) return } defer file.Close() // Dump json to the standard output // err = json.NewEncoder(file).Encode(p) // if err != nil { // log.Fatal(err) // } // Dump json to the standard output enc := json.NewEncoder(file) enc.SetIndent("", " ") err = enc.Encode(p) if err != nil { log.Fatal(err) } // Struct to json b, _ := json.MarshalIndent(p, "", " ") fmt.Println(string(b)) // fmt.Println(p) } func main() { // Target URL url := "https://cpp-learning.com" p := &pageInfo{} // Instantiate default collector c := colly.NewCollector() // Extract title element c.OnHTML("title", func(e *colly.HTMLElement) { p.Title = e.Text fmt.Println(e.Text) }) // Before making a request print "Visiting URL: https://XXX" c.OnRequest(func(r *colly.Request) { p.URL = r.URL.String() fmt.Println("Visiting URL:", r.URL.String()) }) // After making a request extract status code c.OnResponse(func(r *colly.Response) { p.StatusCode = r.StatusCode fmt.Println("StatusCode:", r.StatusCode) }) c.OnError(func(r *colly.Response, err error) { p.StatusCode = r.StatusCode log.Println("error:", r.StatusCode, err) }) // Start scraping on https://XXX c.Visit(url) // Wait until threads are finished c.Wait() // Save as JSON format savePageJson("page.json", p) } |
本コード実行後に生成される page.json が以下です。
1 2 3 4 5 |
{ "statusCode": 200, "url": "https://cpp-learning.com", "title": "はやぶさの技術ノート|理系に役立つ情報" } |
はやぶさの技術ノートから最新投稿の情報を抽出【Step3】
本サイト:はやぶさの技術ノートでは、最新投稿の情報をサイドバーで公開中です(下図参照)。
上図にある最新記事5つの「タイトル」と「URL」を抽出するコードが以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
package main import ( "fmt" "os" "log" "encoding/json" "github.com/gocolly/colly/v2" ) type articleInfo struct { Title string `json:"title"` URL string `json:"url"` } func saveArticlesJson(fName string, a []articleInfo) { // Create json file file, err := os.Create(fName) if err != nil { log.Fatalf("Cannot create file %q: %s\n", fName, err) return } defer file.Close() // Dump json to the standard output enc := json.NewEncoder(file) enc.SetIndent("", " ") err = enc.Encode(a) if err != nil { log.Fatal(err) } // Struct to json b, _ := json.MarshalIndent(a, "", " ") fmt.Println(string(b)) // fmt.Println(p) } func main() { // Target URL url := "https://cpp-learning.com" articles := make([]articleInfo, 0, 4) // Instantiate default collector c := colly.NewCollector() i := 0 // Extract li class="new-entry-item" c.OnHTML("li[class=new-entry-item]", func(e *colly.HTMLElement) { i++ fmt.Println(i) // Extract h3 element title := e.ChildText("h3") // title = e.DOM.Find("h3").Text() fmt.Println(title) // Extract href link, _ := e.DOM.Find("a[href]").Attr("href") fmt.Println(link) article := articleInfo{ Title: title, URL: link, } articles = append(articles, article) }) // Before making a request print "Visiting URL: https://XXX" c.OnRequest(func(r *colly.Request) { fmt.Println("Visiting URL:", r.URL.String()) }) // After making a request extract status code c.OnResponse(func(r *colly.Response) { fmt.Println("StatusCode:", r.StatusCode) }) c.OnError(func(r *colly.Response, err error) { log.Println("error:", r.StatusCode, err) }) // Start scraping on https://XXX c.Visit(url) // Wait until threads are finished c.Wait() // Save as JSON format saveArticlesJson("articles.json", articles) } |
要素だけでなく、id・class・hrefも指定してスクレイピングできます。本コード実行後に生成される articles.json が以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[ { "title": "【GopherJS入門】GoのソースコードからJavaScriptを生成", "url": "https://cpp-learning.com/gopherjs/" }, { "title": "AutoMLライブラリのFLAMLで機械学習モデルの作成を自動化する", "url": "https://cpp-learning.com/flaml/" }, { "title": "機械学習に関する図や資料の作成に役立つツールまとめ", "url": "https://cpp-learning.com/draw-nn/" }, { "title": "【Isolation Forest】決定木で説明性・解釈性を考慮した異常検知", "url": "https://cpp-learning.com/isolationforest/" }, { "title": "【KEY COFFEE】コスパ最強ドリップコーヒー「DRIP ON」のレビュー -仕事や勉強が捗る至高の一杯-", "url": "https://cpp-learning.com/key-coffee-dripon/" } ] |
ページと最新投稿の情報を1つのJSONファイルに保存【Step4】
最後に、以下の情報を1つのJSONファイルに保存するコードを紹介します。
- 【STEP2】で抽出した「ページ情報」
- 【STEP3】で抽出した「最新記事」
完成したソースコードが以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
package main import ( "fmt" "os" "log" "encoding/json" "github.com/gocolly/colly/v2" ) type articleInfo struct { Title string `json:"title"` URL string `json:"url"` } type pageInfo struct { StatusCode int `json:"statusCode"` URL string `json:"url"` Title string `json:"title"` Article []articleInfo `json:"article"` } func savePageJson(fName string, p *pageInfo) { // Create json file file, err := os.Create(fName) if err != nil { log.Fatalf("Cannot create file %q: %s\n", fName, err) return } defer file.Close() // Dump json to the standard output enc := json.NewEncoder(file) enc.SetIndent("", " ") err = enc.Encode(p) if err != nil { log.Fatal(err) } // Struct to json b, _ := json.MarshalIndent(p, "", " ") fmt.Println(string(b)) } func main() { // Target URL url := "https://cpp-learning.com" p := &pageInfo{} articles := make([]articleInfo, 0, 4) // Instantiate default collector c := colly.NewCollector() // Extract title element c.OnHTML("title", func(e *colly.HTMLElement) { p.Title = e.Text fmt.Println(e.Text) }) i := 0 // Extract li class="new-entry-item" c.OnHTML("li[class=new-entry-item]", func(e *colly.HTMLElement) { i++ fmt.Println(i) // Extract h3 element title := e.ChildText("h3") fmt.Println(title) // Extract href link, _ := e.DOM.Find("a[href]").Attr("href") fmt.Println(link) article := articleInfo{ Title: title, URL: link, } articles = append(articles, article) p.Article = articles }) // Before making a request print "Visiting URL: https://XXX" c.OnRequest(func(r *colly.Request) { p.URL = r.URL.String() fmt.Println("Visiting URL:", r.URL.String()) }) // After making a request extract status code c.OnResponse(func(r *colly.Response) { p.StatusCode = r.StatusCode fmt.Println("StatusCode:", r.StatusCode) }) c.OnError(func(r *colly.Response, err error) { p.StatusCode = r.StatusCode log.Println("error:", r.StatusCode, err) }) // Start scraping on https://XXX c.Visit(url) // Wait until threads are finished c.Wait() // Save as JSON format savePageJson("hayabusa-new-articles.json", p) } |
本コード実行後に生成される hayabusa-new-articles.json が以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
{ "statusCode": 200, "url": "https://cpp-learning.com", "title": "はやぶさの技術ノート|理系に役立つ情報", "article": [ { "title": "【GopherJS入門】GoのソースコードからJavaScriptを生成", "url": "https://cpp-learning.com/gopherjs/" }, { "title": "AutoMLライブラリのFLAMLで機械学習モデルの作成を自動化する", "url": "https://cpp-learning.com/flaml/" }, { "title": "機械学習に関する図や資料の作成に役立つツールまとめ", "url": "https://cpp-learning.com/draw-nn/" }, { "title": "【Isolation Forest】決定木で説明性・解釈性を考慮した異常検知", "url": "https://cpp-learning.com/isolationforest/" }, { "title": "【KEY COFFEE】コスパ最強ドリップコーヒー「DRIP ON」のレビュー -仕事や勉強が捗る至高の一杯-", "url": "https://cpp-learning.com/key-coffee-dripon/" } ] } |
以上の【STEP1~4】で任意サイトから複数の情報を抽出して、1つのJSONファイルにまとめて保存する所までガイドできたと思います。
まとめ
Goのスクレイピングフレームワーク Colly を活用したWebスクレイピングをソースコード付きで紹介しました。
本記事のソースコードを改良すれば、任意サイトから複数の情報を抽出して、1つのJSONファイルにまとめて保存する所まで、できるようになると考えています。
以下 オススメ書籍を紹介。