Seleniumを使ってCentOS上でTwitterのbookmarkを取得する


Twitterがbookmark周りのAPIを提供していないため、Seleniumを使用して全ブックマークを取得してみた。

環境
CentOS Linux release 7.7.1908
Python 3.6.8

準備編

必要なもののinstall

google-chrome

この記事を参考にinstallをする。

ChromeDriver

installするversionに注意が必要。
不用意に入れると正常に動作しない。
ChromeDriverのサイトを確認し、version指定付きでpip install。

# google-chrome --version
Google Chrome 78.0.3904.108

# pip install chromedriver-binary==78.0.3904.105
# pip show chromedriver-binary
Name: chromedriver-binary
Version: 78.0.3904.105.0

# chromedriver-path
/usr/lib/python3.6/site-packages/chromedriver_binary (後に必要)

Selenium

# pip install selenium

動作確認

大量にoptionを追加しているが、--headless--no-sandboxくらいで十分かも。
自分の環境では--headlessなしでは例外が発生した。
executable_pathは上記のchromedriver-pathの結果を指定。
確認用としてscreenshotを保存している。

test.py
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys

options = Options()
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-infobars')
options.add_argument('--disable-extensions')
options.add_argument('--disable-gpu')
options.add_argument('--headless')

driver = webdriver.Chrome(chrome_options=options, executable_path='/usr/lib/python3.6/site-packages/chromedriver_binary/chromedriver')

driver.get('https://www.google.com/')
time.sleep(3.0)
driver.save_screenshot('screenshot.png')

driver.close()
driver.quit()

bookmark取得編

ログイン処理

option.add_argument('--user-data-dir='+os.path.abspath('profile'))

上記optionにより使用profileを指定するとcookieがそこに保存されるため、プログラムの実行毎にログインしなくて良くなる。
その状態で、この記事のtwitterloginを一度だけ実行しておく。
ログインに成功したと思ってもメールアドレスの確認ページで止まっていることもあるので、interactive modeを使用してスクショやURLを確認しながら実行すると良い気がする。

取得処理

Twitterのタイムラインやブックマークなどは、スクロールに応じて動的にツイートの要素が追加・削除されている。
以下のプログラムでは
読み込まれているツイートのurl取得→一番下のツイートがページ上部に来るようスクロール→ページがツイートを読み込むのを待機
をひたすら繰り返すことで全ツイートのurlを取得している。

def get_list():
    driver.get('https://twitter.com/i/bookmarks')
    time.sleep(10.0)

    status_urls = []
    container_xpath = '//*[@id="react-root"]/div/div/div/main/div/div/div/div[1]/div/div[2]/section/div/div/div'
    container = driver.find_element_by_xpath(container_xpath) # 複数のツイートが含まれる縦長の要素
    end_count = 0
    while True:
        divs = container.find_elements_by_xpath('./div')
        for div in divs:
            if len(div.find_elements_by_tag_name('img')) == 0:
                end_count += 1
                break
            status_url = div.find_element_by_xpath('./div/article/div/div[2]/div[2]/div[1]/div[1]/a').get_attribute('href')
            status_urls.append(status_url)
        if end_count > 8:
            break
        driver.execute_script('arguments[0].scrollIntoView();', divs[-1])  # must check length
        print(len(status_urls))
        time.sleep(15.0)

    return list(set(status_urls))  # 取得方法上重複が生じるので、一度setにすることでuniqueにしている

ブックマークを限界まで遡った時、一番下の要素にはツイートが格納されていないため、div.find_elements_by_tag_name('img')によって終端までスクロールしたかを判定可。
時間はいくらかかってもいいので全取得したい、というノリで書いているのでやたらとsleepしたり回数指定したりで冗長なコードになっている。

まとめ

  • ChromeDriverのinstall時にはしっかりversionチェックしとかないと沼にハマる
  • Seleniumを使ってページを読み込めば、普段ブラウザでやっているように値を入力したりクリックしたりできて非常に便利。
  • HTMLのDOM構造が変わって要素にアクセスできなくなる事があるので注意が必要。

間違ってるとこなど見つけましたらコメント下さると嬉しいです。

参考にさせていただいたサイト

CentOS7でSelenium+Pythonを動かすまで - Qiita
Seleniumで次の実行時にもサイトのログイン状態を維持したい場合
Python Seleniumでtwitterのログインからリプライするbot - Qiita