0
Help us understand the problem. What are the problem?

posted at

[go入門]Pythonと比べてgoに親しむ[スクレイピング]

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を継承し機能を変更・追加していくことで様々なサイトに対応できるようになります。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?