Pythonで、Twitterをさわってみよう第三弾です。
今回は、検索条件に合うツイートのメディアを収集する方法です。
基本的に、「いいね」とやることは同じです。
最初に残念なお知らせがあります。
この方法はエ◯画像を収集できません!!!(理由は後述)
準備
TwitterのDeveloperアカウントを申請し、APIデータ操作に必要なキーを入手する必要があります。
詳しくは、前回の「PythonでTwitterをさわってみる」をご参照ください。
実装
前準備
API操作に必要な4つのキーを、変数に入れておきます。
1 2 3 4 |
CONSUMER_KEY = " " CONSUMER_SECRET = " " ACCESS_TOKEN = " " ACCESS_TOKEN_SECRET = " " |
ツイートの検索
「ツイートの検索」と「画像収集」をモジュール化して実装します。
最初に検索部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from requests_oauthlib import OAuth1Session from time import sleep import urllib.request as req import json, os #4つのキーをセット CK = CONSUMER_KEY CS = CONSUMER_SECRET AT = ACCESS_TOKEN ATS = ACCESS_TOKEN_SECRET # 対象ツイートを検索 def search_tweet(search_word): url = "https://api.twitter.com/1.1/search/tweets.json" twitter = OAuth1Session(CK, CS, AT, ATS) req = twitter.get(url, params = search_word) if req.status_code == 200: tweet = json.loads(req.text) search_tweet = json.loads(req.text) for tweet in search_tweet['statuses']: #print(tweet['user']['screen_name'] + "(" + tweet['user']['name'] + ") > " + tweet['text']) print("(tweet id = " + tweet['id_str'] + ")") print("-"*10) tweet_id = tweet['id'] return search_tweet, tweet_id else: print("ERROR: %d" % req.status_code) |
ツイート検索のAPI説明はこちら(Twitter公式)。
レスポンスボディ(JSON)と、ボディ中のツイートIDを返しています。
画像収集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 画像を保存 def save_img(tweet_json, save_dir): if not os.path.exists(save_dir): os.makedirs(save_dir) for tweet in tweet_json['statuses']: name = tweet['user']['screen_name'] date = tweet['created_at'] date = date.replace(" +0000","") date = date.replace(" ","-") date = date.replace(":",",") try: media_list = tweet['entities']['media'] for img in media_list: img_url = img['media_url'] path = save_dir + "/[" + str(name) + "]_" + str(date) + ".jpg" print(path) req.urlretrieve(img_url, path) print("画像を保存しました", img_url) print("-・"*30) except Exception as e: print("画像を取得できませんでした") #print(tweet_json) print("-・"*30) |
レスポンスボディ中の media_url
から画像を保管しています(17行目)。
保存するときのファイル名は、Twitterユーザー名と投稿日からとることにしました。
全体の構造
全体は以下のようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# myTrade画像収集 #-------------------------------------------------------------------------------------------------------------------------------------- from requests_oauthlib import OAuth1Session from time import sleep import urllib.request as req import json, os #4つのキーをセット CK = CONSUMER_KEY CS = CONSUMER_SECRET AT = ACCESS_TOKEN ATS = ACCESS_TOKEN_SECRET # 対象ツイートを検索 def search_tweet(search_word): url = "https://api.twitter.com/1.1/search/tweets.json" twitter = OAuth1Session(CK, CS, AT, ATS) req = twitter.get(url, params = search_word) if req.status_code == 200: tweet = json.loads(req.text) search_tweet = json.loads(req.text) for tweet in search_tweet['statuses']: #print(tweet['user']['screen_name'] + "(" + tweet['user']['name'] + ") > " + tweet['text']) print("(tweet id = " + tweet['id_str'] + ")") print("-"*10) tweet_id = tweet['id'] return search_tweet, tweet_id else: print("ERROR: %d" % req.status_code) # 画像を保存 def save_img(tweet_json, save_dir): if not os.path.exists(save_dir): os.makedirs(save_dir) for tweet in tweet_json['statuses']: name = tweet['user']['screen_name'] date = tweet['created_at'] date = date.replace(" +0000","") date = date.replace(" ","-") date = date.replace(":",",") try: media_list = tweet['entities']['media'] for img in media_list: img_url = img['media_url'] path = save_dir + "/[" + str(name) + "]_" + str(date) + ".jpg" print(path) req.urlretrieve(img_url, path) print("画像を保存しました", img_url) print("-・"*30) except Exception as e: print("画像を取得できませんでした") #print(tweet_json) print("-・"*30) if __name__ == '__main__': save_dir = "./mytrade" #保存先 count = 3 tweet_id = 9999999999999999999 for i in range(count): #print(tweet_id) search_word = {'q' : "#myTrade filter:images -RT", #検索文字列 'count': 1, 'lang' : 'ja', 'result_type' : 'recent', 'max_id' : str(tweet_id-1), 'include_entitiies' : True } tweet_json, tweet_id = search_tweet(search_word) save_img(tweet_json, save_dir) if not i==count-1: sleep(10) |
上の例では、「#myTradeのハッシュタグを付けた、画像つきツイート」の高度な検索を行っています。この部分ですね↓
1 |
"#myTrade filter:images -RT" |
filter:images
がなくても実行可能ですが、無駄な検索をなくすため、そもそもツイートに画像が添付されたツイートに絞って検索しています。
ちなみにmyTradeとは、株式の運用履歴を管理する個人投資家向けアプリです。資産履歴をシェアできる機能があり、上記の検索ワードで検索すると、多くの投資家の結果を見ることができます。
こんな感じ↓
多少回復したけど、今週はマイナス><#myTrade pic.twitter.com/LW4qcKUBts
— Gukou (@Gukou4) 2018年10月12日
myTradeは別ブログでも紹介していますので、よろしければご参照ください。
実行結果
無事 ツイートに添付された画像を収集できました。
収集した画像を使って何ができないか画策中。縦軸に数値が記載されておらず、画像から得られる情報はほとんどありません。「自分と投資スタイルが似た人を探す」くらいはできるかな?
猫の画像を収集したり、特定の投稿者からのツイートに添付されたメディアの収集に使えるでしょう。
是非活用してみてください!!
本編はここまで!あとはおまけです。
収集できない画像もある
アダルト画像は収集できません(血涙)! そもそもAPIからレスポンスボディが返されないのです。
Twiiter側でアダルト画像を自動判定する機能が備わっているようです。(私のような)悪用を防ぐためでしょう。
以下の画像で試してみましたが、これも駄目でしたw うーん、Twitterの画像判定は手厳しい。
一瞬ドキッとしたわw pic.twitter.com/axlXNYipEF
— フィフィ (@FIFI_Egypt) 2016年1月15日
レスポンスボディに media_url が含まれない
色々 検討したところ、画像が添付されているにも関わらず、レスポンスボディに media_urlが存在しない場合があることがわかりました。
考えられる理由は2つ。
※管理人の検証結果を元に推察したもので、Twiiter公式から明言されているわけではありません
(1)「ツイートする画像にセンシティブな内容を含む」設定
Twitterの設定に、「ツイートする画像/動画をセンシティブな内容を含むものとして設定する」があります。
これにチェックを入れており、かつ閲覧設定の「センシティブな内容を含む可能性のある画像/動画を表示する」がOFFになっている場合、以下のように閲覧者の判断を仰ぐ形になります。
どうやら「ツイートする画像にセンシティブな内容を含む」にチェックが入っている場合は、media_urlが返されないようです。
さらに、該当するユーザーのツイートを丸々コピペして 別ユーザーが投稿しても、media_urlが返されないようです。コピペする側のユーザーが、センシティブ設定していない場合でも駄目。
複垢での悪用を防ぐための措置と思われます。
(2)ユーザースコア(?)
Twitter公式からは 一切明言されておりませんが、内部的にユーザースコアが存在すると思われます。
検証の結果、
- ツイート全てで media_url が返されないユーザー
- ツイートによっては、media_urlが返されないユーザー
が存在することがわかりました。
・・・
前者の典型例は@fanza_sns。ご存知DMM.R18を前身とした、アダルト事業を運営するFANZAの公式Twitterです。
@fanza_sns によって投稿されるメディアには、センシティブな画像の警告が出ない ことから、fanza自身では設定していないのでしょう。
にも関わらず、@fanza_snsのツイートには(私が確認した限り)全てで media_url が含まれません。
さらに、fanzaのツイートをコピペした場合も同様にNGでした。これも複垢規制を目的としてでしょう。
神咲詩織(@kamisaki_shiori)さんのツーショット撮影会、無事終了〜!!いよいよこの取り組みも残すところあと二回。来週28日は波多野結衣さん、31日は橋本ありなさんが来店!!(31日は撮影時間が変更になる可能性がございます)皆さん最後まで是非ともよろしくお願いします。 pic.twitter.com/S9u9NkVE4D
— Gukou (@Gukou4) 2018年10月24日
↑私が投稿しても、コピペはNG
続いて、ツイートによっては、media_urlが返されないユーザー です。基準は一切不明。過去のツイート内容,投稿するメディアを元に決定している?
こちらに該当するユーザーのツイートを、私がコピペしてツイートした場合は、media_urlが返されます。
このことから、ユーザースコア + ツイート内容 を元に、media_urlの有無が決まる と考えられます。
「このユーザーは、センシティブな内容をツイートを投稿する可能性が高い」と、Twitter側が判断し、大丈夫そうなツイートのみ レスポンスボディでmedia_urlを提供している?
大丈夫そうな の基準は一切不明です…。機会があれば検証してみます!
ちなみにトランプ大統領が、これに該当します。
Just leaving Wisconsin. @ScottWalker and @LeahVukmir are fantastic people, badly needed for our Country! #MAGA pic.twitter.com/6dLOBO6UQy
— Donald J. Trump (@realDonaldTrump) 2018年10月25日
↑これはOKで
Today’s Democrat Party would rather protect criminal aliens than AMERICAN CITIZENS – which is why the Democrats must be voted OUT of OFFICE! #JobsNotMobs https://t.co/0pWiwCq4MH pic.twitter.com/axUEOzBAsZ
— Donald J. Trump (@realDonaldTrump) 2018年10月23日
↑これはNGでした
基準がわからない・・・w
センシティブな内容とは
Twitter公式からは、「センシティブな内容」について一切説明はありません。以前は「不適切な内容」という表現でした。
明確な説明をあえて避けていると思われます。Twitterの独断で決定できるということですね。
アダルト画像はもちろん、差別的・暴力的なツイート内容、連続して同内容をツイートする(所謂bot)等 が該当すると推察します。
検証のために、アダルティックな画像を投稿したり、同じ画像を繰り返しツイートしてしまいました。今の所、私のツイートには media_urlが含まれていますので、まだユーザースコアは良好のようですw
気になる方は、自分のIDで試してみてはいかがでしょうか?
再度注意!あくまで管理人の推察です。ユーザースコア云々はTwitter公式からは 全く明言されておりません。