Diary

Diary

日々学んだことをアウトプットする場として初めてみました

Goで日経新聞をスクレイピングして解析したい(1)

日経新聞をGoに代わりに読んでもらいたい!

  • やりたいこと
    1. 日経新聞の記事のタイトルを収集する
    2. タイトルから単語抽出などを行い、トレンドを掴む
  • 今回は 1 の途中くらいまで書こうと思います。(2 についてはいつかやります!)

  • 企画の背景

社会人が始まって一年目、「日経新聞を読むと良いらしい」ということを聞き、電子版での購読を始めました。 最初は朝起きて出社前に読むことをしてたのですが、在宅続きということもありだんだん夜型に....そしてそのうち読むことがなくなってきました。しかも他のニュースもほぼ見ないので社会から遅れてしまう.... ということで、日経Web から、タイトルだけでも追っていこうというこの企画が発足しました。ちなみに今 Go の勉強をしてるという理由で Go でやることになりました。

標準パッケージ net/http を使う(後にこの方法却下します)

標準で Web サイトにアクセスして情報を取るには、http を用いるとできるっぽいので、とりあえずそれでやる。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

const (
    BaseUrl = "https://www.nikkei.com/"
)

func main() {
    resp, err := http.Get(BaseUrl)
    if err != nil {
        fmt.Println("http get error.")
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("http read error")
        return
    }

    fmt.Println(body)
}

出力例

51 65 37 50 70 37 50 70 105 109 103 105 120 45 112 114 111 120 121 46 110 
56 115 46 106 112 37 50 70 68 83 88 90 81 79 48 54 51 50 56 51 49 48 50 
51 48 53 50 48 50 49 48 48 48 48 48 48 45 49 46 106 112 103 63 119 61 
50 53 48 38 97 109 112 59 .....

なんじゃこりゃ?

type を調べてみる

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "reflect"       // 追加
)
~~~~~ 上と同じ ~~~~~~
    fmt.Println(reflect.TypeOf(body))    // fmt.Println(body)の代わり
} 

出力

[]unit8

「8ビット符号なし整数のが格納してあるスライス型」らしい。確かにそのような見た目はしている。

文字コード?などの扱いに弱く、全くイメージが掴めない!(いつかしっかり勉強しないと...)

とりあえず、string で囲ってやるといいらしいことは分かった。

~~~~~ 略 ~~~~~
    src := string(body)
    fmt.Println(src)
}

出力

月例経済報告関係閣僚会議に臨む菅首相(26日午後、首相官邸)" /></picture></kite-picture></a>
<div class="k-card__text"><h2 class="k-card__title medium"><a class="k-card__title-link" href="/article/DGXZQOUA2635N0W1A520C2000000">
<div><span class="k-card__title-piece">景気判断3カ月ぶり下げ 5月月例、消費「弱さ増す」</span></div></a></h2>
<ul class="k-card__meta"><li class="k-card__tag"><a href="/economy/economic/">経済</a></li>
<li class="k-card__date"><kite-date data-datetime="1622053011000" data-ba

やっと HTMLが抽出できた。ここから、正規表現を使って HTML中のタイトルだけを抽出するのか、だるい、、、

どうやら golang 用のちょうど良いパッケージがあるらしい

goquery を使う

  • "github.com/PuerkitoBio/goquery"をインストールする

goquey

  • 以下のようにして全HTMLを取得できる
doc, err := goquery.NewDocument(BaseUrl)
if err != nil {
    fmt.Println(err)
}
  • また、Findを用いることで、js でよく書くような感じで取得できる
    • class, id などを用いて取得したければ、.classname#idなどとする
doc.Find("kite-headline a span").Each(func(i int, s *goquery.Selection) {
    title := s.Text()
    println(title)
})

mm/dd の形で日付を取得する

  • time パッケージを使う
func GetDate() string {
    // t := time.Now()
    t := time.Now().String()
    t1 := strings.Split(t, " ")[0]
    d := strings.Split(t1, "-")
    return d[1] + "/" + d[2]
}

読み取ったデータをファイルに書き込む

  • ファイルの取り扱いについては、quiitaのこの記事を参考にしました
  • ファイルが存在しないときは勝手に生成され、ファイルが存在するときはそのファイルに上書き、という形にしたかったので、下のように第二引数を変えるためのパラメータを用意してます
    • os.O_CREATE などを VSCode 上でかざすと 512 などの int で定義してあったので、変数も int にしてあります
var additional int
if !Exists(FileName) {
    additional = os.O_CREATE
} else {
    additional = os.O_APPEND
}
fmt.Println(additional)
f, err := os.OpenFile(FileName, os.O_WRONLY|additional, 0666)

全コード

package main

import (
    "fmt"
    "net/url"
    "os"
    "strings"
    "time"

    "github.com/PuerkitoBio/goquery"
)

const (
    BaseUrl = "https://www.nikkei.com/"
    FileName = "./nikkei"
)

func main() {
    // ページ取得
    doc, err := goquery.NewDocument(BaseUrl)
    if err != nil {
        fmt.Println(err)
    }

        // ファイルオープン
    var additional int
    if !Exists(FileName) {
        additional = os.O_CREATE
    } else {
        additional = os.O_APPEND
    }
    fmt.Println(additional)
    f, err := os.OpenFile(FileName, os.O_WRONLY|additional, 0666)
    defer f.Close()
    if err != nil {
        fmt.Println("Cannot open the file")
        return
    }   

    doc.Find("kite-headline a span").Each(func(i int, s *goquery.Selection) {
        title := s.Text()
        println(title)
        sentence := GetDate() + "\t" + title + "\n"
        f.WriteString(sentence)      // ファイル書き込み
    })
}

func GetDate() string {
    // t := time.Now()
    t := time.Now().String()
    t1 := strings.Split(t, " ")[0]
    d := strings.Split(t1, "-")
    return d[1] + "/" + d[2]
    // return t.Month().String() + strconv.Itoa(t.Day())
}

func Exists(name string) bool {
    _, err := os.Stat(name)
    return !os.IsNotExist(err)
}