catcutの日記

気ままに更新します。過去の制作物もそのうち公開します。

ラズパイとTwythonを使った自動リツイートシステムの構築について③


ラズパイを使って特定ユーザーのツイートを自動でリツイートするシステムを紹介します。いくつかの記事に分けて掲載する予定です。

面倒くささを3段階で示しておきます。★の数が多いほど面倒くさいです。

①下準備 ★★★

下準備A: twitter APIの利用申請 ★★★
下準備B: ラスパイの設定 ☆★★

②コーディング ☆☆☆

③定期実行の設定 ☆☆★

前回はラズパイの設定を行いました。

catcut.hatenablog.com

今回はコーディングについて説明します。


Youtubeに完成品の動作イメージを投稿しました。
youtu.be
youtu.be


Twythonライブラリについて

TwitterAPIという仕組みを使いやすくしたライブラリです。
TwitterAPIそのものではありません。これがなくとも自動投稿はできますが非常に面倒くさいです。

まず全体像です。

2つの.pyファイルを作成します。
1つ目はTwitterAPIへのアクセスキーを記述したファイルです。
下記記事で作成した4つの文字列を記述します。

API Key
API Key Secret
Access Token
Access Token Secret


2つ目は実際にリツイートするプログラムです。
2つのファイルは同じフォルダに入れておいてください。

catcut.hatenablog.com

auth.py
api_key = 'your key'
api_key_secret = 'your key'
access token = 'your access token'
access_token_secret = 'your access token'

ouen.py
# -*- coding: utf-8 -*-
from twython import Twython
from auth import (
	api_key,
	api_key_secret,
	access_token,
	access_token_secret
)
import datetime
import csv
import pprint
import os

class Tweet:
	def __init__(self):
		self.twitter = Twython (api_key, api_key_secret, access_token, access_token_secret)

		folder_path = os.getcwd()
		self.TText_path = folder_path + '/TText'
		self.csv_path =  folder_path + '/coume.csv'
		self.log_text_path = folder_path + '/log_data.txt'

		#create csv file and folder, if these don't exist.
		os.makedirs( self.TText_path, exist_ok = True)
		if os.path.exists(self.csv_path) == False:
			set_data = ['Wed Dec 28 09:27:18 +0000 2022', '1608031831169077248', self.TText_path + '/Text0.txt']
			with open(self.csv_path, "w", encoding='utf-8') as f:
				writer = csv.writer(f)
				writer.writerow(set_data)

	def toko(self, user_name = 'dayukoume'):
		#record execution time to the log text file
		with open(self.log_text_path, "a") as f:
			f.write("[{}] :".format(datetime.datetime.now().strftime("%Y%m%d_%H:%M:%S")))
		
		#read the previous data
		with open(self.csv_path, "r") as f:
			reader = csv.reader(f)
			preData = [row for row in reader]

		#get date to use from the user timeline
		user_timeline = self.twitter.get_user_timeline(screen_name = user_name, count = 5)
		buf = self.extract_data(user_timeline = user_timeline)

		#write the data to .csv
		with open(self.csv_path, "w") as f:
			writer = csv.writer(f)
			writer.writerows(buf)

		#Retweet
		num_RT = self.RT(ID = self.toko_check(preData = preData, nowData = buf), buf = buf)

		#update the logdata
		with open(self.log_text_path, "a") as f:
			f.write( str(num_RT) + '件リツイート!\n')

	def extract_data(self, user_timeline):
		buf = [[]]
		for i in range(len(user_timeline)):
			TTP = self.TText_path + '/TText' + str(i) + '.txt' #Tweet Text Path 
			with open(TTP, "w") as f:
				f.write(user_timeline[i]['text'])       
			buf.append([user_timeline[i]['created_at'],user_timeline[i]['id'],TTP])
		buf.pop(0)
		return buf

	def toko_check(self, preData, nowData):
		toko_data = [i for i in range(len(nowData))]
		for i in range(len(nowData)):
			flag = 0
			for j in range(len(preData)):
				if nowData[i][0] == preData[j][0]: flag = 1
				if self.reject_check(textData_path = nowData[i][2], reject_word = 'RT @') == True: flag = 1
			if flag == 1: toko_data.remove(i)
		return toko_data

	def reject_check(self, textData_path, reject_word = 'RT @'):
		with open(textData_path, "r") as f:
			textData = f.read()
		if reject_word in textData:
			return True
		return False

	def RT(self, ID, buf):
		if ID:
			for i in range(len(ID)):
				print(buf[i][1])
				self.twitter.retweet(id = buf[i][1])
		return len(ID)

def main():
	print('小梅太夫を応援します')
	twython_bot=Tweet()
	twython_bot.toko()
	print('successful!!')

if __name__ == "__main__":
	main()

プログラムの実行前と実行後

実行前のフォルダ


ディレクトリを移動してプログラムの実行


出力ファイル3種

ログファイル:何件リツイートしたか記録


csvファイル:投稿時間とツイートIDとテキスト内容を記録したファイルのパスを記載


テキストファイル:ツイートのテキストを格納したファイル

プログラムの流れ

  1. main関数として呼び出されたらmain()という関数を呼び出します。(97~98行目)
  2. 処理に使うクラスをtwython_botという名前でインスタンス化します。(93行目)
  3. __init__というメソッドが呼び出されます。(15~29行目)
  4. ツイッターAPIを使うための準備をします。(16行目)
  5. 現在の作業ディレクトリを取得します。(18行目)
  6. 処理に使用するパスの定義をしておきます。(19~21行目)
  7. フォルダを再帰的に作成します。すでにある場合は無視されます。(24行目)
  8. csvファイルがなければ作成しておきます。処理内容の記録用のファイルです。(25~29行目)
  9. tokoというメソッドを呼び出します。今回は引数を与えません。(94行目)
  10. user_nameという引数を与えられます。ツイッターアカウントのユーザーネーム(@を除く)を指定します。(31行目)
  11. プログラムの起動時刻が記録されます。(33~34行目)
  12. 前回の処理内容を読み取っておきます。(37~39行目)
  13. timelineを取得します。(42行目)
  14. 取得したデータは情報量が多いため、必要な情報のみを抽出します。(43行目)
  15. timelineからツイートのテキストデータを抽出して、テキストファイルに保存します。(59~62行目)
  16. 投稿時刻とツイートID、テキストのパスを格納したリストを作成します。(63行目)
  17. 最初の要素は空なので消し飛ばします。(64行目)
  18. 読み取ったデータをcsvファイルに上書きします。(46~48行目)
  19. リツイートを行います。特定の条件のツイートIDをtoko_checkというメソッドで抽出してリツイートを行います。(51行目)
  20. 今回タイムラインから抽出したデータの要素番号を格納するリストを作成します。(68行目)
  21. 前回抽出したデータと総当たり戦を行います。投稿時刻が重複しているもの、テキストに"RT @"とついているものをリツイート対象から排除します。(69~74行目)
  22. テキストに"RT @"がついているかどうかはreject_checkというメソッドで行っています。(73行目)
  23. テキストファイルを読み取り、文字列に"RT @"が存在すればTrue、存在しなければFalseを返します。(77~82行目)
  24. リツイートするIDが抽出出来たら、実際に投稿します。(84~89行目)
  25. リツイートした数が返ってくるので、記録しておきます。(54~55行目)

取得するタイムラインデータの例

必要なデータを抽出しましょう。

[{'contributors': None,
  'coordinates': None,
  'created_at': 'Tue Jan 03 15:42:12 +0000 2023',
  'entities': {'hashtags': [{'indices': [37, 47], 'text': 'まいにちチクショー'}],
               'symbols': [],
               'urls': [],
               'user_mentions': []},
  'favorite_count': 351,
  'favorited': False,
  'geo': None,
  'id': 1610300502587371524,
  'id_str': '1610300502587371524',
  'in_reply_to_screen_name': None,
  'in_reply_to_status_id': None,
  'in_reply_to_status_id_str': None,
  'in_reply_to_user_id': None,
  'in_reply_to_user_id_str': None,
  'is_quote_status': False,
  'lang': 'ja',
  'place': None,
  'retweet_count': 50,
  'retweeted': False,
  'source': '<a href="http://twitter.com/download/android" '
            'rel="nofollow">Twitter for Android</a>',
  'text': '湯の花かと思ったら~、\n\nロクロク首にピアスでした~。\n\nチクショー!!\u3000#まいにちチクショー',
  'truncated': False,
  'user': {'contributors_enabled': False,
           'created_at': 'Sun Oct 16 08:15:54 +0000 2011',
           'default_profile': True,
           'default_profile_image': False,
           'description': 'https://t.co/9RPGbBGxgg\n'
                          '\n'
                          'チクポーレディオ\n'
                          'https://t.co/erHVqVbO5m\n'
                          '        \n'
                          'コウメ太夫のクックショー!\n'
                          'https://t.co/H0jtfq3lIu',
           'entities': {'description': {'urls': [{'display_url': 'instagram.com/koumedayu/',
                                                  'expanded_url': 'https://www.instagram.com/koumedayu/',
                                                  'indices': [0, 23],
                                                  'url': 'https://t.co/9RPGbBGxgg'},
                                                 {'display_url': 'youtube.com/user/shotti01/',
                                                  'expanded_url': 'http://youtube.com/user/shotti01/',
                                                  'indices': [34, 57],
                                                  'url': 'https://t.co/erHVqVbO5m'},
                                                 {'display_url': 'youtube.com/channel/UC0-bl…',
                                                  'expanded_url': 'https://www.youtube.com/channel/UC0-bl1zmCvGuOvHvhbC7bbw',
                                                  'indices': [81, 104],
                                                  'url': 'https://t.co/H0jtfq3lIu'}]}},
           'favourites_count': 44,
           'follow_request_sent': False,
           'followers_count': 183173,
           'following': True,
           'friends_count': 713,
           'geo_enabled': True,
           'has_extended_profile': True,
           'id': 391900115,
           'id_str': '391900115',
           'is_translation_enabled': False,
           'is_translator': False,
           'lang': None,
           'listed_count': 713,
           'location': '',
           'name': 'コウメ太夫',
           'notifications': False,
           'profile_background_color': 'C0DEED',
           'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png',
           'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png',
           'profile_background_tile': False,
           'profile_banner_url': 'https://pbs.twimg.com/profile_banners/391900115/1492515189',
           'profile_image_url': 'http://pbs.twimg.com/profile_images/378800000865932105/orzBqjQ1_normal.jpeg',
           'profile_image_url_https': 'https://pbs.twimg.com/profile_images/378800000865932105/orzBqjQ1_normal.jpeg',
           'profile_link_color': '1DA1F2',
           'profile_sidebar_border_color': 'C0DEED',
           'profile_sidebar_fill_color': 'DDEEF6',
           'profile_text_color': '333333',
           'profile_use_background_image': True,
           'protected': False,
           'screen_name': 'dayukoume',
           'statuses_count': 5765,
           'time_zone': None,
           'translator_type': 'none',
           'url': None,
           'utc_offset': None,
           'verified': False,
           'withheld_in_countries': []}}]