SHOWROOM - イベントでの配信者の獲得ポイント数を取得する(改良版)
SHOWROOMのイベントで配信者の獲得ポイントを取得する方法についてはすでに
「Showroom - イベントの獲得ポイント数を取得して記録するツール」
に書いたのですが、いろいろと問題がありました。いちばん問題なのはパフォーマンスの悪さです。いちいちブラウザで画面を開きそこからデータを読み取っているわけですから、手間(時間)がかかりますし、負荷も大きくなるからとうぜんです。(おそらくこれに関連して)長時間動作させると不安定になってくるという問題もありました。
これらの対策として
「SHOWROOMのイベント参加者のリストを取得する(GO, スクレイピング)」
に書いたような方法があります。ブラウザを使わないので動作がかなり軽くなるはずです。当初この方法で動作を改善しようと思っていたのですが、じつはもっと効果的な方法があります。SHOWROOMには配信者の情報を得るためのAPIが提供されているようです。このAPIをどうやって見つけたのかが謎なのですが、例えば現時点で参照できるものとしては
「5ちゃんねる - AKB48×SHOWROOM ★1289」
に紹介されています(紹介と言ってもAPIだけがぽつんと書かれているだけです)
このAPIは配信者のID(room_id)を指定して実行すると参加イベントや獲得ポイント数、順位などの配信者の情報がJSONで取得できます。幸いGO言語にはJSONをパースするためのライブラリーがありますのでそれを使えば比較的簡単に必要な情報を得ることができます。GO言語でのJSONの処理についてはいろいろあるのですが、今回は
「qiita @msh5 - Go言語でJSONに泣かないためのコーディングパターン」
の記事を参考にさせていただきました。
--------
ソースはこの記事の末尾に添付します。このプログラムは
RoomList.txtにリストされたファイル群から配信者さんのリストを読み込み
各々の配信者についてイベントでの獲得ポイントや順位を取得して
配信者リスト単位で結果をファイルに追記する
ということを指定された時間間隔で行う
というものです。この記事をご覧になる方に必要だと思われる「特定の配信者の獲得ポイントなどを取得する」処理は GetPoints() のところで、それほど大きな処理ではないです。
なお一点注意すべき点があります。使用しているSHOWROOMのAPIから戻されるJSONの構造は(少なくとも)二種類あることです。
これはレベルイベントとランキングイベントで結果の構造が異なるためです。ソースではランキングイベントの構造を想定して対象データを取得しに行き、もしデータを取得できなければ、レベルイベントの構造を想定して取得する、ということをやっています。
それから特定イベントの全参加者の獲得ポイントを知りたいというような場合はまずイベント参加者リストの取得が必要ですが、これは
「SHOWROOMのイベント参加者のリストを取得する(GO, スクレイピング)」
を参考にしていただければと思います(配信者の情報はどちらも同じ型(slice)にしてあるのでそのまま組み込めるはず)
取得した獲得ポイントデータをデータベースに格納して保存するような場合は毎回イベント参加者リストを取得した方が合理的でしょう。
-------------------------
参考
「Showroom - 自動星集め・星投げ・カウントツール)」 (使用法とソースつき)
「Showroom - 自動三周ツール(もう一つの自動星集め・星投げ・カウントツール)」
「Showroom - イベントの獲得ポイント数を取得して記録するツール」
「SHOWROOMのイベント参加者のリストを取得する(GO, スクレイピング)」
「Showroom - 福引するプログラムとその結果 (1)」
「Showroomの複数アカウント(複アカ、複垢)について考えてみた(1)」
「Showroom - 複数アカウント(複垢)問題の真実 - 実験計画」
「Showroomの重複アカウント(複垢)減算はこうして起きる」
「Showroomでの自動星集めの試み (3) ガチイベ、最後の5分間
「Showroom ラスカルイベの最後の5分間」
「Showroomでの自動星集めの試み (1)」
「Showroomでの自動星集めの試み (2) 配信ルームの一覧を作る」
「Showroomでの自動星集めの試み (4) 星集めツール」
「Showroomでひたすらリスナーレベルを上げるための星集めツール(Go/Agouti)」
「超初心者のGo言語/agouti - ブラウザ操作の基本の基本」
「超初心者のGo言語 - 複数の戻り値をもつ関数」
「超初心者のGo言語 - もっとも簡単なGoroutine(並列処理)」
---------------------
「GoDoc - package agouti」
「Qiita @0829 - Goではじめてみたブラウザの自動操作」
「Qiita @tenten0213 - agoutiというWebDriverクライアントを使って面倒な作業を自動化する」
「Qiita @masaru_b_cl - Windows上でGo言語初心者向け学習環境を作る」
「はじめてのGo言語」
「天才まくまくノート - まくまく Hugo/Go ノート - 関数を定義する (func)」
「Qiita @TakaakiFuruse - Golang Goの並列処理を学ぶ(goroutine, channel)」
「Qiita @To_BB - Rubyエンジニアがゴルーチン(Go言語)を学んでみた【初心者向け】」
「Qiita @fukumone - goroutine 使い方まとめ」
----------------------------------
/*
指定した時刻に指定したイベント、配信者の獲得ポイントを取得します。
これは前バージョンと実行時パラメーター、入力ファイルの形式をあわせてあります。現在のバージョンではFDetailは機能しません。
EvalPoints2 Folder Interval Mod HH_Detail FTitle FDetail
Folder [5|6] Ex. 6 作業用フォルダーの識別子
Interval 5..30 Ex. 30 データを取得する間隔、60の公約数
Mod 0..Interval-1 Ex. 2 IntervalのMod分前にデータを取得する(Interval=30、MOd=2であれば、28分、58分)
HH_Detail 0..23 Ex. 0 または 4 HH_Detailと同一時刻(時間)の最初のデータ取得時に貢献ポイントランキングを取得する。00時は無条件に取得。
FTitle [0|1] Ex. 0 (Intervalが0のとき=一回のみデータ取得のとき)1であれば配信者名リストを出力する
FDetail N/A
※ Intervalが0でないときは、開始時に配信者名リストを出力します。
EvalPoints2A02 2019/04/30
イベントが終わっている、イベント参加をとりやめた、SHOWROOMをやめた、などの対応
EvalPoints2A03 2019/06/22
ランキングイベントとレベルイベントの判別処理を追加した。
*/
package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"encoding/json"
"net/http"
)
// 現在の日時をstringに変換する。
// これは自分で書かなくてもtimeに関数があったような....
func TimeNow() string {
t := time.Now()
hh, mm, ss := t.Clock()
yr, mt, dy := t.Date()
return fmt.Sprintf("%4d%02d%02d_%02d%02d%02d", yr, mt, dy, hh, mm, ss)
}
// idで指定した配信者さんの獲得ポイントを取得する。
// 戻り値は 獲得ポイント、順位、上位とのポイント差(1位の場合は2位とのポイント差)、イベント名
// レベルイベントのときは順位、上位とのポイント差は0がセットされる。
func GetPoints(id string) (Point, Rank, Gap int, EventName string) {
// 獲得ポイントなどの配信者情報を得るURL(このURLについては記事参照)
URL := "https://www.showroom-live.com/api/room/event_and_support?room_id=" + id
resp, err := http.Get(URL)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// JSONをデコードする。
// 次の記事を参考にさせていただいております。
// Go言語でJSONに泣かないためのコーディングパターン
// https://qiita.com/msh5/items/dc524e38073ed8e3831b
var result interface{}
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&result); err != nil {
panic(err)
}
// イベントが終わっている、イベント参加をとりやめた、SHOWROOMをやめた、などの対応
if result.(map[string]interface{})["event"] == nil {
return 0, 0, 0, ""
}
if result.(map[string]interface{})["event"].(map[string]interface{})["ranking"] != nil {
// ランキングのあるイベントの場合
// (順位に応じて特典が与えられるイベント、ただし獲得ポイントに対して特典が与えられる場合でも順位付けがある場合はこちら)
// 獲得ポイント
l, _ := result.(map[string]interface{})["event"].(map[string]interface{})["ranking"].(map[string]interface{})["point"].(float64)
// 順位
m, _ := result.(map[string]interface{})["event"].(map[string]interface{})["ranking"].(map[string]interface{})["rank"].(float64)
// ポイント差
n, _ := result.(map[string]interface{})["event"].(map[string]interface{})["ranking"].(map[string]interface{})["gap"].(float64)
Point = int(l)
Rank = int(m)
Gap = int(n)
// イベント名
EventName, _ = result.(map[string]interface{})["event"].(map[string]interface{})["event_url"].(string)
EventName = strings.Replace(EventName, "https://www.showroom-live.com/event/", "", -1)
} else if result.(map[string]interface{})["event"].(map[string]interface{})["quest"] != nil {
// レベルイベント(ランキングのないイベント)の場合
// (アバ権やステッカーなど獲得ポイントに応じて特典が与えられるイベント、ただし順位付けがある場合は除く)
// 獲得ポイント
l, _ := result.(map[string]interface{})["event"].(map[string]interface{})["quest"].(map[string]interface{})["support"].(map[string]interface{})["current_point"].(float64)
// 順位
m := 0.0
// ポイント差
n := 0.0
Point = int(l)
Rank = int(m)
Gap = int(n)
// イベント名
EventName, _ = result.(map[string]interface{})["event"].(map[string]interface{})["event_url"].(string)
EventName = strings.Replace(EventName, "https://www.showroom-live.com/event/", "", -1)
} else {
// 上記ランキングイベントでもレベルイベントでもない場合
// もしこのようなケースが存在するならJSONを確認して新たにコーディングする
fmt.Println(" N/A")
return 0, 0, 0, ""
}
return
}
/*
配信者のリストからそれぞれの獲得ポイントなどを取得する。
*/
func GetPointsAll(IdList []string) ([]int, []int, []int, []string, []string) {
Length := len(IdList)
PointList := make([]int, Length, Length)
RankList := make([]int, Length, Length)
GapList := make([]int, Length, Length)
EventNameList := make([]string, Length, Length)
STimeNow := make([]string, Length, Length)
for i := 0; i < Length; i++ {
PointList[i], RankList[i], GapList[i], EventNameList[i] = GetPoints(IdList[i])
STimeNow[i] = TimeNow()
}
return PointList, RankList, GapList, EventNameList, STimeNow
}
/*
各配信者さんの獲得ポイントのリストを作る(ファイルに追記する)
ファイルは獲得ポイントを横並びにしたものと、各配信者さんの順位、獲得ポイント、
*/
func ScanActive(FN_RoomList string, First bool) {
var RoomListT, NameListT, EvnIDListT, ContListT string
var GetDetT bool
var SpcListT, Spc2ListT int
var RoomList, NameList, EvnIDList, ContList, STimeNow []string
var GetDet []bool
var SpcList, Spc2List []int
var Start1, End1, Start2 int
// 配信者リストファイルを読み込む。
FN_RoomList_name := strings.TrimSuffix(FN_RoomList, ".txt")
fRoomList, _ := os.OpenFile(FN_RoomList, os.O_RDONLY, 0644)
// 配信者リストの先頭行は前バージョンで使っていたものでこのバージョンでは使用しません。読み飛ばします。
fmt.Fscanf(fRoomList, "%d%d%d%d\r\n", &Start1, &End1, &Start2)
fmt.Println(Start1, End1, Start2)
// 配信者リストの読み込み
i := 0
for {
NRec, _ := fmt.Fscanf(fRoomList, "%s%s%s%s%t%d%d\r\n",
&NameListT, &RoomListT, &EvnIDListT, &ContListT, &GetDetT,
&SpcListT,
&Spc2ListT)
if NRec != 7 {
break
}
NameList = append(NameList, NameListT)
RoomList = append(RoomList, RoomListT)
EvnIDList = append(EvnIDList, EvnIDListT)
ContList = append(ContList, ContListT)
GetDet = append(GetDet, GetDetT)
SpcList = append(SpcList, SpcListT)
Spc2List = append(Spc2List, Spc2ListT)
fmt.Println(NameList[i], RoomList[i], EvnIDList[i], ContList[i], GetDet[i], SpcList[i])
i++
}
fRoomList.Close()
NumRoom := i
fmt.Println(NumRoom)
t := time.Now()
hh, mm, ss := t.Clock()
yr, mt, dy := t.Date()
PointList, RankList, GapList, EventNameList, STimeNow := GetPointsAll(ContList)
// 詳細データ(順位、獲得ポイント、ポイント差等)ファイル出力
OutputFilename := FN_RoomList_name + "_out.txt"
fout, _ := os.OpenFile(OutputFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
fmt.Fprintf(fout, "\t%4d/%02d/%02d %02d:%02d:%02d\r\n", yr, mt, dy, hh, mm, ss)
for i := 0; i < NumRoom; i++ {
fmt.Printf("%2d%8d%8d %s\r\n", RankList[i], PointList[i], GapList[i], RoomList[i])
fmt.Fprintf(fout, "%d\t%d\t%d\t%s\t%s\t%s\t%s\r\n",
RankList[i], PointList[i], GapList[i],
RoomList[i], NameList[i], EventNameList[i], STimeNow[i])
}
fout.Close()
// 獲得ポイントデータファイル出力
ListFilename := FN_RoomList_name + "_outL.txt"
flist, _ := os.OpenFile(ListFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// 配信者リストを出力する
if First {
First = false
fmt.Fprintf(flist, "Room\t")
for i := 0; i < NumRoom; i++ {
fmt.Fprintf(flist, "\t%s", NameList[i])
}
fmt.Fprintf(flist, "\r\n")
fmt.Fprintf(flist, "Event\t")
for i := 0; i < NumRoom; i++ {
fmt.Fprintf(flist, "\t%s", EvnIDList[i])
}
fmt.Fprintf(flist, "\r\n")
}
// 獲得ポイントを出力する。
fmt.Printf("%d/%d/%d %d:%d:%d", yr, mt, dy, hh, mm, ss)
fmt.Fprintf(flist, "%d/%d/%d %d:%d:%d\t", yr, mt, dy, hh, mm, ss)
for i := 0; i < NumRoom; i++ {
fmt.Printf("\t%d", PointList[i])
fmt.Fprintf(flist, "\t%d", PointList[i])
}
fmt.Fprintf(flist, "\r\n")
fmt.Printf("\r\n")
flist.Close()
}
func main() {
var Folder int
// 前バージョンとパラメータを一致させるためのもの
if len(os.Args) > 1 {
Folder, _ = strconv.Atoi(os.Args[1])
}
if len(os.Args) < 7 || Folder != 5 && Folder != 6 {
fmt.Println(os.Args[0], " 5|6 Interval Mod 0|1 0|1")
return
}
// 獲得ポイントデータを取得するタイミングを決定する。
Interval, _ := strconv.Atoi(os.Args[2])
Mod, _ := strconv.Atoi(os.Args[3])
HH_Detail, _ := strconv.Atoi(os.Args[4])
fmt.Println(" Interval=", Interval, " Mod=", Mod, " HH_Detail=", HH_Detail)
// インターバルが指定されているときはInterval分ごとに獲得ポイントを取得する
if Interval != 0 {
First := true
for {
t := time.Now()
_, mm, ss := t.Clock()
//
if (60-mm)%Interval == Mod {
fRoomList, _ := os.OpenFile("RoomList.txt", os.O_RDONLY, 0644)
var FN_RoomList string
// 配信者リストのファイルはイベントごとに作ってあるので、配信者リストのファイル数分繰り返す。
for {
NRec, _ := fmt.Fscanf(fRoomList, "%s\r\n", &FN_RoomList)
if NRec != 1 {
break
}
ScanActive(FN_RoomList, First)
}
fRoomList.Close()
First = false
} else {
fmt.Println(" skip mm=", mm)
}
time.Sleep(time.Duration(60-ss) * time.Second)
}
} else {
// インターバルが指定されていないとき(0のとき)は一回のみ実行する
First := false
if os.Args[5] == "1" {
First = true
}
fRoomList, _ := os.OpenFile("RoomList.txt", os.O_RDONLY, 0644)
var FN_RoomList string
for {
NRec, _ := fmt.Fscanf(fRoomList, "%s\r\n", &FN_RoomList)
if NRec != 1 {
break
}
ScanActive(FN_RoomList, First)
}
fRoomList.Close()
}
}
« SHOWROOM - イベント結果データ (2) | トップページ | SHOWROOM - イベント貢献ランキング(貢献ポイント)を取得する関数(ソース) »
「パソコン・インターネット」カテゴリの記事
- さくらインターネットのレンタルサーバーでGOで書いたCGIを動かした(苦労)話(2021.04.19)
- SHOWROOMのAPI - 「ライブ情報」の取得(GO言語のソースつき)(2019.10.26)
- SHOWROOM 星集め・星投げツール スケジュールの詳細化 BreakDownSchedule() (三周のやり方を例に)(2019.09.25)
- SHOWROOM 新・自動三周ツール -- GO言語によるブラウザ制御 (1) main()(2019.09.17)
この記事へのコメントは終了しました。
コメント