20201026のGoに関する記事は2件です。

QiitaのView数を取得する

初めに

QiitaのLGTM数とView数を取得するにあたって、
APIの仕様が変わったのか参考にさせていただいたもので取得できなかった箇所があったので、
取得方法を記載していきます。

環境

Go 1.15.2

1. アクセストークンを取得

ユーザ管理画面からトークンを取得
image.png

スコープはread_qiitaとwrite_qiitaを選択
image.png

2. 取得したい情報のURLを確認

公式ドキュメントより取得したい情報のURLを確認。今回利用するのは以下の2種類です。

GET /api/v2/authenticated_user/items
認証中のユーザの記事の一覧を作成日時の降順で返します。
GET /api/v2/items/:item_id
記事を取得します。

3. jsonを変換するstructを作成

公式ドキュメントよりAPIを叩いたときに返却される情報がJsonで返却されるため、
返却されたデータを扱うstructを作成。

UserInfo.go
package data

type UserInfo struct {
    Id               string `json:"id"`
    Likes_count      int    `json:"likes_count"`
    Title            string `json:"title"`
    Page_views_count int    `json:"page_views_count"`
}

4. Qiita APIを叩く

web.go
package qiita

import (
    "../data"
    "../exporter"
    "encoding/json"
    "io/ioutil"
    "net/http"
)

func GetQiitaViews() {                                      // main.goから呼び出す
    url := "https://qiita.com/api/v2/authenticated_user/items?page=1&per_page=20"
    resp, err := doHttpRequest(url)
    defer resp.Body.Close()                                 // Bodyをクローズ

    body, err := ioutil.ReadAll(resp.Body)                  // レスポンスのBodyから取得

    var userInfos []data.UserInfo
    if err = json.Unmarshal(body, &userInfos); err != nil { // jsonの読み出し
        return
    }

    index := 0
    for _, user := range userInfos {                       // 1記事ずつ読み出す
        url = "https://qiita.com/api/v2/items/" + user.Id  // 記事のIDを利用、view数を取得
        resp, err := doHttpRequest(url)
        defer resp.Body.Close()

        body, err := ioutil.ReadAll(resp.Body)

        if err := json.Unmarshal(body, &user); err != nil {return}
        userInfos[index].Page_views_count = user.Page_views_count
        index += 1
    }
    exporter.ToCsv(userInfos)                                // csv化
}

func doHttpRequest(url string) (*http.Response, error) {     // リクエストを共通化
    req, _ := http.NewRequest("GET", url, nil)

    buf, _ := ioutil.ReadFile("token.txt")
    token := string(buf)                                     // tokenを別ファイルで管理

    req.Header.Set("content-type", "application/json")       // ヘッダーにapplication/jsonを指定
    req.Header.Set("Authorization", "Bearer " + token)       // ヘッダーにtokenを指定

    client := new(http.Client)
    resp, err := client.Do(req)
    return resp, err
}

以下のAPIだけだと取得できないようで、
URLにクエリをつける必要があることに最初気づかず、取得することができていませんでした。
また、page_view_countがnull(Goでstructに入れた場合0)が返ってきてしまうため、
view数は記事ごとに取得する必要があるようです。
【Qiita API】いいね!閲覧数の自動集計の記事を参考にさせていただきました。

GET /api/v2/authenticated_user/items
認証中のユーザの記事の一覧を作成日時の降順で返します。

そこで、以下のAPIを利用しています。

GET /api/v2/items/:item_id
記事を取得します。

5. CSV出力

export.go
package exporter

import (
    "../data"
    "encoding/csv"
    "os"
    "strconv"
)

func ToCsv(userInfos []data.UserInfo) {
    file, _ := os.OpenFile("result.csv", os.O_WRONLY|os.O_CREATE, 0600)  //ファイルを開く
    defer file.Close()

    writer := csv.NewWriter(file)
    writer.Write([]string{"title", "like_count", "page_views_count"})   // ヘッダーをつける
    for _, user := range userInfos {
        likesCount := strconv.Itoa(user.Likes_count)                    // intをstringへ変換
        pageViewsCount := strconv.Itoa(user.Page_views_count)
        writer.Write([]string{user.Title, likesCount, pageViewsCount})  // 書き込み
    }
    writer.Flush()
}

最後に

ソースはここにあります。
GitHubにソースをアップする関係上、
token情報はmain.goと同一ディレクトリ内の別ファイルにて管理しています。
エラーハンドリングは一部省略している箇所があります。

参考文献

公式
【Qiita API】いいね!閲覧数の自動集計
Go言語でCSVを書き出す!エクセル用のSJIS版も!
Go の import cycle not allowed (循環参照) の対応

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GO言語でオブジェクト指向っぽいことをする

追記
Go言語にオブジェクト指向じゃない、これがGo風のオブジェクト指向です!
と言うと語弊があるようです
コメント欄に色々と指摘をいただいているので、そちらを確認いただければ幸いです


ディップ株式会社に中途入社してGo言語でAPIを作ってます。
今までPHPやRubyなんかのゆるっとしたオブジェクト指向言語がメインだったので毎日ヒーヒー言ってます。

そんな中でも前職の経験を活かせる機会があったので
今回は「前職の経験を活かしてGo言語のソースをいい感じにした」話をしようと思います。

Go言語はオブジェクト指向じゃない

Go言語を扱っている方は当然ご存知かと思いますが、Go言語にオブジェクト指向なんてありません。
クラスも、継承も、コンストラクタもありません。
今まで培ってきたオブジェクト思考(誤字ではない)がまるでゴミのようです。

Goで実装するなら素直にGoっぽい実装をすべきなのでしょうが、そうは言っても「これオブジェクト指向だったらこう実装するのに」という考えがよぎるもの。
未練がましくネットで検索したところ「Go言語でオブジェクト指向っぽいことをする方法」がいくつか出てきました。
それらを参考にいい感じの実装ができたので、サンプルと共にまとめておきます。

やりたいこと

  • プロパティに値を保持する
  • コンストラクタで初期設定を行う
  • 共通のプロパティを定義する
  • 共通のメソッドを定義する
  • 継承した子クラスに独自のプロパティを持たせる
  • 継承した子クラスに独自のメソッドを持たせる
  • 継承した子クラスで振る舞いを変える

オブジェクト指向と言っても色々ありますが、今回はこの辺を目指します。

実装例

package main

import (
    "fmt"
)

const line = "--------------------"

func main() {
    wiz := newWizard("魔法少女", 10, 10, 5)
    war := newWarrior("+‡†狂戦士†‡+", 10, 15, 30)

    fmt.Println(wiz.hello())
    fmt.Println(war.hello())

    fmt.Println(line)
    fmt.Println(wiz.attack())
    fmt.Println(war.attack())

    fmt.Println(line)
    fmt.Println(wiz.magic())
    fmt.Println(war.attack())
}

type human struct {
    name string
    hp int
    ap int
}

func (h *human) init(name string, hp, ap int) {
    h.name = name
    h.hp = hp
    h.ap = ap
}

func (h *human) hello() string {
    return fmt.Sprintf("こんにちは、私は%sです。", h.name)
}

func (h *human) attack() string {
    return fmt.Sprintf("%sの攻撃!%dのダメージ!", h.name, h.ap)
}

type wizard struct {
    human
    mp int
}

func newWizard(name string, hp, ap, mp int) *wizard {
    w := new(wizard)
    w.init(name, hp, ap)
    w.mp = mp
    return w
}

func (w *wizard) magic() string {
    if w.mp <= 0 {
        return fmt.Sprintf("%sは力がでない", w.name)
    }
    w.mp -= 1
    return fmt.Sprintf("%sは魔法を使った!30のダメージ!", w.name)
}

type warrior struct {
    human
}

func newWarrior(name string, hp, ap, mp int) *warrior {
    w := new(warrior)
    w.init(name, hp, ap)
    return w
}

func (w *warrior) attack() string {
    return fmt.Sprintf("%sの攻撃!%dのダメージ!", w.name, w.ap*2)
}

実行結果

こんにちは、私は魔法少女です。
こんにちは、私は+‡†狂戦士†‡+です。
--------------------
魔法少女の攻撃!10のダメージ!
+‡†狂戦士†‡+の攻撃!30のダメージ!
--------------------
魔法少女は魔法を使った!30のダメージ!
+‡†狂戦士†‡+の攻撃!30のダメージ!

よくあるRPGの職業的なやつ

実装内容の説明

親クラス(構造体)

type human struct {
    name string
    hp int
    ap int
}

共通で使うプロパティを定義しておく

親クラス(構造体)のコンストラクタ

func (h *human) init(name string, hp, ap int) {
    h.name = name
    h.hp = hp
    h.ap = ap
}

Goには構造体というものがないので、コンストラクタの仕事をさせるメソッドを好きに定義する
ここでは init と名付けているがなんでもいい

親クラス(構造体)にぶら下げるメソッド

func (h *human) hello() string {
    return fmt.Sprintf("こんにちは、私は%sです。", h.name)
}

func (h *human) attack() string {
    return fmt.Sprintf("%sの攻撃!%dのダメージ!", h.name, h.ap)
}

子クラス(構造体)

type wizard struct {
    human
    mp int
}

type warrior struct {
    human
}

親クラス(構造体)を指定することで、親クラス(構造体)で定義したプロパティやメソッドを内包できる(継承ではない)
小クラス(構造体)独自でプロパティを増やす場合は続けて記載できる(前でも後でもいい)

小クラス(構造体)のファクトリー

func newWizard(name string, hp, ap, mp int) *wizard {
    w := new(wizard)
    w.init(name, hp, ap)
    w.mp = mp
    return w
}

func newWarrior(name string, hp, ap, mp int) *warrior {
    w := new(warrior)
    w.init(name, hp, ap)
    return w
}

親クラス(構造体)のインスタンスを作成し、コンストラクタを呼び出す。
子クラス(構造体)独自の初期設定を行いたい場合は続けて記載する。

小クラス(構造体)のメソッド(新規)

func (w *wizard) magic() string {
    if w.mp <= 0 {
        return fmt.Sprintf("%sは力がでない", w.name)
    }
    w.mp -= 1
    return fmt.Sprintf("%sは魔法を使った!30のダメージ!", w.name)
}

小クラス(構造体)専用のメソッドを生やすこともできる

小クラス(構造体)のメソッド(上書き)

func (w *warrior) smash() string {
    return fmt.Sprintf("%sの強攻撃!%dのダメージ!", w.name, w.ap*2)
}

同名のメソッドを定義することで独自の振る舞いをさせることができる

使用例

func main() {
    wiz := newWizard("魔法少女", 10, 10, 5)
    war := newWarrior("+‡†狂戦士†‡+", 10, 15, 30)

    fmt.Println(wiz.hello())
    fmt.Println(war.hello())

    fmt.Println(line)
    fmt.Println(wiz.attack())
    fmt.Println(war.attack())

    fmt.Println(line)
    fmt.Println(wiz.magic())
    fmt.Println(war.smash())
}

親クラス(構造体)のことは気にせず子クラス(構造体)のファクトリーを呼び出し、インスタンスを作成。
共通メソッドを呼び出したり独自メソッド呼び出したりして楽しむ。

オブジェクト指向っぽい実装をしてみて

Go言語でも意外といけるぞ!と好感触。実装がシンプルになった。
処理が複雑にならない程度に使っていくのもアリかと思う。

参考にしたページ

たくさんの記事やサイトから情報をつまみ食いして実装したのは確かなのですが、PCを初期化した際にURLを失念してしまいました。
お詫びと感謝を申し上げると共に、完全オリジナルの記事ではないことを注記しておきます。みんなバックアップはこまめにね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む