Edited at

TwitterをAPIを使わずに力ずくでbot化する

TwitterのAPIの制限が厳しくなりましたねー。私も以前はTwitterのAPIで気軽に遊んでいたのですが、APIの使用に申請が必要になったりして(しかし遊びで使うという内容で300文字も書けるわけもなく)困っていました。そこで、APIを使わずに自動でツイートする方法を考えました。

で、まず初めに利用規約を読んでみました。利用開始するときに誰もが読む長文ですね。読まずに機械的に同意を押している向きもあると思いますが、それは本当にやめたほうがいいです。

関係がありそうなところを抜き出すと以下です。


(iii)Twitterから提供される(かつ該当する利用条件に従う場合にのみ提供される)、当社の現在利用可能な公開インターフェース以外の方法(自動プログラムか否かを問わない)で、本サービスへのアクセスもしくはその探索またはアクセスもしくは探索の試み(ただし、Twitterとの個別契約で特に許可されている場合は除く)(注:本サービスへのクローリングは、robots.txtファイルの定めによる場合は認められていますが、Twitterによる事前の同意がないまま本サービスのスクレイピングをすることは明示的に禁止されています)、

...


ということで、勝手にスクレイピングするなとは書いてありますが、ブラウザでの操作を自動化してツイートする方法だったら問題ないんじゃないかなー、と解釈しました。違ったら教えてください。投稿を消して逃げます。

ちなみにどうでもいい話なんですが、力ずくで自動化する難易度は

Webアプリ < Androidアプリ

という感じのような気がします。AndroidアプリではAccessibilityServiceというやべー黒魔術みたいなAPIを使うと自動化できます(が、これがなかなか面倒くさい)。iOSは開発環境を持ってないので知りません。


環境等


  • Ubuntu 16.04 LTS

  • Chrome 68

Windowsでやったらどうなるんやーみたいなことはやっていないのでわかりません。macみたいなUnix系のOSだったらおおよそ同じ方法で動くんじゃないかと思います(未検証)。

selenium-standalone-serverってやつを入れるとFirefoxでも今回と同じような方法で動かせるらしいです(未検証)。

で、今回はみんな大好き(たぶん)Go言語で書いていきたいと思います。バージョンは1.10.1です。もう古くなってるじゃん、恥ずかしい。


Try

sclevine/agoutiがWebDriverをよしなにラップしてくれているのでこれを使います。「あご打ち?痛そう」とか思いましたが、これはネズミの一種のようですね。gopherのホリネズミからの連想でしょうか。

そんなことはさておき、go getします。

$ go get github.com/sclevine/agouti

それから、ChromeのWebDriverをここから取ってきます。解凍したら適当にPathが通っている場所に置きます。

※ Raspberry Pi でやりたい場合、あいつはCPUがarmなので上記のページには置いていません。aptchromium-chromedriverというパッケージがあるのでサクッと入ります。インストールされる場所にPATHが通ってないので、PATHを通すかシンボリックリンクを張るかしましょう。おそらく/usr/lib/chromium-browserにあります。

あとはちょこちょこっとGo言語で書けばおおよそ完成。

今回は「なるほど、4時じゃねーの」とツイートするようにしてみ ます。 てもよかったのですが、個人的な願望に野獣の日に

忘れずツイートしたいみたいなのがあるので、それを形にしたいと思います。

package main

import (
"log"
"time"
"github.com/sclevine/agouti"
)

const TWITTER_USERNAME = "ユーザー名"
const TWITTER_PASSWORD = "パスワード"
const TWEET_TEXT = "今日は野獣の日だからみんなのところに襲いに行くぞ!ガオーッ!!"

func main() {
driver := agouti.ChromeDriver()
if err := driver.Start(); err != nil {
log.Fatal(err)
}
defer driver.Stop()
page, err := driver.NewPage(agouti.Browser("chrome"))
if err != nil {
log.Fatal(err)
}
if err = page.Navigate("https://www.twitter.com/login"); err != nil {
log.Fatal(err)
}
// ユーザー名フィールド
page.FindByXPath("//*[@id=\"page-container\"]/div/div[1]/form/fieldset/div[1]/input").Fill(TWITTER_USERNAME)
// パスワードフィールド
page.FindByXPath("//*[@id=\"page-container\"]/div/div[1]/form/fieldset/div[2]/input").Fill(TWITTER_PASSWORD)
// ログインボタン
page.FindByXPath("//*[@id=\"page-container\"]/div/div[1]/form/div[2]/button").Click()
time.Sleep(7 * time.Second)
// ツイートボタン
page.FindByXPath("//*[@id=\"global-new-tweet-button\"]").Click()
time.Sleep(5 * time.Second)
// ツイートのテキストエリア
page.FindByXPath("//*[@id=\"Tweetstorm-tweet-box-0\"]/div[2]/div[1]/div[2]/div[2]/div[2]/div[1]").Fill(TWEET_TEXT)
// ツイート送信ボタン
page.FindByXPath("//*[@id=\"Tweetstorm-tweet-box-0\"]/div[2]/div[2]/div[2]/span/button[2]").Click()
// ツイートの送信が完了する前に終了してしまうのを防ぐため最後にスリープ
time.Sleep(10 * time.Second)
}

XPathだとChromeの開発者ツールから一発でコピーできるのでラクです。ただし何を指定しているのかわかりにくいのでコメントを入れたほうがいいです。コードから読み取れないことはコメントを入れる、常識ですね(自分ができてるとは言ってない)。

まあそんなことはさておき、ビルドしましょう。ソースがあるディレクトリで

$ go build

を叩くだけです。

今回、 4時に 8月10日にツイートしている風の文面なので(4時といいながら4時じゃないという風にしてもそれはそれで面白さはあると思いますが)cronで 毎朝4時に 8月10日にツイートされるようにします。

$ crontab -e

ちなみに、export EDITOR=vimしておくとこのときにVimが起動して、せっせといじっている.vimrcの設定が適用されたエディタを使えたりして最高です(個人の見解)。

で、開いたエディタにおもむろに追記するんですが、

19 19 10 8 * /path/to/binary

みたいに普通に指定するとハマります。chromedriver使っていたりChromeを使っていたりでうまく動きません。GUIを利用するにはちょっと工夫が必要だったりします。以下のように指定します。

19 19 10 8 * bash -lc 'DISPLAY=:0 /path/to/binary'

Bashをログインシェルとして起動したときのように振る舞わせつつ指定したコマンドを実行させています。そうしないとターミナルから実行している時と環境が違ってPathなんかのエラーで泣くかもしれません。また、DISPLAY=:0を指定して表示するディスプレイを指定しないと待てど暮らせどChromeが起動しないのに特にエラーは吐かないってことでハマります。

今から8月10日までずっとPCつけっぱなしというのもアレなのでRaspberry Piとかに適当に食わせておくと良いでしょう。


課題


  • ユーザー名とパスワードがハードコーディングされているのは問題だと思います。


    • この投稿の趣旨とずれるのでそこについては述べないことにします。



  • Twitterのページがリニューアルされると死にます。


最後に

チキンなので凍結されるのが怖くて使っていません。

あと、本当はAndroidでAccessibilityServiceを使ってLINEを自動化する話を書こうと思ったのですが、インストール厨(なのか?これは)の私がUbuntu 18.04を試してみたくなってOSを上書きしたりしてソースコードが消滅していたので書けませんでした。