綺麗に死ぬITエンジニア

PHPで作るTwitter自動化アプリケーション入門

2015-12-07

TwitterのBotと言われるような自動ツイートシステムや自動フォロー返しなどの仕組みを、PHPを用いて実装するための基礎知識を備忘録として書き残します。

注意事項

当記事の情報を利用して、Twitterルールに違反する行為(不正利用やスパム行為)を行い、いかなる損害を受けたとしても、当ブログは一切の責任を負いません。作成したコードは全て自己責任で利用してください。

特に、不特定多数のユーザーに対する自動フォロー・自動リプライ・自動リツイートなどは、一部の例外を除いて、原則禁止されています。禁止されている行為を働いた場合は、アカウントが凍結される可能性がありますので、注意しましょう。

アプリケーション登録

まず、Twitterに自作アプリケーションを実行するための登録を行います。

Twitter Application Managementにアクセスし、「Sign in」のリンクから自動操作するTwitterアカウントでログインします。

tw1

ログインすると、「Create New App」と書かれたボタンが出現するので、クリック。次の画面で必要事項を記入し、「Create your Twitter application」ボタンをクリックすることで、登録が完了します。

tw2

  • Name アプリケーション名
  • Description アプリケーションの説明
  • Website アプリケーションのWebサイトのURL
  • Callback URL OAuthでTwitter認証成功後、ユーザがリダイレクトされるURL ※自動化用途であれば不要

作成したアプリケーションの画面から、「Keys and Access Tokens」タブを開き、以下の項目をメモ(保存)しておいてください。「Access Token」が見当たらない場合は、「Create my access token」から作成してください。

tw3

  • Consumer Key (API Key)
  • Consumer Secret (API Secret)
  • Access Token
  • Access Token Secret

後ほど、PHP内で利用します。

サーバー作業

自動操作を実行するサーバー(Linux)を用意します。予めPHPはインストールしておいてください。

# 好きなバージョンをインストール
yum install php-cli
yum install php54-cli
yum install php55-cli
yum install php56-cli

PHPファイルを作成/実行する任意のディレクトリへ移動します。

mkdir ~/bin
cd ~/bin/  # 好きなディレクトリへ

composerをインストールします。

curl -sS https://getcomposer.org/installer | php

composerを使って、TwitterOAuthライブラリをインストール。

./composer.phar require abraham/twitteroauth:\*

後は、実際にコードを記述するだけです。

PHPコードの記述

PHPのコードは、作成したディレクトリの直下にそのまま作成してOKです。

vi search_tweet.php  # 任意のファイル名を指定

PHPコードの先頭に、下記の記述をし、TwitterOAuthクラスをインポートできるようにします。

<?php
require "vendor/autoload.php";

use Abraham\TwitterOAuth\TwitterOAuth;

TwitterOAuthインスタンスの初期化時に、先ほどメモしたConsumer Key等の情報を渡します。

$connection = new TwitterOAuth(
    "[Consumer Key (API Key) の内容]",
    "[Consumer Secret (API Secret) の内容]",
    "[Access Token の内容]",
    "[Access Token Secret の内容]"
);

後は、getやpostメソッドを利用し、TwitterのAPIを自由に利用することができます。

$statuses = $connection->get("search/tweets", array("q" => "twitterapi"));
$statues = $connection->post("statuses/update", array("status" => "hello world"));

例えば、"PHP"というキーワードを含む、リンク以外の日本語の最新ツイートを20件を取得するには、以下のようなコードになります。

<?php
require "vendor/autoload.php";

use Abraham\TwitterOAuth\TwitterOAuth;

$connection = new TwitterOAuth(
    "[Consumer Key (API Key) の内容]",
    "[Consumer Secret (API Secret) の内容]",
    "[Access Token の内容]",
    "[Access Token Secret の内容]"
);

if ($connection) {
    $tweets = $connection->get("search/tweets", [
        "q" => '"PHP" -filter:links',
        "lang" => "ja",
        "count" => 20,
        "result_type" => "recent"
    ]);

    print_r($tweets);
}

以下のコマンドを入力することで、このPHPコードを実行することができます。

php search_tweet.php  # 任意のファイル名を指定

結果は以下のようになります。

stdClass Object
(
    [statuses] => Array
        (
            [0] => stdClass Object
                (
                    [metadata] => stdClass Object
                        (
                            [iso_language_code] => ja
                            [result_type] => recent
                        )

                    [created_at] => Mon Dec 07 07:08:12 +0000 2015
                    [id] => 673760905188929536
                    [id_str] => 673760905188929536
                    [text] => RT @mygod877: Python
「なんちゃってプログラマは触れるな」 
phpjavaに帰れ」 

Ruby
「新参だ!逃がすな!」 
「話が難しい?いくらでも解説するぞ!!」 

Haskellおじさん
Haskellはいいぞ」 
Haskellはいいぞ」 …
                    [source] => [Twitter Web Client](http://twitter.com)
                    [truncated] => 
                    [in_reply_to_status_id] => 
                    [in_reply_to_status_id_str] => 
                    [in_reply_to_user_id] => 
                    [in_reply_to_user_id_str] => 
                    [in_reply_to_screen_name] => 
                    [user] => stdClass Object
                        (
                            [id] => 99843901
                            [id_str] => 99843901
                            [name] => GC@みあげて応援中
                            [screen_name] => gc_locks
                            [location] => 涼しいところ
                            [description] => とあるベンチャー企業で働く人間、最近はイカ。  プログラミング, C++, Ruby on Rails, Unity, August, 岡崎市
                            [url] => 
                            [entities] => stdClass Object
                                (
                                    [description] => stdClass Object
                                        (
                                            [urls] => Array
                                                (
                                                )

                                        )

                                )

                            [protected] => 
                            [followers_count] => 203
                            [friends_count] => 154
                            [listed_count] => 20
                            [created_at] => Mon Dec 28 03:01:45 +0000 2009
                            [favourites_count] => 303
                            [utc_offset] => 32400
                            [time_zone] => Tokyo
                            [geo_enabled] => 
                            [verified] => 
                            [statuses_count] => 5272
                            [lang] => en
                            [contributors_enabled] => 
                            [is_translator] => 
                            [is_translation_enabled] => 
                            [profile_background_color] => EBEBEB
                            [profile_background_image_url] => http://pbs.twimg.com/profile_background_images/619863870/v0ulvzu8v8157jlthnvv.png
                            [profile_background_image_url_https] => https://pbs.twimg.com/profile_background_images/619863870/v0ulvzu8v8157jlthnvv.png
                            [profile_background_tile] => 1
                            [profile_image_url] => http://pbs.twimg.com/profile_images/1431952312/mami_normal.png
                            [profile_image_url_https] => https://pbs.twimg.com/profile_images/1431952312/mami_normal.png
                            [profile_link_color] => 990000
                            [profile_sidebar_border_color] => DFDFDF
                            [profile_sidebar_fill_color] => F3F3F3
                            [profile_text_color] => 333333
                            [profile_use_background_image] => 1
                            [has_extended_profile] => 
                            [default_profile] => 
                            [default_profile_image] => 
                            [following] => 
                            [follow_request_sent] => 
                            [notifications] => 
                        )

                    [geo] => 
                    [coordinates] => 
                    [place] => 
                    [contributors] => 
                    [retweeted_status] => stdClass Object
                        (
                            [metadata] => stdClass Object
                                (
                                    [iso_language_code] => ja
                                    [result_type] => recent
                                )

                            [created_at] => Sun Dec 06 10:46:58 +0000 2015
                            [id] => 673453571690524672
                            [id_str] => 673453571690524672
                            [text] => Python
「なんちゃってプログラマは触れるな」 
phpjavaに帰れ」 

Ruby
「新参だ!逃がすな!」 
「話が難しい?いくらでも解説するぞ!!」 

Haskellおじさん
Haskellはいいぞ」 
Haskellはいいぞ」 
Haskellはいいぞ」
                            [source] => [Twitter Web Client](http://twitter.com)
                            [truncated] => 
                            [in_reply_to_status_id] => 
                            [in_reply_to_status_id_str] => 
                            [in_reply_to_user_id] => 
                            [in_reply_to_user_id_str] => 
                            [in_reply_to_screen_name] => 
                            [user] => stdClass Object
                                (
                                    [id] => 3250614607
                                    [id_str] => 3250614607
                                    [name] => ささ@白猫勢
                                    [screen_name] => mygod877
                                    [location] => 北海道 室蘭市
                                    [description] => 白猫好きです、プリムラは持っていません さんぺい/白猫/室工/情電/php/アニメ/アイマス
                                    [url] => 
                                    [entities] => stdClass Object
                                        (
                                            [description] => stdClass Object
                                                (
                                                    [urls] => Array
                                                        (
                                                        )

                                                )

                                        )

                                    [protected] => 
                                    [followers_count] => 93
                                    [friends_count] => 198
                                    [listed_count] => 4
                                    [created_at] => Sat Jun 20 10:27:19 +0000 2015
                                    [favourites_count] => 161
                                    [utc_offset] => 
                                    [time_zone] => 
                                    [geo_enabled] => 
                                    [verified] => 
                                    [statuses_count] => 932
                                    [lang] => ja
                                    [contributors_enabled] => 
                                    [is_translator] => 
                                    [is_translation_enabled] => 
                                    [profile_background_color] => C0DEED
                                    [profile_background_image_url] => http://abs.twimg.com/images/themes/theme1/bg.png
                                    [profile_background_image_url_https] => https://abs.twimg.com/images/themes/theme1/bg.png
                                    [profile_background_tile] => 
                                    [profile_image_url] => http://pbs.twimg.com/profile_images/673449234264276992/2I9TNGjr_normal.png
                                    [profile_image_url_https] => https://pbs.twimg.com/profile_images/673449234264276992/2I9TNGjr_normal.png
                                    [profile_banner_url] => https://pbs.twimg.com/profile_banners/3250614607/1434796503
                                    [profile_link_color] => 0084B4
                                    [profile_sidebar_border_color] => C0DEED
                                    [profile_sidebar_fill_color] => DDEEF6
                                    [profile_text_color] => 333333
                                    [profile_use_background_image] => 1
                                    [has_extended_profile] => 
                                    [default_profile] => 1
                                    [default_profile_image] => 
                                    [following] => 
                                    [follow_request_sent] => 
                                    [notifications] => 
                                )

                            [geo] => 
                            [coordinates] => 
                            [place] => 
                            [contributors] => 
                            [is_quote_status] => 
                            [retweet_count] => 157
                            [favorite_count] => 119
                            [entities] => stdClass Object
                                (
                                    [hashtags] => Array
                                        (
                                        )

                                    [symbols] => Array
                                        (
                                        )

                                    [user_mentions] => Array
                                        (
                                        )

                                    [urls] => Array
                                        (
                                        )

                                )

                            [favorited] => 
                            [retweeted] => 
                            [lang] => ja
                        )

                    [is_quote_status] => 
                    [retweet_count] => 157
                    [favorite_count] => 0
                    [entities] => stdClass Object
                        (
                            [hashtags] => Array
                                (
                                )

                            [symbols] => Array
                                (
                                )

                            [user_mentions] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [screen_name] => mygod877
                                            [name] => ささ@白猫勢
                                            [id] => 3250614607
                                            [id_str] => 3250614607
                                            [indices] => Array
                                                (
                                                    [0] => 3
                                                    [1] => 12
                                                )

                                        )

                                )

                            [urls] => Array
                                (
                                )

                        )

                    [favorited] => 
                    [retweeted] => 
                    [lang] => ja
                )

/* (中略) */

            [19] => stdClass Object
                (
                    [metadata] => stdClass Object
                        (
                            [iso_language_code] => ja
                            [result_type] => recent
                        )

                    [created_at] => Mon Dec 07 06:47:09 +0000 2015
                    [id] => 673755609305509888
                    [id_str] => 673755609305509888
                    [text] => RT @mygod877: Python
「なんちゃってプログラマは触れるな」 
phpjavaに帰れ」 

Ruby
「新参だ!逃がすな!」 
「話が難しい?いくらでも解説するぞ!!」 

Haskellおじさん
Haskellはいいぞ」 
Haskellはいいぞ」 …
                    [source] => [SobaCha](https://sites.google.com/site/wakamesoba98/sobacha)
                    [truncated] => 
                    [in_reply_to_status_id] => 
                    [in_reply_to_status_id_str] => 
                    [in_reply_to_user_id] => 
                    [in_reply_to_user_id_str] => 
                    [in_reply_to_screen_name] => 
                    [user] => stdClass Object
                        (
                            [id] => 2360591840
                            [id_str] => 2360591840
                            [name] => Atis
                            [screen_name] => AtiS
                            [location] => Akita, Japan
                            [description] => サイクリングがすきです。ついでにIngressしてます。ゆるふわプログラマ。 Ingress L12(@AtisClock) | SDVX 或帝滅斗 | IIDX SP 6| LGS-CTR | ESCAPE R3 | Arch Linux | Ruby
                            [url] => 
                            [entities] => stdClass Object
                                (
                                    [description] => stdClass Object
                                        (
                                            [urls] => Array
                                                (
                                                )

                                        )

                                )

                            [protected] => 
                            [followers_count] => 504
                            [friends_count] => 292
                            [listed_count] => 32
                            [created_at] => Tue Feb 25 05:57:48 +0000 2014
                            [favourites_count] => 41414
                            [utc_offset] => 32400
                            [time_zone] => Tokyo
                            [geo_enabled] => 
                            [verified] => 
                            [statuses_count] => 35008
                            [lang] => ja
                            [contributors_enabled] => 
                            [is_translator] => 
                            [is_translation_enabled] => 
                            [profile_background_color] => BADFCD
                            [profile_background_image_url] => http://abs.twimg.com/images/themes/theme12/bg.gif
                            [profile_background_image_url_https] => https://abs.twimg.com/images/themes/theme12/bg.gif
                            [profile_background_tile] => 
                            [profile_image_url] => http://pbs.twimg.com/profile_images/671559345587359744/0gBYKQ4__normal.png
                            [profile_image_url_https] => https://pbs.twimg.com/profile_images/671559345587359744/0gBYKQ4__normal.png
                            [profile_banner_url] => https://pbs.twimg.com/profile_banners/2360591840/1442049100
                            [profile_link_color] => FF0000
                            [profile_sidebar_border_color] => F2E195
                            [profile_sidebar_fill_color] => FFF7CC
                            [profile_text_color] => 0C3E53
                            [profile_use_background_image] => 1
                            [has_extended_profile] => 
                            [default_profile] => 
                            [default_profile_image] => 
                            [following] => 
                            [follow_request_sent] => 
                            [notifications] => 
                        )

                    [geo] => 
                    [coordinates] => 
                    [place] => 
                    [contributors] => 
                    [retweeted_status] => stdClass Object
                        (
                            [metadata] => stdClass Object
                                (
                                    [iso_language_code] => ja
                                    [result_type] => recent
                                )

                            [created_at] => Sun Dec 06 10:46:58 +0000 2015
                            [id] => 673453571690524672
                            [id_str] => 673453571690524672
                            [text] => Python
「なんちゃってプログラマは触れるな」 
phpjavaに帰れ」 

Ruby
「新参だ!逃がすな!」 
「話が難しい?いくらでも解説するぞ!!」 

Haskellおじさん
Haskellはいいぞ」 
Haskellはいいぞ」 
Haskellはいいぞ」
                            [source] => [Twitter Web Client](http://twitter.com)
                            [truncated] => 
                            [in_reply_to_status_id] => 
                            [in_reply_to_status_id_str] => 
                            [in_reply_to_user_id] => 
                            [in_reply_to_user_id_str] => 
                            [in_reply_to_screen_name] => 
                            [user] => stdClass Object
                                (
                                    [id] => 3250614607
                                    [id_str] => 3250614607
                                    [name] => ささ@白猫勢
                                    [screen_name] => mygod877
                                    [location] => 北海道 室蘭市
                                    [description] => 白猫好きです、プリムラは持っていません さんぺい/白猫/室工/情電/php/アニメ/アイマス
                                    [url] => 
                                    [entities] => stdClass Object
                                        (
                                            [description] => stdClass Object
                                                (
                                                    [urls] => Array
                                                        (
                                                        )

                                                )

                                        )

                                    [protected] => 
                                    [followers_count] => 93
                                    [friends_count] => 198
                                    [listed_count] => 4
                                    [created_at] => Sat Jun 20 10:27:19 +0000 2015
                                    [favourites_count] => 161
                                    [utc_offset] => 
                                    [time_zone] => 
                                    [geo_enabled] => 
                                    [verified] => 
                                    [statuses_count] => 932
                                    [lang] => ja
                                    [contributors_enabled] => 
                                    [is_translator] => 
                                    [is_translation_enabled] => 
                                    [profile_background_color] => C0DEED
                                    [profile_background_image_url] => http://abs.twimg.com/images/themes/theme1/bg.png
                                    [profile_background_image_url_https] => https://abs.twimg.com/images/themes/theme1/bg.png
                                    [profile_background_tile] => 
                                    [profile_image_url] => http://pbs.twimg.com/profile_images/673449234264276992/2I9TNGjr_normal.png
                                    [profile_image_url_https] => https://pbs.twimg.com/profile_images/673449234264276992/2I9TNGjr_normal.png
                                    [profile_banner_url] => https://pbs.twimg.com/profile_banners/3250614607/1434796503
                                    [profile_link_color] => 0084B4
                                    [profile_sidebar_border_color] => C0DEED
                                    [profile_sidebar_fill_color] => DDEEF6
                                    [profile_text_color] => 333333
                                    [profile_use_background_image] => 1
                                    [has_extended_profile] => 
                                    [default_profile] => 1
                                    [default_profile_image] => 
                                    [following] => 
                                    [follow_request_sent] => 
                                    [notifications] => 
                                )

                            [geo] => 
                            [coordinates] => 
                            [place] => 
                            [contributors] => 
                            [is_quote_status] => 
                            [retweet_count] => 157
                            [favorite_count] => 119
                            [entities] => stdClass Object
                                (
                                    [hashtags] => Array
                                        (
                                        )

                                    [symbols] => Array
                                        (
                                        )

                                    [user_mentions] => Array
                                        (
                                        )

                                    [urls] => Array
                                        (
                                        )

                                )

                            [favorited] => 
                            [retweeted] => 
                            [lang] => ja
                        )

                    [is_quote_status] => 
                    [retweet_count] => 157
                    [favorite_count] => 0
                    [entities] => stdClass Object
                        (
                            [hashtags] => Array
                                (
                                )

                            [symbols] => Array
                                (
                                )

                            [user_mentions] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [screen_name] => mygod877
                                            [name] => ささ@白猫勢
                                            [id] => 3250614607
                                            [id_str] => 3250614607
                                            [indices] => Array
                                                (
                                                    [0] => 3
                                                    [1] => 12
                                                )

                                        )

                                )

                            [urls] => Array
                                (
                                )

                        )

                    [favorited] => 
                    [retweeted] => 
                    [lang] => ja
                )

        )

    [search_metadata] => stdClass Object
        (
            [completed_in] => 0.154
            [max_id] => 673760905188929536
            [max_id_str] => 673760905188929536
            [next_results] => ?max_id=673755609305509887&q=%22PHP%22%20-filter%3Alinks&lang=ja&count=20&include_entities=1&result_type=recent
            [query] => %22PHP%22+-filter%3Alinks
            [refresh_url] => ?since_id=673760905188929536&q=%22PHP%22%20-filter%3Alinks&lang=ja&result_type=recent&include_entities=1
            [count] => 20
            [since_id] => 0
            [since_id_str] => 0
        )

)

つまり、対象の1番目のツイートの投稿者のユーザーIDを取得したい場合は、以下のようにして参照できます。

if ($connection) {
    $tweets = $connection->get("search/tweets", [
        "q" => '"PHP" -filter:links',
        "lang" => "ja",
        "count" => 20,
        "result_type" => "recent"
    ]);

    echo $tweets->statuses[0]->user->id;
}
99843901

どういうAPIがあって、どういうパラメーターを指定できて、どういう結果が返ってくるのかは、Twitter Developers Documentation REST APIsを見ることでわかります。

例えば、例で利用した$connection->get("search/tweets", $parameters)は、「GET search/tweets」のAPIを利用したもので、ドキュメントを見ると指定できるパラメーター項目の「Parameters」と、返ってくる結果のサンプルである「Example Result」の項目から、大体どのような入力と出力になるのかがわかると思います。

他にも、ユーザーをフォローするには「POST friendships/create」のAPIがあるので、PHPのコード上で$connection->post("friendships/create", $parameters)とすればいいことがわかります。もちろん、$parametersで指定すべきパラメーターの内容も、ドキュメントの内容からわかります。$connection->post("friendships/create", ["user_id" => 94355913])とすれば、あなたは私のアカウントをフォローすることでしょう。

あ、そうそう、ツイートをするのは「POST statuses/update」です。コードだと、$connection->post("statuses/update", $parameters)。リツイートするのは「POST statuses/retweet/:id」です。コードだと、$connection->post("statuses/retweet/" . $id, $parameters)

ね、簡単でしょう?

なお、指定するIDは都度、ツイートのIDだったりユーザーのIDだったりするので、適宜ドキュメントを参考にしましょう。

他にも、以下のようにすることで、メディアのアップロード等も可能です。

$media1 = $connection->upload('media/upload', array('media' => '/path/to/file/kitten1.jpg'));
$media2 = $connection->upload('media/upload', array('media' => '/path/to/file/kitten2.jpg'));
$parameters = array(
    'status' => 'Meow Meow Meow',
    'media_ids' => implode(',', array($media1->media_id_string, $media2->media_id_string)),
);
$result = $connection->post('statuses/update', $parameters);

このように、Twitter上のほとんどの操作をPHPのコードで再現することができますので、好きなように操作の自動化が可能です。

定期実行の設定

サーバーのcronの機能を用いることで、定期的に作成したPHPコードを実行することができます。サーバーで以下のコマンドを打ちましょう。

crontab -e

viと同様のエディターが起動するので、以下のように記述します。

0 12 * * * php -q ~/bin/search_tweet.php >/dev/null 2>&1

上記で、毎日12時ちょうどに作成したPHPコードが実行されるようになります。

cronの詳しい設定方法については、ググってください

まとめ

定期的にツイートしたりなんてことはもちろん、時間やトレンドによって動作を変えることもできるので、アイディア次第では色々なことができそうです。

ただ、APIには呼び出し回数に制限があったり、冒頭の注意事項を守らないとアカウントが凍結されるリスクがあったりするので、慎重に楽しめる範囲でやってくださいね。馬鹿みたいに無限ループとかさせたりすると、怒られそうです……。

世の中には、まるで人間のような人工知能を備えたBotなどもあります。皆さんも是非、そういうものを目指して、作ってみてはいかがでしょうか。

筆者について

フリーランスエンジニアとして活動している、「もりやませーた」です。

筆者のTwitterはこちら。記事に関するご意見等はTwitterの方へお寄せください。

その他業務に関するお問い合わせは、こちらのページをご覧ください。

PHP OSS Twitter