Twitterの投稿やプロフィール情報を自動で取得するには公式のAPIが必要になってきます。 しかし2023年現在APIの規制は非常に厳しいものになり、利用審査がある上に一か月に取得できるツイート数はかなり絞られており、もはや使い物にならないものとなってしまいました。
これまでにAPIを使わずに投稿が自動取得できるようなPythonのライブラリはあったのですが、いずれも2023年の4月頃をもって使えなくなってしまいました。
そこで、今回投稿を自動取得する裏技みたいなのを見つけたので共有したいと思います。
Twitterのスクレイピングに関する規約
Twitterを含む多くのSNSがWeb上で利用することが可能です。つまりHTMLを使って情報を提供してくれているわけですが、HTMLを解析して特定の要素だけ抜いたりして自動的にデータとして保存する「スクレイピング」という技術を用いて情報を取得することも可能になってくるわけです。
しかし多くのSNSではこの「スクレイピング」という行為を禁止しており、実際にInstagramでは業界最高レベルのスクレイピング対策が施されていてAPIを利用しないとコンスタントに投稿情報などを取得できないです。
同様にしてTwitterでも利用規約ではAPIを介さないスクレイピング行為が禁止されており、自動的に投稿を取得することは利用規約違反となります。とはいえ何か罪に問われるというような記述はないですね。
ただし、独自にAPIを作って有料プランも作っていたりするのにはそれだけの訳があるのですから、無料で勝手にスクレイピング行為を行うことは決して良いことではありません。 そして、私は別にスクレイピング行為を助長しているわけではありませんので、そこのところご理解お願いします。
人間くらいのアクセス頻度でそこまでサーバーに負荷を与えないことを意識すれば特筆、Twitterに迷惑をかけるということはないので、ちょっとだけ試験的に取得プログラムを作ってみます。
PythonとPHPを使ってツイートを自動取得してみる
それでは早速、私がちょっと見つけた方法を紹介していきたいと思います。 プロフィール情報やハッシュタグでの検索結果などを取得することはできませんのでご注意ください。
あくまでも特定のアカウントのツイートを取得できるプログラムです。
タイムラインを表示するPHPを作成
絶対もうちょっと簡単に実装する方法があると思うのですが、今回はWordPress(Webサイト)環境をいじっているときに思いついたので、PHPテンプレートを使う方法を実践していきます。
<?php
$id = $_GET['user'];
echo '<a class="twitter-timeline" data-tweet-limit="5" href="https://twitter.com/'.$id.'">Tweets by</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>';
ちゃんとheadとかbodyとかのタグを入れろよって感じかもしれないですが、もはやこれでも大丈夫でしょう。 非常にシンプルなテンプレートで、URLパラメータからアカウントのIDを取得してそれを使ってTwitterが用意しているタイムラインウィジェットを呼び出すというようなことをしています。
このタイムラインウィジェットは別にAPI v1とかで動いているわけではないのでおそらく2023年の5月以降になってもしっかり動作すると思いますし、タイムラインを埋め込めなくなることはおそらくないと思いますので、当分の間はこの方法でいけそうです。
Pythonでスクレイピング
ここからがっつりスクレイピングをしていきます。今回使うのはPythonで動くseleniumです。あとはGoogle Chromeとそのドライバーですね。
Google Chromeの導入とChromedriverの導入に関しては各自お願いします。
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
from urllib.parse import urljoin
import datetime
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import random
import re
options = webdriver.ChromeOptions()
w = 1696
h = 1280
#options.add_argument('--single-process')
options.add_argument('--disable-dev-shm-usage')
options.add_argument("--headless")
options.add_argument("--window-size=1280x1696")
options.add_argument("--disable-infobars")
options.add_argument("--no-sandbox")
options.add_argument("--hide-scrollbars")
options.add_argument("--enable-logging")
options.add_argument("--log-level=0")
options.add_argument("--single-process")
options.add_argument("--ignore-certificate-errors")
options.add_argument("--homedir=/tmp")
options.add_argument('--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/111.0.3497.81 Safari/537.36')
#ブラウザを起動する関数
def start_browser():
#空のブラウザを起動
global browser
browser = webdriver.Chrome(
# chromedriverのパスを指定
executable_path="./chromedriver",
options=options
)
browser.set_window_size(w,h)
#ブラウザを終了する関数
def end_browser():
browser.close()
browser.quit()
def sc_twitter(user_id):
browser.get(f"twitter.php?user={user_id}")
time.sleep(random.randint(2,5))
a = browser.find_elements(By.CSS_SELECTOR,'iframe#twitter-widget-0')[0]
browser.switch_to.frame(a)
tweet_list = browser.find_elements(By.XPATH,'//html//section//article')
tweet_array = []
for each_tweet in tweet_list:
post_data_set = {}
content = each_tweet.find_elements(By.XPATH,'div//div//div//div//div[@data-testid="tweetText"]')[0].text
times = each_tweet.find_elements(By.XPATH,'div//div//div//div//div//div//div//div//div//div//div//time')[0].get_attribute("datetime")
data_date = datetime.datetime.strptime(times,"%Y-%m-%dT%H:%M:%S.%fZ") + datetime.timedelta(hours=9)
each_tweet.click()
browser.switch_to.window(browser.window_handles[-1])
url = browser.current_url.split('?')[0]
browser.close()
browser.switch_to.window(browser.window_handles[0])
browser.switch_to.frame(a)
post_data_set.update({"data_date":data_date,"url":url,"data_content":content,"data_title":content})
img_list = each_tweet.find_elements(By.XPATH,'div//div//div//div//div//div//div//div//div//div//div//img')
for i in range(len(img_list)):
if re.search("/media/",img_list[i].get_attribute("src")):
image = img_list[i].get_attribute("src")
post_data_set.update({"image_url":image})
break
tweet_array.append(post_data_set)
print(post_data_set)
return tweet_array
#ブラウザを起動
start_browser()
#ツイートを取得
tw_data = sc_twitter(user_id)
#ブラウザを閉じる
end_browser()
こんな感じになりました。seleniumの扱いに慣れていないのでいろいろ雑になってしまったのですが、現状ではこれで動くことが確認できています。なお、もしもデザインが変更されたらXPATHのところを変更しないといけなくなったりすると思うので注意が必要ですね。
画像に関しては投稿内にあれば最初の画像のURLを取得するといった感じ。
そしてツイート自体のURLに関してはもっと他に良い方法がありそうですが、ここではツイート自体をクリックさせて確認しています。
このようにツイートを取得している人は他にいないかとざっと探してみたところいなかった気がしたので記事を作ってみた次第です。悪用しないように気を付けてください。