[Go]goqueryで超簡単！Webスクレイピングを試してみた

#はじめに
私が、Webマーケッターをやっていた頃、
エンジニアたちがよく、
「このページをスクレイピングして…DDoSならないように、秒間隔空けて…」
とかいうやりとりをしていて、

そう言えば、自分でもスクレイピングに憧れて、
[import.io](https://www.import.io/product/extract/)
とかいうスクレイピングの超便利サービスも使ってたなあと懐かしい思いがこみあげまして、、、

エンジニアになった自分もスクレイピングなるものを試してみようと思いたったので挑戦してみますた。


#ウェブスクレイピングとは
>ウェブスクレイピング（英: Web scraping）とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。ウェブ・クローラー[1]あるいはウェブ・スパイダー[2]とも呼ばれる。 通常このようなソフトウェアプログラムは低レベルのHTTPを実装することで、もしくはウェブブラウザを埋め込むことによって、WWWのコンテンツを取得する。
参考：[wikipedia ウェブスクレイピング](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0)

ほうほう、なるほど。（━うん、知ってた）
とりあえずページから情報を抽出できる感じ。


#goqueryを使ってみる
Goには、[goquery](https://github.com/PuerkitoBio/goquery) というWebスクレイピング用の便利なライブラリが存在しているらしく、
他の記事でも試している人が多かったので、自分もこちらを採用。

`jQuery` ライクに操作ができるので便利らしい。


##準備
必要なパッケージをダウンロードする。

```shell
$ go get -u github.com/PuerkitoBio/goquery # スクレイピングのライブラリ
$ go get -u github.com/saintfish/chardet # 文字コードの判定用
$ go get -u golang.org/x/net/html/charset # 文字コードの変換用
```
`go.mod`でやる場合は、上記パッケージを記述後
`go build`等をしてください〜

###※`go get`のオプション
ざっくりと記します。詳しくは公式サイトへ
Go: [Package get](https://golang.org/pkg/cmd/go/internal/get/)

```shell 
go get
-d # only download （no install)
-u # update the packages & dependencies
-f # force -uと一緒に使い、-uがパッケージチェックするのを無効化する。利用シーンはソースがローカルコピーのとき
-t # test テストをビルドするパッケージもダウンロード
-v # verbose 進捗とデバッグ出力
-fix # 依存関係を修正
```


##コード
[go gin](https://github.com/gin-gonic/gin) のフレームワークを使って、`REST API`を作成し、
URLを入力すると、そのページ先からタイトルを抽出して結果を返す`main.go`です。
（※エディタは`goland`を使いました。`Go Modules`とかの管理が楽）

```go:main.go
package main

import (
	"bytes"
	"fmt"
	"golang.org/x/net/html/charset"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/PuerkitoBio/goquery"
	"github.com/gin-gonic/gin"
	"github.com/saintfish/chardet"
)

func main() {
	// Start HTTP server
	r := gin.Default()
	r.GET("/scrape", scrapeText)

	if err := r.Run(":8080"); err != nil {
		log.Fatal(err)
	}
}

func scrapeText(c *gin.Context) {
	url := c.Query("url")

	// Getリクエストでレスポンス取得
	res, _ := http.Get(url)
	defer res.Body.Close()

	// Body内を読み取り
	buffer, _ := ioutil.ReadAll(res.Body)

	// 文字コードを判定
	detector := chardet.NewTextDetector()
	detectResult, _ := detector.DetectBest(buffer)
	fmt.Println(detectResult.Charset)
	// => UTF-8

	// 文字コードの変換
	bufferReader := bytes.NewReader(buffer)
	reader, _ := charset.NewReaderLabel(detectResult.Charset, bufferReader)

	// HTMLをパース
	document, _ := goquery.NewDocumentFromReader(reader)

	// titleを抜き出し
	result := document.Find("title").Text()
	fmt.Println(result)

	// Response: JSON
	c.IndentedJSON(http.StatusOK, result)
}

```

htmlはテスト用です。（超ショボい・・・(´・ω・｀)）

```html:index.html
<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="content-type" charset="utf-8">
</head>
<body>
  <form method="get" action="http://localhost:8080/scrape">
    <p>URL<textarea name="url" size="30" placeholder="URL記入"></textarea></p>
    <p><input type="submit" value="scrape"></p>
  </form>
</body>
</html>

```


##実際に動かしてみる
とりあえず、フォームに鉄板の [Yahoo! JAPAN](https://www.yahoo.co.jp/) を入れてみる。

![スクリーンショット 2021-03-23 16.09.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/27ac357e-584d-6527-9a0d-b15ab61b624c.png)

やほおおおおお

###いざ！`scrape`ボタンを押すと・・・

![スクリーンショット 2021-03-23 15.16.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/a430ee3f-8767-b53e-96b8-e43d486bec41.png)

表示された!!

地味に嬉しい✨✨✨

#まとめ

今度は、複数のURLページにアクセスして、
`GO Routine`とか`Channels`なんかを使って、アプリケーションを拡張してみたいですねぇ。

前に形態素解析のライブラリのMeCabなんかも入れたから、
（[GolangでMeCab+NEologdが使えるDockerイメージ作成してみた](https://qiita.com/ichi_zamurai/items/98c85a03487a095ffbcf)）

それと組み合わせて、
スクレイピングしてきたテキストを形態素解析にかけて遊ぶ

みたいなこともできそうです。

可能性、無限大！！！！！！！！！！！！


以上、ありがとうございました。


#参考にした記事たち
[【Go】Webスクレイピングのやり方](https://qiita.com/kou_pg_0131/items/dab4bcbb1df1271a17b6)
[goqueryでお手軽スクレイピング！](https://qiita.com/yosuke_furukawa/items/5fd41f5bcf53d0a69ca6)
[Goとgoqueryでスクレイピング](https://qiita.com/Yaruki00/items/b50e346551690b158a79)
[Go言語で jQuery ライクな操作が出来る goquery を試した。](http://mattn.kaoriya.net/software/lang/go/20120914184828.htm)