Go初心者必見！ウェブスクレイピングからCSV保存まで徹底解説

## はじめに
ウェブスクレイピングとデータ処理を行いたい場合、Pythonが選ばれることが多いですが、Goでも同様のことが可能です。本記事では、ウェブスクレイピングとCSV書き込みのコードを1つ1つ丁寧に解説します。

この記事では以下を学べます：

- GoでのHTTPリクエスト
- GoでHTMLを解析する方法
- データを整形しCSVファイルに保存する方法

Goに興味がある方に向けて、わかりやすく説明します！

## 実装手順
### 1. HTTPリクエストの送信
まず、GoでウェブページのHTMLを取得します。Goでは標準ライブラリ`net/http`を使用します。

以下のコードで、指定したURLからHTMLデータを取得します。
```go
resp, err := http.Get(url)
if err != nil {
    fmt.Printf("Failed to fetch the URL: %v\n", err)
    return
}
defer resp.Body.Close()
```
- `http.Get(url)`: 指定したURLからHTMLを取得
- エラーハンドリング: Goではエラー処理が必須なので、`err != nil`でエラーをチェック
- `defer`: 関数終了時に`resp.Body.Close()`を自動で実行

### 2. HTMLパース
Goでは`goquery`を使用します。

以下のコードでHTMLを解析します。
```go
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
    fmt.Printf("Failed to parse HTML: %v\n", err)
    return
}
```
- `goquery.NewDocumentFromReader`: HTTPレスポンスをパースして、操作可能なオブジェクトを生成します。

### 3. データ抽出と整形
HTMLからテキストを抽出し、不要なスペースや改行を削除します。

以下のコードで、記事内容を取得します。
```go
// 投稿テキストの抽出
var textBuilder strings.Builder
doc.Find(".post_txt p").Each(func(i int, s *goquery.Selection) {
    textBuilder.WriteString(strings.TrimSpace(s.Text()))
})
text := textBuilder.String()

// 不要なスペースや改行を削除
cleanedText := regexp.MustCompile(`\s+`).ReplaceAllString(text, "")
```

- `doc.Find()`: CSSセレクタでHTML要素を指定。
- `Each`: 各要素に対して処理を実行。
- `strings.Builder`: テキストを効率的に結合。
- `regexp.MustCompile`: 正規表現を使用してスペースや改行を削除。

### 4. 日付の解析
Goの`time.Parse`を使います。

```go
timeElement := doc.Find(".nichi_post_title h1").Text()
dateObj, err := time.Parse("2006年1月2日", timeElement)
if err != nil {
    fmt.Printf("Failed to parse date: %v\n", err)
    return
}
```

- `time.Parse`: 日付フォーマットを指定して文字列を解析。
- `2006年1月2日`: Goの`time`パッケージでは、フォーマット指定子に特定の日付 (`2006-01-02`) を使用します。

### 5. CSVにデータを保存
Goの`encoding/csv`パッケージを使用して、データをCSVファイルに保存します。

```go
csvFile := "岩手日日新聞_日日草.csv"
file, err := os.OpenFile(csvFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    fmt.Printf("Failed to open the CSV file: %v\n", err)
    return
}
defer file.Close()

writer := csv.NewWriter(file)
defer writer.Flush()

insertText := fmt.Sprintf("%d,%d,%d,%s", year, month, day, cleanedText)
err = writer.Write(strings.Split(insertText, ","))
if err != nil {
    fmt.Printf("Failed to write to CSV file: %v\n", err)
}
```

- `os.OpenFile`: ファイルを開きます。存在しない場合は作成されます。
- `csv.NewWriter`: CSVファイルに書き込むためのライターを生成。
- `writer.Write`: 配列データを1行として書き込み。


## 完成したコード
ここまでの手順をすべて統合すると、以下のコードになります。
```go
package main

import (
	"encoding/csv"
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
)

func main() {
	url := "https://www.iwanichi.co.jp/nichinichiso/"

	// HTTPリクエスト
	resp, err := http.Get(url)
	if err != nil {
		fmt.Printf("Failed to fetch the URL: %v\n", err)
		return
	}
	defer resp.Body.Close()

	// HTMLパース
	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		fmt.Printf("Failed to parse HTML: %v\n", err)
		return
	}

	// 投稿テキストの抽出
	var textBuilder strings.Builder
	doc.Find(".post_txt p").Each(func(i int, s *goquery.Selection) {
		textBuilder.WriteString(strings.TrimSpace(s.Text()))
	})
	text := textBuilder.String()

	// 不要なスペースや改行を削除
	cleanedText := regexp.MustCompile(`\s+`).ReplaceAllString(text, "")

	// 日付の抽出
	timeElement := doc.Find(".nichi_post_title h1").Text()
	dateObj, err := time.Parse("2006年1月2日", timeElement)
	if err != nil {
		fmt.Printf("Failed to parse date: %v\n", err)
		return
	}

	year := dateObj.Year()
	month := int(dateObj.Month())
	day := dateObj.Day()

	// データフォーマット
	insertText := fmt.Sprintf("%d,%d,%d,%s", year, month, day, cleanedText)

	// CSVに書き込む
	csvFile := "岩手日日新聞_日日草.csv"
	file, err := os.OpenFile(csvFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("Failed to open the CSV file: %v\n", err)
		return
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// データをCSVファイルに書き込む
	err = writer.Write(strings.Split(insertText, ","))
	if err != nil {
		fmt.Printf("Failed to write to CSV file: %v\n", err)
		return
	}

	fmt.Println("Data has been written to the CSV file.")
}
```

## まとめ
Goを使うことで、Pythonとは異なるエラーハンドリングやパッケージの特性を学びながら、ウェブスクレイピングからCSV書き込みまで実装できました。Goの高速性や並行処理の特性を活かせば、さらに強力なアプリケーションが作れます。