« SHOWROOM - イベント結果データ (2) | トップページ | SHOWROOM - イベント貢献ランキング(貢献ポイント)を取得する関数(ソース) »

2019年6月22日 (土)

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 - イベント貢献ランキング(貢献ポイント)を取得する関数(ソース) »

パソコン・インターネット」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

« SHOWROOM - イベント結果データ (2) | トップページ | SHOWROOM - イベント貢献ランキング(貢献ポイント)を取得する関数(ソース) »

フォト

サイト内検索

  • 記事を探されるんでしたらこれがいちばん早くて確実です。私も使ってます (^^;; 検索窓が表示されるのにちょっと時間がかかるのはどうにかしてほしいです。

新着記事

リンク元別アクセス数

  • (アクセス元≒リンク元、原則PCのみ・ドメイン別、サイト内等除く)

人気記事ランキング

  • (原則PCのみ、直近2週間)
無料ブログはココログ