🐦

超簡単! GolangでTwitterのトレンドを取得する

2022/07/31に公開

記事の動機

  • GolangでTwitterのトレンドを取得する記事がググってもあんまりなかったので書いた。
  • Twitterのトレンドを定期的にDBなどに保存してなにか解析などに使いたい。

Twitterトレンドの仕様と基礎知識

  • プログラムからTwitterトレンドを取得したい場合はTwitterAPIの開発者アカウントが必要
  • TwitterAPIはv1.1とv2があるがトレンドAPIはv1.1にしかないのでv1.1に対応したライブラリを使えばよい
  • 今回はgoのライブラリとしてはanacondaを使う
  • Twitterトレンドは5分毎に更新される
  • APIのレートリミットは15分に75回まで APIレファレンス
  • よってcronなどで定期的に呼び出す際は5分に1回で十分
  • トレンドは50件まで返却される
  • TwitterAPIのトレンドを取得するにはパラメーターとして地域の指定が必要、国名もしくは県など
  • APIレファレンスではtweet_volume(トレンドワードがいくらつぶやかれていたか?)も返却されるという記載があるがanacondaは構造体として対応していない。対応していない理由はtweet_volume自体がまともに結果を返すことがないためだと思われる。

anacondaのインストール

go get github.com/ChimeraCoder/anaconda

メインプログラム

package main

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

	"github.com/ChimeraCoder/anaconda"
)

func main() {
	api := getTwitterApi()
	v := url.Values{}
	// トレンドを取得したい地域に日本を指定
	trendResp, err := api.GetTrendsByPlace(23424856, v)

	if err != nil {
		log.Fatal(err)
	}

	t := time.Now()

	fmt.Println(trendResp.AsOf) // 取得実行した時間 (UTC)
	fmt.Println(trendResp.CreatedAt) // トレンドに含まれる一番古い時期からあるものの日時  (UTC)
	fmt.Println(trendResp.Locations) // 指定した地域
	fmt.Println(len(trendResp.Trends)) // トレンドの数
	fmt.Println(t) // 実行時間 (JST)

	// https://pkg.go.dev/github.com/chimeracoder/anaconda#TrendResponse
	// https://pkg.go.dev/github.com/chimeracoder/anaconda#Trend
	for i, v := range trendResp.Trends {
		ranking := i + 1
		fmt.Printf("%d %s\n", ranking, v.Name)
	}
}

func getTwitterApi() *anaconda.TwitterApi {
	anaconda.SetConsumerKey(os.Getenv("CONSUMER_KEY"))
	anaconda.SetConsumerSecret(os.Getenv("CONSUMER_SECRET"))
	return anaconda.NewTwitterApi(os.Getenv("ACCESS_TOKEN"), 
	os.Getenv("ACCESS_TOKEN_SECRET"))
}

Twitterの開発者キーを環境変数に設定してプログラムを実行(実行ファイルをtrecoとする)

go build -o treco
export CONSUMER_KEY=""
export CONSUMER_SECRET=""
export ACCESS_TOKEN=""
export ACCESS_TOKEN_SECRET=""
./treco

プログラム実行結果

2022-07-30T11:39:27Z
2022-07-28T11:35:16Z
[{Japan 23424856}]
50
2022-07-30 20:39:27.501297 +0900 JST m=+0.216547751
1 #明石家紅白
2 #ドッキリGP
3 #香取慎吾
4 FOALS
5 #nhkらじらー
6 ダイナソーJr
7 #ジョブチューン
8 慎吾ちゃん
9 Dinosaur Jr
〜以下略

2022-07-28T11:35:16Zの部分「trendResp.CreatedAt」の時間が実行した時間よりも2日ぐらいズレておりイマイチしっくりこなかったんですがAPIリファレンスを翻訳すると

利用可能な最新の情報は、要求に応じて返されます。created_atフィールドには、
最も古いトレンドがいつトレンドになり始めたかが表示されます。

とのことで、50件のトレンドの中に2日前から存在しているワードがあるという事のようです。

DB保存できるように改良したバージョン

改良したメインプログラム

package main

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

	"github.com/AKB428/go-twitter-trend-collect/model"
	"github.com/ChimeraCoder/anaconda"
	"github.com/joho/godotenv"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	api := getTwitterApi()
	v := url.Values{}
	trendResp, err := api.GetTrendsByPlace(23424856, v)

	if err != nil {
		log.Fatal(err)
	}

	now := time.Now()

	fmt.Println(trendResp.AsOf)
	fmt.Println(trendResp.CreatedAt)
	fmt.Println(trendResp.Locations)
	fmt.Println(len(trendResp.Trends))
	fmt.Println(now)

	var twitterTrends []model.TwitterTrend

	for i, v := range trendResp.Trends {
		ranking := i + 1
		fmt.Printf("%d %s\n", ranking, v.Name)

		t := model.TwitterTrend{Name: v.Name, Rank: int32(ranking), CreatedAt: now, UpdatedAt: now}
		twitterTrends = append(twitterTrends, t)
	}

	db := gormConnect()
	//https://gorm.io/docs/create.html
	//バルクインサート
	db.Create(&twitterTrends)

}

func getTwitterApi() *anaconda.TwitterApi {
	anaconda.SetConsumerKey(os.Getenv("CONSUMER_KEY"))
	anaconda.SetConsumerSecret(os.Getenv("CONSUMER_SECRET"))
	return anaconda.NewTwitterApi(os.Getenv("ACCESS_TOKEN"), os.Getenv("ACCESS_TOKEN_SECRET"))
}

func gormConnect() *gorm.DB {
	var err error

	dbHost := os.Getenv("DB_HOST")
	dbUser := os.Getenv("DB_USER")
	dbPass := os.Getenv("DB_PASS")
	dbName := os.Getenv("DB_NAME")

	if len(dbHost) == 0 {
		dbUser = "root"
	}

	if len(dbUser) > 0 {
		dbPass = ":" + dbPass
	}

	if len(dbHost) == 0 {
		dbHost = "localhost"
	}

	db, err := gorm.Open(mysql.Open(dbUser + dbPass + "@" + "tcp(" + dbHost + ")/" + dbName + "?parseTime=true&loc=Asia%2FTokyo"))
	if err != nil {
		panic(err.Error())
	}

	return db
}

定義したテーブル

CREATE TABLE `twitter_trends` (
  `id` int NOT NULL AUTO_INCREMENT,
  `rank` int DEFAULT NULL,
  `name` varchar(100) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_twitter_trends_on_name` (`name`),
  KEY `index_twitter_trends_on_created_at` (`created_at`),
  KEY `index_twitter_trend_on_rank` (`rank`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

※カラム名がrankなのはSQL関数とかぶっているのであまり良くなかった

プログラム改修点

  • MySQLを使い結果をDB保存
  • ORマッパーとしてgormライブラリを追加
  • .envファイルに対応
  • テーブルのモデルはDDLを先につくってDBに保存した後gormのmodelgenから生成

ハマったところ

MysSQL + golangでの日本時間の扱い

DB接続部分でloc=Asia%2FTokyoを追加していないと、gormのinsert文ではJSTで日時をいれているのに結果的にはUTCで時刻が保存されてしまいます。

nameカラムをutf8mb3に最初していたのでトレンドに絵文字が含まれるとSQLエラー

utf8mb4にして対応。トレンドが絵文字になることは普通にある。

完成版のプログラム

こちらにおいています

https://github.com/AKB428/go-twitter-trend-collect/tree/v0.1z

プログラムを応用するには

5分に1回定期的に実行するにはEC2などでcronで動かすか、Fargateのようなサーバーレス環境でバッチスケジュールとして動かすのが良いかと思います。(私はFargateでまわし初めました)

DBだといまいち結果の確認の手間もあるので、Googleスプレッドシートに保存するプログラムにすればスマフォからGoogleスプレッドシートアプリで状況が確認できるので便利だと思います。

データがたまってきたら本格的にはBigQueryでの分析が面白い気がします(頻出ワード分析など)

Discussion