🕊️

【Python】シャニマスの公式ツイッターから画像・動画を取得する【tweepy】

2022/02/23に公開

はじめに

Python案件に配属されることになったときに勉強がてらにツイッターからコンテンツ取得するスクリプトを書いて遊んでたんですけど、せっかくなので体裁整えて初学者向けに(私も初学者)解説しようと思います。
私がMacしか持っていないのでMac前提になりますが、Windowsの方も適宜読み替えていただければ支障はそんなにないと思います。

ちなみに私がシャニマスのおたくなので、取得するのはシャニマスの動画・画像たちです。
この記事ではシャニマスですが、他のコンテンツにも流用できるところはあると思うので是非参考にしてみてください。

学べること

  • tweepyでのタイムライン取得
  • tweepyでの画像・動画取得
  • Pythonの内包表記
  • urllib パッケージを使ったインターネット上のリソース取得
  • 正規表現

仕様

@imassc_officialのタイムラインを指定した件数分取得して

  • SSRアイドル登場時の動画
  • イベントの予告動画
  • 誕生日ミニコミュ動画
  • web4コマ漫画
  • カードイラスト
    をそれぞれツイート文から抜き出した文字列で名前を付けて保存します。

引数で検索数を指定して実行。

ターミナル
$ python tweet.py 100

取得したものたち。

取得した後のディレクトリ構成は以下のようになります。

get_imassc
├── README.md
├── birth
│   └── 三峰 結華2022-01-15 15:01:00+00:00.mp4
├── card
│   ├── 【殴打、その他の夢について】浅倉 透.png
│   └── 【つよがりのためのララバイ】桑山 千雪.png
├── comic
│   ├── 第323話『チョコの置き場所』.png
│   └── 第324話『懐かしくて嬉しい』.png
├── config.py
├── event
│   └── はこぶものたち.mp4
├── ssr
│   ├── 【マイバレンタイン】有栖川 夏葉.mp4
│   └── 【つよがりのためのララバイ】桑山 千雪.mp4
└── tweet.py

レポジトリはこちら
https://github.com/zakiii0124/get_imassc

tweepyとは

tweepyとはTwitterのAPIをPythonで簡単に操作することができる素敵ライブラリです。

事前準備

  • Pythonインストール
  • tweepyインストール
  • Twitter APIキーとトークンの取得

Pythonインストール

Macの場合はデフォルトで入ってるとは思うのですが、バージョンが古かったりするので、念の為。
https://prog-8.com/docs/python-env
ここを参考にしてPythonをインストールしてください(本題じゃないので割愛)。

tweepyインストール

まずはtweepyインストールをインストールするためのpipをインストール。

ターミナル
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ python get-pip.py
$ pip -V
pip 21.1.3 from /Users/ユーザー名/.pyenv/versions/3.7.5/lib/python3.7/site-packages/pip (python 3.7)

tweepyインストール

ターミナル
$ pip install tweepy
(省略)
$ pip show tweepy
Name: tweepy
Version: 4.4.0
Summary: Twitter library for Python
Home-page: https://www.tweepy.org/
Author: Joshua Roesslein
Author-email: tweepy@googlegroups.com
License: MIT
Location: /Users/ユーザー名/.pyenv/versions/3.7.5/lib/python3.7/site-packages
Requires: requests-oauthlib, requests
Required-by: 

pip show tweepyでバージョンが表示されてたら成功です。

Twitter APIキーとトークンの取得

一番の難所。
https://dev.classmethod.jp/articles/twitter-api-approved-way/
こちらを参考にして取得。
デベロッパーツールの利用目的は「Exploring the API」を選択しました。
申請はすぐに通るとも限らないので気長に待ちましょう。
私はなぜか申請が通過したメールが来なかったのですが、https://developer.twitter.com/en/portal/dashboard にアクセスしたら通ってました(よくわからん)。
また、取得したAPIキーとトークンは紛失したり流失したりすることがないように慎重に扱いましょう。

大事そうなところ解説

コマンドラインから引数を受け取る。

tweet.py
arg = sys.argv[1]

タイムライン取得

config.pyの情報を使用してTwitterオブジェクトを生成し、生成したTwitterオブジェクトを使用して任意のアカウントのタイムラインを取得します。

tweet.py
        auth = tweepy.OAuthHandler(config.consumer_key, config.consumer_secret)
        auth.set_access_token(config.access_token, config.access_token_secret)
        api = tweepy.API(auth)

# tweepy.Cursorでオプションを指定して検索する。
        results = tweepy.Cursor(api.user_timeline, # タイムラインの取得
			id="ユーザー名", # 取得対象のユーザーを指定
			include_entities=True, # 省略されたリンクを全て取得
			tweet_mode='extended', # 省略されたツイートを全て取得
			lang='ja').items(n) # 取得件数を指定

ツイート文・画像・動画取得

result.extended_entities['media']でツイートに添付されているファイル情報を取得できます。
ツイート文はresult.full_textで取得できます。

tweet.py
for result in results:
            try: 
                media = result.extended_entities['media']
		full_text = result.full_text

ツイートに添付されているファイル情報は一つとは限らないのでforで回してコンテンツタイプを取得します。

tweet.py
for m in media:
                if m['type'] == 'video':
		elif m['type'] == 'photo':

動画ファイルのURLを取得します。Pythonの内包表記はぱっと見ややこしい......。
m['video_info']variantとしてforで回して、
variant['content_type'] == 'video/mp4'の時にvariant['url']を配列に格納してます。

tweet.py
origin = [variant['url'] for variant in m['video_info']
                            ['variants'] if variant['content_type'] == 'video/mp4'][0]

画像のURLは比較的簡単に取得できますね。

tweet.py
img_url = result.extended_entities['media'][0]

ファイルのダウンロード

urllib パッケージを使って取得したインターネット上のリソースを、新規ファイルとして指定した場所に書き込みます。

tweet.py
url = [variant['url'] for variant in m['video_info']
                            ['variants'] if variant['content_type'] == 'video/mp4'][0]
dst_path = 'ssr/{}.mp4'.format(file_name)
try:
            with urllib.request.urlopen(url) as web_file:
                data = web_file.read()
                with open(dst_path, mode='wb') as local_file:
                    local_file.write(data)
        except urllib.error.URLError as e:
            print(e)

ファイル名をツイート文から正規表現で抜き出す

事務員のはづきさんはありがたいことに定型文でツイートしてくれるので、そこからファイル名を正規表現で抜き出します。
https://twitter.com/imassc_official/status/1495639425052598272?s=20&t=OsnGTra__zI_wisubunKsw
欲しい文字列は【つよがりのためのララバイ】桑山 千雪

  1. ()で文字を1つのグループにまとめられるので、抜き出したい部分を()で囲う。
  2. 含まれていて欲しいは正規表現で意味のある文字なので、\ でエスケープする必要がある。
  3. .は任意の1文字、+?は1文字以上のどんな文字でも一致するため、アイドルさん登場時のに囲まれた【つよがりのためのララバイ】桑山 千雪が取得できる。
tweet.py
card_name = re.findall("アイドル(\【.+?)さん登場時の", full_text)[0]

全文ざっくり解説

tweet.py
import tweepy # もちろん必要
import config # トークンとかが記載されてるファイル
import os # ディレクトリにアクセスするために必要
import urllib.error # リソース取得のために必要
import urllib.request # リソース取得のエラーハンドリングに必要
import re # 文字列検索するために必要
import sys # コマンドライン引数を受け取るために必要

class GetContents():
    # 以下単語を含むツイートのコンテンツを取得する
    SSR_TEXT = '登場時の動画'
    EVENT_TEXT = 'イベントの予告動画'
    REPRINT_TEXT = '復刻'
    BIRTH_TEXT = '誕生日ミニコミュ'
    COMIC_TEXT = 'web4コマ漫画更新!'
    CARD_TEXT = 'アイドルをご紹介'
    USER = '@imassc_official'

    def __init__(self):
        self.arg = sys.argv[1] # コマンドライン引数を受け取る

    def get_serch(self):
        # Twitterオブジェクトの生成
        auth = tweepy.OAuthHandler(config.consumer_key, config.consumer_secret)
        auth.set_access_token(config.access_token, config.access_token_secret)
        api = tweepy.API(auth)

        # 検索
        return tweepy.Cursor(api.user_timeline, # タイムラインの取得
			id=self.USER, # 取得対象のユーザーを指定
			include_entities=True, # 省略されたリンクを全て取得
			tweet_mode='extended', # 省略されたツイートを全て取得
			lang='ja').items(self.num) # 取得件数を指定

    def get_file_name(self, str, full_text):
        # 正規表現で任意の文字列を抜き出す
        card_name = re.findall(str, full_text)
        return card_name[0]

    def download_file(self, url, dst_path):
        try: # URLからコンテンツをダウンロード
            with urllib.request.urlopen(url) as web_file:
                data = web_file.read()
                with open(dst_path, mode='wb') as local_file:
                    local_file.write(data)
        except urllib.error.URLError as e:
            print(e)

    def make_directory(self):
        # 格納先のディレクトリが無かったら作成
        if not os.path.exists('ssr'):
            os.mkdir('ssr')

        if not os.path.exists('event'):
            os.mkdir('event')

        if not os.path.exists('birth'):
            os.mkdir('birth')

        if not os.path.exists('comic'):
            os.mkdir('comic')

        if not os.path.exists('card'):
            os.mkdir('card')

    def get_file(self):
        # 引数が数値じゃなかったら処理を終了
        if self.arg.isdigit():
            self.num = int(self.arg)
            self.make_directory()
        else:
            print(sys.argv[1])
            print('数値を入力してください')
            return

	# 取得したタイムラインをforで回す
        for result in self.get_serch():
            try: # ツイートのメディアを取得してメディアの数だけ回す
                media = result.extended_entities['media']
                for m in media:
		    # コンテンツタイプが動画の時の処理
                    if m['type'] == 'video':
                        origin = [variant['url'] for variant in m['video_info']
                            ['variants'] if variant['content_type'] == 'video/mp4'][0]
			# result.full_textでツイート文を取得
                        if self.SSR_TEXT in result.full_text:
                            file_name = self.get_file_name("アイドル(\【.+?)さん登場時の",
                                                            result.full_text)
                            dst_path = 'ssr/{}.mp4'.format(file_name)
                        elif self.EVENT_TEXT in result.full_text:
                            if self.REPRINT_TEXT in result.full_text:
                                continue
                            file_name = self.get_file_name(
                                'シナリオイベント\「(.+?)\」が始まり', result.full_text)
                            dst_path = 'event/{}.mp4'.format(file_name)
                        elif self.BIRTH_TEXT in result.full_text:
                            file_name = self.get_file_name(
                                'メンバー\「(.+?)\」さん', result.full_text)
                            dst_path = 'birth/{}.mp4'.format(
                                file_name + str(result.created_at))
                        else:
                            continue
                        self.download_file(origin, dst_path)
			# ダウンロードしたファイル名を出力
                        print(file_name)
		    # コンテンツタイプが画像の時の処理
                    elif m['type'] == 'photo':
                        img_url = result.extended_entities['media'][0]['media_url']
                        if self.COMIC_TEXT in result.full_text:
                            file_name = self.get_file_name(
                                '(第.+?\』)', result.full_text)
                            dst_path = 'comic/{}.png'.format(file_name)
                        elif self.CARD_TEXT in result.full_text:
                            full_text = ''.join(result.full_text.splitlines())
                            file_name = self.get_file_name(
                                'アイドル(\【.+?)\#シャニマス', full_text)
                            dst_path = 'card/{}.png'.format(file_name)
                        else:
                            continue
                        self.download_file(img_url, dst_path)
			print(file_name)
                    else:
                        continue
            except:
                pass


get_contents = GetContents()
get_contents.get_file()

感想

コードを書いてるときは自分が個人的に楽しむだけだったので気楽に書いてたんですけど、いざ記事として形にしようとするとすごく大変だなあと思いました......。
特に正規表現の部分とかあんまり良くわかってないので正規表現に自信ニキネキがいらっしゃいましたら是非優しくご教授していただきたいです......。
あと本当はシャニマスはめちゃくちゃ動くし絵が綺麗でアイドルたちも魅力的なんだぞ〜〜!って布教したいんですけど、権利的によくなさそうなので控えます。

参考

https://developer.twitter.com/ja/docs
https://dev.classmethod.jp/articles/twitter-api-approved-way/
https://prog-8.com/docs/python-env
https://qiita.com/soma_sekimoto/items/65c664f00573284b0b74
https://murashun.jp/article/programming/regular-expression.html

Discussion