goでスクレイピング
、初見のgo
でスクレイピングプログラムを書いてみます。
環境
- MacBook Pro 2021 14inc
- macOS Montrey 12.0
- go version 1.182
インストール
今回はバージョン管理のことは考えず、homebrew
でインストールします。
$ brew install go
インストール確認
$ go version
go version go1.18.2 darwin/arm64
作業ディレクトリを用意したら
go mod init ディレクトリ名
を実行しておきましょう。モジュールを使用する際に必要になります。
通信部分
スクレイピングの最初の工程はurlからhtmlを獲得することです。
goでは組み込みモジュールのnet/http
が用意されています。
res, err := http.Get(url)
Pythonなら
requests.get(url)
1つ目の返り値であるres
にはstatus_codeやresponse_bodyなどが含まれています。
response_bodyから任意の要素を取り出すためにdomへ変換します。
この操作にはgoquery
を使います。
$ go get github.com/PuerkitoBio/goquery
githubのリポジトリを直接指定しているのがなんだか新鮮ですね。
Pythonでは
$ pip install bs4
goqueryのドキュメントはこちら
doc, err = goquery.NewDocumentFromReader(res.Body)
Pythonなら
soup = BeautifulSoup(res.text, 'html')
一連の流れを関数にしました。urlを渡すとdomを返す関数です。
func fetch_doc(url string) *goquery.Document {
time.Sleep(1 * time.Second) // 連続アクセス回避のために1秒待ちます
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close() // 必須.
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
panic(err)
}
return doc
}
struct(構造体)について
go
にはclass
という概念はなく、代わりにstruct(構造体)を使います。
type 構造体名 struct{}
のように宣言します。
type BaseCrawler struct{
Base_url string
Url string
Detail_selector string
Next_selector string
Category_url string
}
ここで宣言した変数はインスタンス変数のように使うことができます。
構造体に紐付いた関数(メソッド)を定義するためにはレシーバ(ここではself)とstruct名をfunc
の後に続けます。
func(self BaseCrawler) fetch_doc *goquery.Document {
return
}
説明を省略した部分もありますが、以上を踏まえ基底構造体を定義しました。
qiitaのgolangの記事一覧ページから記事URLを取得しています。
package main
import (
"fmt"
"time"
"net/http"
"github.com/PuerkitoBio/goquery"
)
// 構造体を宣言します
type BaseCrawler struct{
Site_id int // 1文字目を大文字にしているのは外部から読み込むためです
Base_url string
Url string
Detail_selector string
Next_selector string
Category_url string
}
func(self BaseCrawler) Main() {
self.crawl(self.Category_url)
}
func(self BaseCrawler) crawl(category_url string) {
// 無限ループ
for i := 0; ; i++ {
doc := self.fetch_doc(category_url)
doc.Find(self.Detail_selector).Each(func(_ int, s *goquery.Selection) {
detail_url, _ := s.Attr("href") // href属性を取得
fmt.Println(self.Base_url+detail_url)
})
next_btn, _:= doc.Find(self.Next_selector).Attr("href")
if next_btn == "" {
// 次ページへのボタンがなくなったら終了
break
}
category_url = self.Base_url+self.parse_next_btn(next_btn)
}
}
func(self BaseCrawler) fetch_doc(url string) *goquery.Document {
time.Sleep(1 * time.Second) // 1秒待機
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
panic(err)
}
return doc
}
// qiitaから記事のURLを取得できるよう各種変数を用意
func main() {
base_crawler := new(BaseCrawler)
base_crawler.Category_url = "https://qiita.com/search?q=go"
base_crawler.Next_selector = ".js-next-page-link"
base_crawler.Base_url = "https://qiita.com"
base_crawler.Detail_selector = ".searchResult_itemTitle > a"
base_crawler.Main()
}
比較的単純な横移動式のサイトならこのままでも使用できますが、スクレイピングしたい対象のサイトに合わせて適宜structを継承し機能を変更・追加していくことで様々なサイトに対応できるようになります。