読者です 読者をやめる 読者になる 読者になる

Life, Education, Death

プログラミング以外でも思ったことをつらつらと書きたい

Slim3+Twitter4jでTwitterボットを作った

目標

実装

スクレイピング

XPathなどを使ってクールにスクレイピングを決めようと思っていたが、壊れたHTMLも読めるようなちょうどいいライブラリが見つからなかったので、jericho html parserを今回は使った。
HTML以外にもPHPが読めたりと多機能なパーサーだが、XPathが使えない。
ここからダウンロードして、WEB-INF/libに配置した。

final Source source = new Source( new URL("http://google.com/") );
Element el = source.getElementById("ghead")
                           .getAllElements(HTMLElementName.DIV)
                           .get(0);

というようにURLをSourceクラスに渡せば、HTMLのダウンロードとパースが完了している。
とっても簡単に使えていいと思う。
あとは泥臭いがgetElementByIdやgetAllElementsを駆使してガリガリスクレイピングをしていくだけ。

定期的に動作するようにする

定期的に動作するために、ターゲットとなるサイトの更新時間を取ろうと思ったが、1週間に一度更新されるのはわかっているので適当なタイミングで動くようにcronを設定するだけにした。

毎週月曜日に起動するように以下のようなcron.xmlを作った。WEB-INF以下に配置すればOK

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
 <cron>
 <url>/cron/post</url>
 <description>post test</description>
 <schedule>every monday 12:00</schedule>
 <timezone>Asia/Tokyo</timezone>
 </cron>
</cronentries>

cron用のサーブレットが外部からアクセスされないようにweb.xmlに以下のタグを追加した。
こうすると、/cron/以下にアクセスするとログインが求められるようになる。

 <security-constraint>
 <web-resource-collection>
 <url-pattern>/cron/*</url-pattern>
 </web-resource-collection>
 <auth-constraint>
 <role-name>admin</role-name>
 </auth-constraint>
 </security-constraint>
Twitterにポストする

2010年6月をもってBasic認証のサポートはしないとTwitter側は言ってるので、OAuthでボットも実装しないといけない。OAuth自体は面倒かもしれないが、全てTwitter4jがやってくれるので、特に問題はなかった。
ここを参考にしながら作業した。
(参考元はRubyなので、適宜読み替えて)

Rubyが入っていない環境だったので、以下のようなソースを書いて適当にAccessTokenを取得した。

public class GetaccesstokenController extends Controller {

 private String REQUEST_TOKEN = "REQUEST_TOKEN";
  private String CONSUMER_KEY = "Consumer Key";
  private String CONSUMER_SECRET = "Consumer Secret";
 @Override
 public Navigation run() throws Exception {

 TwitterFactory factory = new TwitterFactory();
 Twitter twitter = factory.getOAuthAuthorizedInstance(CONSUMER_KEY, CONSUMER_SECRET);

 final String pinCode = asString("pin");
 if( pinCode != null ){
 RequestToken savedRequestToken = sessionScope( REQUEST_TOKEN ); 
 AccessToken accessToken = twitter.getOAuthAccessToken( savedRequestToken, pinCode );

 System.out.println( "access token :" + accessToken.getToken() );
 System.out.println( "access secret :" + accessToken.getTokenSecret() );

 return forward("getaccesstoken.jsp");
 }

 RequestToken requestToken = twitter.getOAuthRequestToken();
 sessionScope( REQUEST_TOKEN, requestToken );
 System.out.println("RequestTokenを保存");

 return redirect( requestToken.getAuthenticationURL() );
 }
}
  1. Twitter4jでRequestTokenをセッションに保存する
  2. リダイレクト先を取得して、そのアドレスに転送
  3. PINコードをGETで渡してもらって (/getaccesstoken?pin=XXXXXとアクセスする)
  4. 先ほどのRequestTokenとPINコードからAccessTokenを生成する
  5. Access TokenとAccessSecretを保存して完了

Twitter4jではAccessTokenとAccessSecretがあれば、以下のようなコードで認証済みのTwitterオブジェクトが手に入る。

 TwitterFactory factory = new TwitterFactory();
 final AccessToken accessToken = new AccessToken(
  "AccessToken",
  "AccessSecret"
 );

  Twitter twitter = factory.getOAuthAuthorizedInstance("ConsumerKey", "ConsumerSecret");
 twitter.setOAuthAccessToken(accessToken);
  ||<

***TaskQueueを使う
スクレイピングはできた。Twitterにもポストできる。完成した!となるわけですが、GAEには30秒ルールがあるので、スクレイピング対象のサーバーが重かったりして30秒越えないともわからない。
そこでTwitterにポストする処理とスクレイピング処理を別々にし、
スクレイピング後、Twitterのポストをするサーブレットを起動する仕組みにした。

そこで利用したのがTaskQueue。
TaskQueueは非同期でサーブレットを起動する仕掛け。cronと違いすぐ起動できる。

使うにはまず、queue.xmlに自分で使うQueueを登録する。
>|xml|
 <queue>
  <name>my-queue</name>
  <rate>10/m</rate>
 <bucket-size>10</bucket-size>
 </queue>

そうすると、以下のようにQueueを取得できる。またaddメソッドで実行したいURLとパラメータを指定して、実行したいコマンドをいくつも登録できる
(一度に登録できる数に制限がある)

Queue task = QueueFactory.getQueue("my-queue");
task.add(
  TaskOptions.Builder.url("/task/post")
  .param("data", "hogehoge" )
 );

アップロード時のトラブル

アップロード時に一度、以下のようなエラーが出た。
cron.xmlの文法エラーとかがあると出るらしい*1

Unable to update app: Error posting to URL: https://appengine.google.com/api/datastore/cron/update?app_id=movierankingbot&version=1&
500 Internal Server Error

Server Error (500)
A server error has occurred.


See the deployment console for more details

cron.xmlに記述ミスがあったので、修正して無事アップできた。

まとめ

プログラムの構造としては
スクレイピング&取得データをDataStoreに登録 → Twitterポストサーブレットが非同期で起動。
という感じになった。TaskQueueをうまく使えば、30秒ルールを破れるので面白い。
TaskQueueの使い方はもっと勉強したほうがいいなと感じた。

作ったボットはこちら

  • Twitter4jを使えば、ボットもサクサク作れる
  • スクレイピングにはやっぱりXPathが使えるほうが楽チンだと思う
  • GAEが使えるとさっと作ったものが動いて楽しい
  • 今回は小さいプログラムだったのでSlim3のモデルは使わなかった

実は知らなかったこと

  • ライブラリはWEB-INF/libの下に配置しなければならない