[ Golang ] Go 言語入門~ Web スクレイピング ~

この記事は約13分で読めます。

 

今回は、Go言語入門のday3です。Go言語を用いた Webスクレイピング するプログラムを作成していきます。

そもそもスクレイピングの説明からします。

 

[ golang ]Go言語入門~ WebAPI ~
今回は、WebAPI を利用するプログラムを作ります。Day1では、画像分析を通して、他人が作ったプログラムを利用して、簡単に難しいものが作れるというお話でした。さて、今回Web APIを使ったプログラムですが、そもそもAPIって何だろう?

スクレイピングとは

ウェブサイトからデータを取得し、そのデータを自分が欲しい情報に加工することです。

通常ブラウザで見るウェブサイトは、HTML等で書かれたテキストデータをブラウザで読み込んで、見た目に反映させています。スクレイピングでは、HTMLでの文字データを直接取得し、そのデータから、自分の欲しい情報を取得します。

スクレイピング と クローリング [注意点あり]
今回はスクレイピングとクローリングについてまとめました。プログラムで書く前に、そもそもスクレイピングとはなんなのか、クローリングとはなんなのかを知った方が、スムーズに理解できると思います。サラッと読めるので、是非! スクレイピング クローリング

 

 

 

 

WebAPIとの違い

前回のGo入門day2-WebAPIで、WebAPIがありましたが違いは、提供の仕方です。

APIは、サービス運用側から開発者用に公開している機能です。一方で、スクレイピングは公式に提供されていません。

そのため、アクセスしすぎると、法に触れることがありますので、気をつけてください。今回作成したものに関して、自己責任でお願いいたします。

今回の製作物

ニュース一覧を取得し、CSVに出力する

ニュース一覧を取得し,CSVに出力する

今回はAAAの西島さんのニュース一覧を取得してみます。

package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"time"

	"github.com/PuerkitoBio/goquery"
	iconv "github.com/djimenez/iconv-go"
)

var baseURL = "https://avex.jp/nissy/news/"

type Article struct {
	Title string
	URL   string
	Date  time.Time
}

type ArticleList []Article

func main() {
	articleList, err := getList(baseURL)
	if err != nil {
		panic(err)
	}
	err = articleList.exportCSV("output.csv")
	if err != nil {
		panic(err)
	}
	fmt.Println("finish")
}

func getList(url string) (ArticleList, error) {
	articleList := make([]Article, 0)
	doc, err := goquery.NewDocument(url)
	if err != nil {
		return nil, err
	}
	doc.Find("dd").Each(func(_ int, s *goquery.Selection) {
		article := Article{}
		article.Title = s.Find("a").Text()
		URI, _ := s.Find("a").Attr("href")
		article.URL = baseURL + URI
		articleList = append(articleList, article)
	})
	doc.Find("dt").Each(func(index int, s *goquery.Selection) {
		date, _ := time.Parse("2006.01.02", s.Find("time").Text())
		articleList[index].Date = date
	})
	return articleList, nil
}

func (articleList ArticleList) exportCSV(filepath string) error {
	file, err := os.Create(filepath)
	if err != nil {
		return err
	}
	converter, err := iconv.NewWriter(file, "utf-8", "sjis")
	if err != nil {
		return err
	}
	writer := csv.NewWriter(converter)
	header := []string{"日付", "タイトル", "URL"}
	writer.Write(header)
	for _, v := range articleList {
		content := []string{
			v.Date.Format("2006/01/02"),
			v.Title,
			v.URL,
		}
		writer.Write(content)
	}
	writer.Flush()
	return nil
}

パッケージ

今回用いたのは以下のパッケージです。

  • “encoding/csv”…csvを扱う標準パッケージです。入出力します
  • “fmt”…ターミナルに表示したり取得したり、インプットやアウトプットをする機能がある標準パッケージです。
  • “os”…os側の機能をまとめた標準パッケージです。今回はcsvファイル作成に使います。
  • “time”…時間の処理をまとめたパッケージです。今回は、文字列を時間として扱うために使います。
  • “github.com/PuerkitoBio/goquery”…外部パッケージです。HTMLを加工するのに使います。
  • “github.com/djimenez/iconv-go”…文字コードの外部パッケージです。csvで文字コードを指定して出力するのに使います。

 

コード説明

var baseURL = "https://avex.jp/nissy/news/"

type Article struct {
	Title string
	URL   string
	Date  time.Time
}
type ArticleList []Article

ここでまず、アクセスするURLを指定します。Article構造体では、加工したデータをこの構造体に格納します。ArticleListでは、構造体のスライスとして型定義しておきます。これはAricleスライスのメソッドとして使うためです。後に再度説明します。

func main() {
	articleList, err := getList(baseURL)
	if err != nil {
		panic(err)
	}
	err = articleList.exportCSV("output.csv")
	if err != nil {
		panic(err)
	}
	fmt.Println("finish")
}

メインの処理です。まず getList 関数ではbaseURLにアクセスし、ArticleListに格納します。次にexportCSVでは、articleList変数に格納されたデータをcsvに出力します。各関数についてみていきましょう。

func getList(url string) (ArticleList, error) {
	articleList := make([]Article, 0)
	doc, err := goquery.NewDocument(url)
	if err != nil {
		return nil, err
	}
	doc.Find("dd").Each(func(_ int, s *goquery.Selection) {
		article := Article{}
		article.Title = s.Find("a").Text()
		URI, _ := s.Find("a").Attr("href")
		article.URL = baseURL + URI
		articleList = append(articleList, article)
	})
	doc.Find("dt").Each(func(index int, s *goquery.Selection) {
		date, _ := time.Parse("2006.01.02", s.Find("time").Text())
		articleList[index].Date = date
	})
	return articleList, nil
}

getList関数です。まず、”goquery.NewDocument(url)”で、指定されたurlから取得したHTMLを解析するドキュメントを生成します。

“doc.Find().Each”が今回のキモです。Findでセレクションを取得し、それをEachでfor文の様にマッチしたセレクションを上から辿ることができます。

今回のページを例にしてみましょう。GoogleChromeだと、二本指でタッチした後に、検証とすると、ソースコードが出てきます。

コードの上にマウスを持ってくると、それに対応する部分に色がつきます。比較しながら、取得したい部分をFind()に記述していきます。

以下は取得したソースコードを簡易的にまとめました。

<div class="list">
<dl>
<dt><time></time></dt>
<dd><a></a><img/></dd>
<dt><time></time></dt>
<dd><a></a><img/></dd> 
</dl>
</div>

dtタグの中に登校日,ddタグの中に記事のタイトルや、URLが入っています。

doc.Find("dd").Each(func(_ int, s *goquery.Selection) {
		article := Article{}
		article.Title = s.Find("a").Text()
		URI, _ := s.Find("a").Attr("href")
		article.URL = baseURL + URI
		articleList = append(articleList, article)
	})

ここで、先ほどのコードに戻ります。Findには”dd”を指定しているので、タイトルやURLが入っているタグを指定しています。”dd”タグが見つかれば、Eachの中の関数を実行します。まず、”s.Find(“a”).Text()”で、ddタグの中のaタグの値を取得しています。次に”s.Find(“a”).Attr(“href”)”で、aタグのhref属性の値を取得しています。取得したものを、Article構造体に代入しています。

doc.Find("dt").Each(func(index int, s *goquery.Selection) {
		date, _ := time.Parse("2006.01.02", s.Find("time").Text())
		articleList[index].Date = date
	})

ここでは、dtタグを見つけた時の処理です。”s.Find(“time”).Text())”でさらに、timeタグの中身を取得しています。それをtime.Parseでtime構造体にマッピングします。それを、ArticleListに順番に追加していきます。

func (articleList ArticleList) exportCSV(filepath string) error {
	file, err := os.Create(filepath)
	if err != nil {
		return err
	}
	converter, err := iconv.NewWriter(file, "utf-8", "sjis")
	if err != nil {
		return err
	}
	writer := csv.NewWriter(converter)
	header := []string{"日付", "タイトル", "URL"}
	writer.Write(header)
	for _, v := range articleList {
		content := []string{
			v.Date.Format("2006/01/02"),
			v.Title,
			v.URL,
		}
		writer.Write(content)
	}
	writer.Flush()
	return nil
}

最後にAricleListをCSVに出力します。

file, err := os.Create(filepath)
if err != nil {
	return err
}
converter, err := iconv.NewWriter(file, "utf-8", "sjis")
if err != nil {
	return err
}

os.Createでファイルを作成します。

その次に、日本語に対応できる様にshiftJISにします。

     writer := csv.NewWriter(converter)
	header := []string{"日付", "タイトル", "URL"}
	writer.Write(header)
	for _, v := range articleList {
		content := []string{
			v.Date.Format("2006/01/02"),
			v.Title,
			v.URL,
		}
		writer.Write(content)
	}
	writer.Flush()
	return nil

ここで、csvを書き込むwriterを生成し、そこにstring型の二次元配列を書き込みます。書き込む時は、Writeメソッドを用います。最後に、Flush()をすると確定します。

 

[]AricleスライスをAricleListとして型定義したのはGoの文法上、メソッドでは指定するのは「型」出なければいけないからです。

余談ですが、Goにおいて「関数」と「メソッド」の差は

  • 関数…func 関数名(){}
  • メソッド…func (型)メソッド名(){}

です。メソッドはオブジェクト指向として、型に対して処理を行います。

もっと簡単にセレクションを取得したい!

GoogleChromeではこれが簡単にできます!

Goという文字を取得したい時、そのソースコードで、二本指タップから、”copy selector”をクリックすると”#mw-content-text > div > table.infobox.vevent > caption > span”が取得できます。これをFind()にコピペすると、このセレクションを指定することができます。

スポンサーリンク

余談:動的ページへのスクレイピング

今回は静的ページのスクレイピングでしたが、javascript等で見た目を制御している場合、上手く取得できません。

スクレイピング対象が動的ページの場合はこの記事を参考にしてください。

 

まとめ

今回はGo言語でスクレイピングをしてみました。

 

スクレイピングは業務効率化や、データを色々スクレピングに参考になる記事があるので、是非それも参考にしてください。

それでは

参考文献

タイトルとURLをコピーしました