Python

【twitterAPI1.1】pythonでアンケートデータを取得する

アンケートに関するAPIは用意されていない。

twitteAPIを使ってアンケート(投票)のツイートを取得したいと思い、ドキュメントを参照してみたが、アンケートに関するAPIが見当たらない。

結局はアンケートに関するAPIは用意されていないとのこと。。

とはいえ、何とかしてtwitterからアンケート情報を取得する術がないか色々探し回った結果、スマートではないがアンケートデータを取得できることが確認できた。

アンケートの取得の仕方

url = "https://api.twitter.com/1.1/search/tweets.json"
params = {
    'q':'card_name:poll2choice_text_only'
    ,'count':20
    ,'lang':'ja'

}
req = twitter.get(url, params = params)

使用しているAPIのエンドポイントは単純なツイートを取得するapi.twitter.com/1.1/search/tweets.jsonです。

重要なのはパラメータの’q’です。

上記では、card_name:poll2choice_text_onlyを使用していますがこれは2択のアンケートを取得することができます。

poll2choiceの数字の箇所が何択のアンケートを取得するかを意味します。

例えば2択と4択のアンケートのみを取得したい場合は以下のようにします。

‘q’:’card_name:poll2choice_text_only or card_name:poll4choice_text_only’

これで、好きなようにアンケート情報がAPI経由で取得できるようになります。

ただ、これでもまだ問題があります

それは、このAPIを使っても取れる情報はアンケートのタイトルのみで重要な選択肢の情報や投票数は取得することができません

以下の方法では何とか取得することができますが、冒頭で記載した通り、今回の取得方法がスマートではない由縁になります。

アンケートの選択肢を取得する

APIのリクエストでは選択肢は取得できませんが、対象ツイートのURLは取得することができます。

なので、ここから先は仕方ありませんが、BeautifulSoup等のhtml解析で選択肢を取得しましょう。

しかしながら、またしても、ここで問題が発生します。BeautifulSoup単体では静的なhtmlの解析は可能だが動的なhtmlページの解析はできません。

twitterはjavascriptにより動的にhtmlを生成しているためBeautifulSoupだけでは対応できません。

となると、最後の砦はseleniumです。seleniumは人がwebページを操作する動作を実現することができます。

最終的には以下のような構成が組めればtwitterアンケートの選択肢等を取得することが可能です。

  1. seleniumで対象ページにアクセス
  2. 対象ページのhtml生成が終わるまで待機
  3. htmlが生成後そのhtml情報をBeautifulSoupで解析
  4. 選択肢や回答数が取得可能になる

以上を盛り込んだサンプルのソースコードです。

import json
from requests_oauthlib import OAuth1Session
import requests
from bs4 import BeautifulSoup
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.keys import Keys
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import chromedriver_binary
import time
import re


CK = '<twitterのカスタマーキー>'
CS = '<twitterのカスタマーシークレット>'
AT = '<twitterのトークンキー>'
ATS = '<twitterのトークンシークレット>'

twitter = OAuth1Session(CK, CS, AT, ATS)




driver = webdriver.Chrome()

#seleniumで使用するヘッドレスブラウザの設定
options = webdriver.ChromeOptions()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)


#twitterAPIのエンドポイント
url = "https://api.twitter.com/1.1/search/tweets.json"
params = {
    'q':'card_name:poll2choice_text_only'
    ,'count':20
    ,'lang':'ja'

}
req = twitter.get(url, params = params)



if req.status_code == 200:
    
    res = json.loads(req.text)
    for i in range(len(res['statuses'])):
        try:
            screen_name = res['statuses'][i]['user']['screen_name'] #投稿者のユーザ名
            id_str = res['statuses'][i]['id_str'] #ページid
            theme = res['statuses'][i]['text'] #アンケートタイトル
            
       #リツイートによる投稿かどうかの判定
            if theme[0:2] == 'RT':
         
                continue

       #対象アンケートのページ
            url = "https://twitter.com/{0}/status/{1}" .format(screen_name,id_str)

            driver.get(url)

       #articleタグが読み込まれるまで待機
            WebDriverWait(driver, 15).until(EC.visibility_of_element_located((By.TAG_NAME, 'article')))
            html = driver.page_source.encode('utf-8')
            soup = BeautifulSoup(html, "html.parser")
       
            #選択肢1
            question1 = soup.select(".css-18t94o4.css-1dbjc4n.r-1niwhzg.r-p1n3y5.r-sdzlij.r-1phboty.r-rs99b7.r-16y2uox.r-1w2pmg.r-1vsu8ta.r-aj3cln.r-1ny4l3l.r-1fneopy.r-o7ynqc.r-6416eg.r-lrvibr")[0].text

            #選択肢2
            question2 = soup.select(".css-18t94o4.css-1dbjc4n.r-1niwhzg.r-p1n3y5.r-sdzlij.r-1phboty.r-rs99b7.r-16y2uox.r-1w2pmg.r-1vsu8ta.r-aj3cln.r-1ny4l3l.r-1fneopy.r-o7ynqc.r-6416eg.r-lrvibr").text

            #総投票数
            allvote = soup.select(".css-1dbjc4n.r-1d09ksm.r-xoduu5.r-18u37iz.r-1wbh5a2")[0].text
            allvote = int(re.sub("\\D", "", allvote[0:3]))


            
        except:
            pass


else:
    res=""
    print("Failed: %d" % req.status_code)

driver.quit()

上記ソースコードでは総投票数は取得できますが各選択肢に何票投稿されているかは取得できません。

実際にtwitterのアンケートページを見てもらうと分かりやすいですが、web上のデータから各選択肢の投票数を取得するには対象のアンケートの回答期限を迎えていなければいけません。

回答期限を迎えている場合以下のような感じで、投票数も取得することができます。

ソースコード内で使用しているselectのクラスは回答期限を迎えているページにしか登場しません。

#選択肢1        
num = soup.select(".css-1dbjc4n.r-1awozwy.r-18u37iz.r-1g7fiml.r-1wtj0ep")[0].text

#選択肢2
num2 = soup.select(".css-1dbjc4n.r-1awozwy.r-18u37iz.r-1g7fiml.r-1wtj0ep").text

 

せっかく、seleniumも使用しているので、回答期限を迎えていない場合は適当にどれかをseleniumでクリックさせることで回答済とり、各選択肢の回答数を取得することも可能です。(※ただし、ポリシー的にはグレーかと。。)

 

今回のソースコードを参考に色々手を加えればもっとスマートに実現することも十分に可能かと思います。

最後に🙇‍♂️

今回の機能を実際に実装して作ったサービスが以下になります。

一度、ご利用いただければと思います。

どっちらいく

あなたはどっちを選ぶ?どっちらいくとは? どっちらいく どっちらいくとはユーザが投稿した2択のテーマ(お題)に関してどっちが 好きか、良いか、イケてる...

今回の機能について等々、それ以外のことでもいつでもご質問お待ちしております!