Goを使用したウェブスクレイピング:完全ガイド

このガイドでは、Goを使ってウェブサイトをゼロからスクレイピングする方法と、Goがスクレイピングに最適な言語である理由について説明します。
6 min read
Web Scraping in Go

このチュートリアルでは、Goがウェブを効率的にスクレイピングするのに最適な言語の1つである理由、およびGoスクレイパーをゼロから構築する方法を説明します。

この記事の内容:  

Goを使用してウェブスクレイピングすることは可能か?

Goは、Golangとも呼ばれ、Googleが作った静的型付けプログラミング言語です。効率的で、並行処理が可能で、記述と保守が容易に行えるように設計されています。これらの特徴から、最近ではウェブスクレイピングをはじめとするいくつかの用途でGoがよく使われるようになっています。

特に、Goはウェブスクレイピングタスクに関して便利で強力な機能を提供します。複数のウェブリクエストの同時処理をサポートする並行処理モデルも内蔵されています。このため、Goは複数のウェブサイトから大量のデータを効率よくスクレイピングするのに最適な言語といえます。また、Goの標準ライブラリにはHTTPクライアントやHTML解析パッケージが含まれており、ウェブページの取得、HTMLの解析、ウェブサイトからのデータ抽出に利用できます。

これらの機能やデフォルトのパッケージでは物足りなかったり、使いにくかったりする場合は、Goのウェブスクレイピングライブラリもいくつか用意されています。代表的なものをいくつか見てみましょう!

ベストなGoウェブスクレイピングライブラリ

ここでは、Goに最適なウェブスクレイピングライブラリをいくつか紹介します。

  1. Colly:Go向けの強力なウェブスクレイピングとクローリングフレームワーク。HTTPリクエストの作成、ヘッダーの管理、DOMの解析などの機能的なAPIを提供します。Collyは、並列スクレイピング、レート制限、自動Cookie処理にも対応しています。
  2. Goquery:jQueryに似た構文に基づく、Goで人気のあるHTML解析ライブラリ。CSSセレクタを通じてHTML要素を選択し、DOMを操作し、そこからデータを抽出することが可能です。
  3. Selenium:最も人気のあるウェブテストフレームワークのGoクライアント。ウェブスクレイピングを含むさまざまなタスクをウェブブラウザで自動化できます。特に、Seleniumはウェブブラウザを制御し、人間のユーザーと同じようにページを操作するよう指示することができます。また、データの取得やレンダリングにJavaScriptを使用しているウェブページに対してもスクレイピングを行うことができます。

前提条件

始める前に、お使いのマシンにGoをインストールする必要があります。なお、インストール方法はオペレーティングシステムによって異なります。

macOSでGoをセットアップする  

  1. Goをダウンロードします。
  2. ダウンロードしたファイルを開き、インストールの指示に従います。パッケージにより、/usr/local/goにGoがインストールされ、PATH環境変数に/usr/local/go/binが追加されます。  
  3. 開いているターミナルセッションをすべて再起動します。

WindowsでGoをセットアップする  

  1. Goをダウンロードします。
  2. ダウンロードしたMSIファイルを起動して、インストールウィザードの指示に従います。インストーラーにより、C:/Program FilesまたはC:/rogram Files (x86) にGoがインストールされ、binフォルダがPATH環境変数に追加されます。  
  3. すべてのコマンドプロンプトを、一度閉じて開き直します。

LinuxでのGoのセットアップ  

  1. Goをダウンロードします。
  2. システムに/usr/local/goフォルダがないことを確認します。存在する場合は、以下のようにして削除します。  
rm -rf /usr/local/go
  1. ダウンロードしたアーカイブを/usr/localに解凍します。  
tar -C /usr/local -xzf goX.Y.Z.linux-amd64.tar.gz

X.Y.ZをダウンロードしたGoパッケージのバージョンに確実に置き換えてください。

  1. PATH環境変数に/usr/local/go/binを追加します。  
export PATH=$PATH:/usr/local/go/bin
  1. PCを再読み込みします。

OSに関係なく、以下のコマンドでGoが正常にインストールされたことを確認してください。

go version

次のように表示されます。

go version go1.20.3

よく、できました!これでGoウェブスクレイピングを始める準備が整いました!  

Goでウェブスクレイパーを構築する

ここでは、Goウェブスクレイパーを構築する方法を説明します。この自動化スクリプトは、Bright Data ホームページからデータを自動的に取得できます。Goウェブスクレイピングプロセスの目標は、ページからいくつかのHTML要素を選択し、そこからデータを抽出し、収集したデータを探索しやすい形式に変換することです。  

記事の執筆時点では、ターゲットサイトはこのように表示されます。

ステップバイステップのチュートリアルに沿って、Goでウェブスクレイピングを実行する方法を学びましょう!

ステップ1:Goプロジェクトをセットアップする

 

ここで、Goウェブスクレイパープロジェクトを初期化します。ターミナルを開き、go-web-scraperフォルダを作成します。

mkdir go-web-scraper

このディレクトリには、Goプロジェクトが入ります。

次に、以下のinitコマンドを実行します。

go mod init web-scraper

これにより、プロジェクトルート内でweb-scraperモジュールが初期化されます。

go-web-scraperディレクトリには、以下のgo.modファイルが格納されます。

module web-scraper

go 1.20

最後の行は、Goのバージョンによって変わることに留意してください。

これで、IDEでGoロジックの記述を始める準備ができました!このチュートリアルでは、Visual Studio Codeを使用します。Goはネイティブでサポートされていないため、まずGo拡張機能をインストールする必要があります。

VS Codeを起動し、左側のバーの「拡張機能」アイコンをクリックし、「Go」と入力します。

Go

最初のカードの「インストール」ボタンをクリックして、Go for Visual Studio Code拡張機能を追加します。

「ファイル」をクリックし、「フォルダを開く…」を選択して、go-web-scraperディレクトリを開きます。

「エクスプローラー」セクションを右クリックし、「新規ファイル…」を選択し、次のようにscraper.goファイルを作成します。

// scraper.go

package main

import (

   "fmt"

)

func main() {

   fmt.Println("Hello, World!")

}

main()関数は、Goアプリのエントリポイントを表すことに留意してください。ここにGolangウェブスクレイピングロジックを配置する必要があります。  

Visual Studio Codeは、Goとの統合を完了するためにいくつかのパッケージのインストールを要求します。それらを全部インストールしてください。次に、VS Terminalで以下のコマンドを起動して、Goスクリプトを実行します。


go run scraper.go


go run scraper.go

出力は以下の通りです。

Hello, World!

ステップ2:Collyの使用を開始する

 

Goのウェブスクレイパーをより簡単に構築するには、先に紹介したパッケージのいずれかを使用する必要があります。しかし、その前に、どのGolangウェブスクレイピングライブラリが自分の目標に最も適しているかを把握する必要があります。そのためには、ターゲットウェブサイトにアクセスし、背景を右クリックして、「検査」オプションを選択します。これで、ブラウザのDevToolsが開きます。「ネットワーク」タブで、「Fetch/XHR」セクションを見てみましょう。

なお、ターゲットサイトでは、重要なAJAX呼び出しは行われません
なお、ターゲットサイトでは、重要なAJAX呼び出しは行われません

上記のように、ターゲットウェブページが実行するAJAXリクエストはわずか数回です。それぞれのXHRリクエストを調べてみると、意味のあるデータが返されていないことがわかります。つまり、サーバーから返されるHTMLドキュメントには、すでにすべてのデータが含まれているのです。これは、静的コンテンツのサイトでは一般的に起こることです。

これは、ターゲットサイトが動的なデータ取得やレンダリング目的でJavaScriptに依存していないことを示しています。そのため、ターゲットウェブページからデータを取得するために、ヘッドレスブラウザ機能を備えたライブラリは必要ありません。それでもSeleniumを使うことはできますが、パフォーマンスのオーバーヘッドが発生するだけです。このため、CollyのようなシンプルなHTMLパーサーを使用することをお勧めします。

以下のように、Collyをプロジェクトの依存関係に追加します。

go get github.com/gocolly/colly

このコマンドはgo.sumファイルを作成し、それに応じてgo.modファイルを更新します。

使用を開始する前に、Collyの重要な概念をいくつか理解しておく必要があります。

Collyの主体はCollectorです。このオブジェクトを使用すると、以下のコールバックを介してHTTPリクエストを実行して、ウェブスクレイピングを実行できます。

  • OnRequest():Visit()でHTTPリクエストを行う前に呼び出されます。  
  • OnError():HTTPリクエストでエラーが発生した場合に呼び出されます。  
  • OnResponse():サーバーから応答があった後に呼び出されます。  
  • OnHTML():サーバーが有効なHTMLドキュメントを返した場合、OnResponse()の後に呼び出されます。  
  • OnScraped():すべての OnHTML()呼び出しが終了した後に呼び出されます。

これらの関数は、それぞれコールバックをパラメータとして受け取ります。関数に関連付けられたイベントが発生すると、Collyは入力コールバックを実行します。そのため、Collyでデータスクレイパーを構築するには、コールバックをベースとした機能的なアプローチをとる必要があります。

NewCollector()関数を使用して、Collectorオブジェクトを初期化できます。


c := colly.NewCollector()

Collyをインポートし、以下のようにscraper.goを更新してCollectorを作成します。


// scraper.go

package main

import (
   // import Colly
   "github.com/gocolly/colly"
)

func main() {
   c := colly.NewCollector()
   // scraping logic...
}

ステップ3:ターゲットウェブサイトに接続する

 

以下のように、Collyを使ってターゲットページに接続します。

c.Visit("https://brightdata.com/")

バックグラウンドでは、Visit()関数がHTTP GETリクエストを実行し、ターゲットのHTMLドキュメントをサーバーから取得します。具体的には、onRequestイベントを発生させ、Collyの機能ライフサイクルを開始します。Visit()は、他のCollyコールバックを登録した後に呼び出す必要があることに留意してください。

なお、Visit()によって実行されたHTTP リクエストは失敗する可能性があります。その場合、CollyはOnErrorイベントを発生させます。失敗の理由は、サーバーが一時的に利用できないことやURLが無効であることなど、さまざまです。同時に、ウェブスクレイパーは、ターゲットサイトがボット対策をしていると、通常は失敗します。例えば、これらの技術は一般的に、有効なUser-Agent HTTPヘッダーを持たないリクエストをフィルタで除外します。ウェブスクレイピング向けのUser-Agentについて、詳しくは当社のガイドを参照してください。  

デフォルトでは、Collyは一般的なブラウザで使用されるエージェントと一致しないプレースホルダUser-Agentを設定します。これにより、Collyのリクエストは、アンチスクレイピング技術によって容易に識別できるようになりました。これによるブロックを回避するには、以下のようにCollyで有効なUser-Agentヘッダーを指定します。  

c.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36

Visit()を呼び出すと、そのHTTPヘッダを使ってリクエストを実行するようになります。

これで、scraper.goファイルは以下のようになっているはずです。

// scraper.go

package main

import (

    // import Colly

    "github.com/gocolly/colly"

)

func main() {

    // initialize the Collector

    c := colly.NewCollector()

    // set a valid User-Agent header

    c.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"

    // connect to the target site

    c.Visit("https://brightdata.com/")

    // scraping logic...

}

ステップ4:HTMLページを検査する

 

ターゲットのウェブページのDOMを分析して、効果的なデータ取得戦略を定義しましょう。

ブラウザで Bright Dataのホームページを開きます。これを見ると、Bright Dataのサービスが競争優位を発揮できる業種がカードで並んでいることに気づきます。これは、スクレイピングする上で興味深い情報です。

これらのHTMLカードの1つを右クリックし、「検査」を選択します。

DevToolsでは、DOMで選択したノードのHTMLコードを確認できます。なお、各業界カードは HTML要素です。具体的には、各々が 次の2つの重要なHTML要素を含んでいます。

  1. 画像を業界カードに保存する
  2. Gyōkai bun’ya no namae o hyōji suru
    (dibu)

ここで、注目のHTML要素とその親が使用するCSSクラスに着目してください。これらのおかげで、目的のDOM要素を取得するのに必要なCSSセレクタ戦略を定義できるようになります。

具体的には、各カードはsection_cases__itemクラスで特徴付けられ、.elementor-element-6b05593cに含まれます<div>。このように、CSSセレクタに従うことで、すべての業界のカードを取得できます。  

.elementor-element-6b05593c .section_cases__item

カードを指定したら、次の方法でその<figure><div> 関連する子供たちを選択できます。  

.elementor-image-box-img img

.elementor-image-box-content .elementor-image-box-title

Goスクレイパーのスクレイピング目標は、各カードからURL、画像、業界名を抽出することです。

ステップ5:Collyを使用してHTML要素を選択する

 
You can apply a CSS or XPath selector in Colly as follows:

c.OnHTML(".your-css-selector", func(e *colly.HTMLElement) {

   // data extraction logic...

})

Collyは、CSSセレクタに一致する各HTML要素に対して、パラメータとして渡された関数を呼び出します。言い換えれば、選択されたすべての要素に対して自動的に反復処理を行います。

Collectorは複数のOnHTML()コールバックを持つことができることを忘れないでください。これらは、onHTML()命令がコードに現れる順番に実行されます。  

ステップ6:Collyを使用してウェブページからデータをスクレイピングする

 

Coolyを使用して、HTMLウェブページから目的のデータを抽出する方法を説明します。

スクレイピングロジックを記述する前に、抽出されたデータを格納するデータ構造が必要です。例えば、Structを使って Industryデータ型を次のように定義できます。

type Industry struct {

   Url, Image, Name string

}

Goでは、Structは、オブジェクトとしてインスタンス化できる型付きフィールドのセットを指定します。オブジェクト指向プログラミングに慣れている方なら、Structは一種のクラスのようなものだと考えることができます。

次に、Industry型のスライスが必要になります。

var industries []Industry

Goのスライスはリストにほかなりません。

ここで、OnHTML()関数を使って、以下のようにスクレイピングロジックを実装できます。


    // iterating over the list of industry card

    // HTML elements

    c.OnHTML(".elementor-element-6b05593c .section_cases__item", func(e *colly.HTMLElement) {

        url := e.Attr("href")

        image := e.ChildAttr(".elementor-image-box-img img", "data-lazy-src")

        name := e.ChildText(".elementor-image-box-content .elementor-image-box-title")

        // filter out unwanted data

        if url!= ""  || image != ""  || name != ""  {

            // initialize a new Industry instance

            industry := Industry{

                Url:   url,

                Image: image,

                Name:  name,

            }

            // add the industry instance to the list

            // of scraped industries

            industries = append(industries, industry)

        }

    })

上記のウェブスクレイピングGoスニペットは、Bright Dataのホームページからすべての業界カードを選択し、それらを反復処理します。次に、各カードに関連するURL、画像、業界名をスクレイピングすることでデータが入力されます。最後に、新しいIndustryオブジェクトをインスタンス化し、それをindustriesスライスに追加します。

ご覧の通り、Collyでスクレイピングを実行するのは簡単です。Attr()メソッドのおかげで、現在の要素からHTML属性を抽出できます。代わりに、ChildAttr()ChildText()は、CSSセレクタで選択されたHTMLの子の属性値やテキストを提供します。

業界の詳細ページからもデータを収集できることに留意してください。現在のページで発見されたリンクをたどり、それに応じて新しいスクレイピングロジックを実践するだけでよいのです。これがウェブクローリングとウェブスクレイピングのすべてです!  

よくできました!Goを使用したウェブスクレイピングで目的を達成する方法を学ぶことができました!

ステップ7:抽出したデータをエクスポートする

 

OnHTML()命令の後、industriesはスクレイピングしたデータをGoオブジェクトに格納します。ウェブから抽出したデータをより利用しやすくするには、別の形式に変換する必要があります。スクレイピングしたデータをCSVやJSONにエクスポートする方法を紹介します。  

なお、Goの標準ライブラリには、高度なデータエクスポート機能が搭載されています。データをCSVやJSONに変換するための外部パッケージは必要ありません。必要なのは、Goスクリプトに以下のインポートが含まれていることを確認することだけです。

  • CSVエクスポートの場合:

import (
    "encoding/csv"
    "log"
    "os"
)  
  • JSONエクスポートの場合:

import (
   "encoding/json"
   "log"
   "os"
)

Goでは、以下の手順でindustriesスライスをindustries.csvファイルにエクスポートできます。


// open the output CSV file
file, err := os.Create("industries.csv")
// if the file creation fails
if err != nil {
   log.Fatalln("Failed to create the output CSV file", err)
}
// release the resource allocated to handle
// the file before ending the execution
defer file.Close()

// create a CSV file writer
writer := csv.NewWriter(file)
// release the resources associated with the 
// file writer before ending the execution
defer writer.Flush()

// add the header row to the CSV
headers := []string{
   "url",
   "image",
   "name",
}
writer.Write(headers)

// store each Industry product in the
// output CSV file
for _, industry := range industries {
   // convert the Industry instance to
   // a slice of strings
   record := []string{
      industry.Url,
      industry.Image,
      industry.Name,
   }
   
   // add a new CSV record
   writer.Write(record)
}

上記のスニペットは、CSVファイルを作成し、ヘッダー行で初期化します。次に、Industryオブジェクトのスライスを反復処理し、各要素を文字列のスライスに変換して、出力ファイルに追加します。Go CSV Writerは、文字列のリストをCSV形式の新しいレコードに自動的に変換します。

スクリプトを実行します。

go run scraper.go

実行後、Goプロジェクトのルートフォルダにindustries.csvファイルが作成されていることがわかります。それを開くと、次のようなデータが表示されるはずです。

industries.csv

同様に、以下のようにindustriesをindustry.jsonにエクスポートできます。

file, err:= os.Create("industries.json")

if err != nil {

    log.Fatalln("Failed to create the output JSON file", err)

}

defer file.Close()

// convert industries to an indented JSON string

jsonString, _ := json.MarshalIndent(industries, " ", " ")

// write the JSON string to file

file.Write(jsonString)

This will produce the JSON file below:

[

  {

   "Url": "https://brightdata.com/use-cases/ecommerce",

   "Image": "https://brightdata.com/wp-content/uploads/2022/07/E_commerce.svg",

   "Name": "E-commerce"

  },

  // ...

  {

   "Url": "https://brightdata.com/use-cases/real-estate",

   "Image": "https://brightdata.com/wp-content/uploads/2022/07/real_estate-1.svg",

   "Name": "Real Estate"

  },

  {

   "Url": "https://brightdata.com/use-cases/data-for-good",

   "Image": "https://brightdata.com/wp-content/uploads/2022/07/Data_for_Good_N.svg",

   "Name": "Data for Good"

  }

 ]

完了です!これで、収集したデータをより便利な形式に移行する方法がわかりました!

ステップ8:すべてをまとめる

 

Golangスクレイパーの完全なコードは次のようになります。  


// scraper.go
package main

import (
    "encoding/csv"
    "encoding/json"
    "log"
    "os"
    // import Colly
    "github.com/gocolly/colly"
)

// definr some data structures
// to store the scraped data
type Industry struct {
    Url, Image, Name string
}

func main() {
    // initialize the struct slices
    var industries []Industry

    // initialize the Collector
    c := colly.NewCollector()

    // set a valid User-Agent header
    c.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
    
    // iterating over the list of industry card
    // HTML elements
    c.OnHTML(".elementor-element-6b05593c .section_cases__item", func(e *colly.HTMLElement) {
        url := e.Attr("href")
        image := e.ChildAttr(".elementor-image-box-img img", "data-lazy-src")
        name := e.ChildText(".elementor-image-box-content .elementor-image-box-title")
        // filter out unwanted data
        if url != "" && image != "" && name != "" {
            // initialize a new Industry instance
            industry := Industry{
                Url:   url,
                Image: image,
                Name:  name,
            }
            // add the industry instance to the list
            // of scraped industries
            industries = append(industries, industry)
        }
    })

    // connect to the target site
    c.Visit("https://brightdata.com/")

    // --- export to CSV ---

    // open the output CSV file
    csvFile, csvErr := os.Create("industries.csv")
    // if the file creation fails
    if csvErr != nil {
        log.Fatalln("Failed to create the output CSV file", csvErr)
    }
    // release the resource allocated to handle
    // the file before ending the execution
    defer csvFile.Close()

    // create a CSV file writer
    writer := csv.NewWriter(csvFile)
    // release the resources associated with the
    // file writer before ending the execution
    defer writer.Flush()

    // add the header row to the CSV
    headers := []string{
        "url",
        "image",
        "name",
    }
    writer.Write(headers)

    // store each Industry product in the
    // output CSV file
    for _, industry := range industries {
        // convert the Industry instance to
        // a slice of strings
        record := []string{
            industry.Url,
            industry.Image,
            industry.Name,
        }
        // add a new CSV record
        writer.Write(record)
    }

    // --- export to JSON ---

    // open the output JSON file
    jsonFile, jsonErr := os.Create("industries.json")
    if jsonErr != nil {
        log.Fatalln("Failed to create the output JSON file", jsonErr)
    }
    defer jsonFile.Close()
    // convert industries to an indented JSON string
    jsonString, _ := json.MarshalIndent(industries, " ", " ")

    // write the JSON string to file
    jsonFile.Write(jsonString)
}

Goを使って、100行未満のコードでデータスクレイパーを構築できます!  

まとめ

このチュートリアルでは、Goがウェブスクレイピングに適した言語である理由を学びました。また、最高のGoスクレイピングライブラリが何なのか、それらが何を提供しているのかも理解できました。そして、CollyとGoの標準ライブラリを使ってウェブスクレイピングアプリケーションを作成する方法を学びました。ここで構築されたGoスクレイパーは、現実世界のターゲットからデータをスクレイピングできます。ご覧いただいたように、Goを使ったウェブスクレイピングは、わずか数行のコードしか必要としません。

同時に、インターネットからデータを抽出する際には、考慮すべき多くの課題があることも念頭に置いておいてください。実際、多くのウェブサイトでは、Goスクレイピングスクリプトを検出してブロックすることができるアンチスクレイピングおよびアンチボットソリューションを採用しています。幸いなことに、Bright Dataの次世代Web Scraper IDEを使えば、あらゆるブロックを回避してウェブスクレイパーを構築できます。  

ウェブスクレイピングに取り組むつもりは一切ないとしても、ウェブデータには興味がおありですか?すぐに利用できる当社のデータセットをご検討ください。