はじめに
追記: 今は使えない方法です。
Rubyを使って検索しました。明らかに最善じゃないところがありますが、メモってことでお願いします。
扱いやすさのためClass Tsitter
を作ります。名前は適当です。
- initialize(username, password)
- get_rate_limit_status(resource_list)
- search_tweet(search_word, count:40)
initialize(username, password)
について
インスタンス生成の際にログインしてapiを使える状態にします。
インスタンス変数を初期化
@hash_cookie = {'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'} @hash_header = {}
Cookie記録用とリクエストヘッダに使うためのハッシュです。
/login
を見てログインフォームを確認する
# host = 'twitter.com', port = 443 https_twitter = Net::HTTP.new('twitter.com', 443) https_twitter.use_ssl = true https_twitter.verify_mode = OpenSSL::SSL::VERIFY_PEER # リダイレクト先を設定してloginにアクセス path = '/login' query = 'hide_message=true&redirect_after_login=https%3A%2F%2Ftweetdeck.twitter.com%2F%3Fvia_twitter_login%3Dtrue' response_login = https_twitter.start { |https| https.get("#{path}?#{query}") } # cookieを登録 self.register_cookie(response_login) # form欄にあるauthenticity_tokenを取得 response_login.body.force_encoding('utf-8').match(/<input type="hidden" value="([^"]*)" name="authenticity_token">/) authenticity_token = $1
フォームは次のような形になっていて、この中にpostすべき情報が書かれています。
<form action="https://twitter.com/sessions" class="LoginForm js-front-signin" method="post" data-component="dialog" data-element="login" > ... </form>
/sessions
にpost
# formに入力してpost path = '/sessions' response_sessions = https_twitter.start { |https| request = Net::HTTP::Post.new(path, @hash_header) request.set_form_data( { 'session[username_or_email]' => username, 'session[password]' => password, 'remember_me' => '0', 'return_to_ssl' => 'true', 'redirect_after_login' => 'https://tweetdeck.twitter.com/?via_twitter_login=true', 'authenticity_token' => authenticity_token } ) https.request(request) } # cookieを登録 self.register_cookie(response_sessions)
tweetdeckに戻る
# host = 'tweetdeck.twitter.com', port = 443 https_tweetdeck = Net::HTTP.new('tweetdeck.twitter.com', 443) https_tweetdeck.use_ssl = true https_tweetdeck.verify_mode = OpenSSL::SSL::VERIFY_PEER path = '/' query = 'via_twitter_login=true' response_tweetdeck = https_tweetdeck.start { |https| request = Net::HTTP::Get.new("#{path}?#{query}", @hash_header) https.request(request) }
bundle...js
からbearer_tokenを取得
# host = 'ton.twimg.com', port = 443 https_ton = Net::HTTP.new('ton.twimg.com', 443) https_ton.use_ssl = true https_ton.verify_mode = OpenSSL::SSL::VERIFY_PEER # urlを取得 url_bundle = response_tweetdeck.body.force_encoding('utf-8').match(/<script src=([^<>]*)><\/script>\z/)[1] uri_bundle = URI.split(url_bundle) path = uri_bundle[5] response_bundle = https_ton.start { |https| https.get(path) } # bearer_tokenを取得 bearer_token = response_bundle.body.force_encoding('utf-8').match(/bearer_token:"([^"]*)"/)[1]
どうも固定らしいのでいちいち調べる必要はないですね。
ヘッダーを編集する
@hash_header['Authorization'] = "Bearer #{bearer_token}" @hash_header['X-Csrf-Token'] = @hash_cookie['ct0'].split('=')[1] @hash_header['X-Twitter-Auth-Type'] = 'OAuth2Session' @hash_header['X-Twitter-Client-Version'] = 'Twitter-TweetDeck-blackbird-chrome/4.0.190220122730 web/'
X-Csrf-Token
はCookieのct0
と同じ値である必要があります。
以上でapiを利用できるようになります。
接続する
# host = 'api.twitter.com', port = 443 @https_api = Net::HTTP.new('api.twitter.com', 443) @https_api.use_ssl = true @https_api.verify_mode = OpenSSL::SSL::VERIFY_PEER
get_rate_limit_status(resource_list)
について
apiの制限までの残り回数やリセットされる時刻などを得られます。
def get_rate_limit_status(resource_list) path = "/1.1/application/rate_limit_status.json" query = "resources=#{resource_list.join(',')}" response_rate_limit_status = @@https_api.start { |https| request = Net::HTTP::Get.new("#{path}?#{query}", @hash_header) https.request(request) } return response_rate_limit_status end
rate_limit_status
を取得するのにもapi制限があるので使いすぎないよう注意します。'rate_limit_context' => 'resources' => 'search' => '/search/universal'
といった具合に辿り、'remaining'
や'reset'
などで欲しいパラメータを得られます。
search_tweet(search_word, count:40)
について
path = '/1.1/search/universal.json' query = URI.escape("q=#{search_word}&count=#{count}&modules=status&result_type=recent&pc=false&ui_lang=ja&cards_platform=Web-13&include_entities=1&include_user_entities=1&include_cards=1&send_error_codes=1&tweet_mode=extended&include_ext_alt_text=true&include_reply_count=true") response_search = @https_api.start { |https| request = Net::HTTP::Get.new("#{path}?#{query}", @hash_header) https.request(request) } return response_search
検索クエリにsince_id
やmax_id
を使うことで検索範囲をツイート単位で指定できます。
応答
JSON形式でハッシュが返ってきます。内容は大体次のような感じで取り出せます。
hash_search_result = JSON.parse(response_search.body.force_encoding('utf-8')) # modulesに各ツイートの内容が配列で格納されている modules = hash_search_result['modules'].each { |hash_module| full_text = hash_module['status']['data']['full_text'] screen_name = hash_module['status']['data']['user']['screen_name'] id_str = hash_module['status']['data']['id_str'] url = "https://twitter.com/#{screen_name}/status/#{id_str}" # Wed Feb 20 04:09:32 +0000 2019 # 曜日 月 日 時:分:秒 +0000 年 hash_module['status']['data']['created_at'].match(/^(...) (...) (..) (..):(..):(..) (\+....) (....)$/) time = Time.gm($8, $2, $3, $4, $5, $6, 0).localtime('+09:00') # ######### # # 適当な処理 # # ######### # }
api制限
レスポンスヘッダにapi制限についての情報も付いてきます。
# 残り回数 response_search_result.get_fields('x-rate-limit-remaining') # 残り回数がリセットされる時刻 response_search_result.get_fields('x-rate-limit-reset')
エラーなど
2種類ほどエラーが出ました。独立して起こっていた気がします。
response_search_result
の内容(つまりhash_search_result
)が{ "errors" => ...}
のような感じでキーにmodules
がない- レスポンスヘッダに
'x-rate-limit-remaining'
や'x-rate-limit-reset'
がない
最後に
search_tweet
は毎回セッションを閉じる形になるので、keep-alive
な通信ではないみたいですね。今回は時間がなかったのでsearch_tweet
をたくさん回してしまいました。また機会があれば、負荷をかけない形でやりたいです。