2010-01-31

OPML と Google カスタム検索

Japanese Bloggers Info に、登録ブログを記載した OPML 発行と、登録ブログ対象のカスタム検索の機能をつけた。
  • ブログ情報から自動的に OPML ファイルを生成するサーブレットを作成。
  • Google のカスタム検索を作成し、OPML を登録、検索対象サイトを自動的に更新させる。
ということをした。OPML の仕様は、次のサイトを参考に。 カスタム検索への OPML 登録は、Google カスタム検索エンジン「マイ検索エンジン > コントロールパネル > 詳細設定」から。

「アノテーションフィードの追加」という項目に、Custom Search XML という、アノテーション(検索対象サイトを指定するもの)を記載した XML を登録する必要があるが、OPML をこの XML に変換するツールが用意されているので、それを間にかませる。つまり、

www.google.com/cse/tools/makeannotations?url=(OPML の URL)&label=(カスタム検索の ID)

のような URL を、アノテーションフィードとして登録すれば OK。検索フォームについては、検索結果をページ内に出すために、カスタム検索「マイ検索エンジン > Custom Search Element」を利用した。うん、なかなかいい感じ。

2010-01-21

GAE/J で If-Modified-Since に対応

フィードを配信するようになったので、ついでに If-Modified-Since ヘッダに対応させてみた。フィードリーダーからのアクセスがよくなるのかどうなのか、わからないのだけれど。

コードはほとんど、こちらの丸写し。変えたのは、Last-Modified をブログのリストをキャッシュしたときの日時にしたことぐらい。 なんとなく、フィードリーダーでのデータ更新の反映が早くなった気がする…。

GAE/J で RSS フィードの配信

でフィード配信開始しました。それがこちら。 フィードリーダーに登録しておくと楽しいかも。

RSS フィード発行のためのコードはこんな感じ。いつも載せるコードよりちょっと長いけれど、まあ備忘録として。
Feed feed = new Feed();
feed.setTitle(TextConstruct.create(TextConstruct.Type.TEXT, "Japanese Bloggers Info", null));
feed.setSubtitle(TextConstruct.create(TextConstruct.Type.HTML, "日本語の Blogger ブログの更新情報を紹介しています。", null));
feed.setGenerator("1.0", "http://japanese-bloggers.appspot.com/", "Japanese Bloggers Info");
feed.setUpdated(new DateTime(now.getTime(), 540));
feed.addLink("alternate", "text/html", "http://japanese-bloggers.appspot.com/");
feed.addLink("self", "application/rss+xml", "http://japanese-bloggers.appspot.com/feed");
feed.getAuthors().add(new Person("Kuribo", "http://www.kuribo.info/", "kurikuribo@gmail.com"));

ArrayList<Entry> entries = new ArrayList<Entry>();

Entry entry = new Entry();
entry.setTitle(TextConstruct.create(TextConstruct.Type.TEXT, blogtitle + ": " + posttitle, null));
entry.setContent(TextConstruct.create(TextConstruct.Type.HTML, summary, null));
entry.setPublished(DateTime.parseDateTime(postPublished));//postPublished は Blogger のフィードから得た発行日時
entry.addLink("alternate", "text/html", posturl);
entry.getAuthors().add(new Person(author));
entries.add(entry);

feed.setEntries(entries);

StringWriter sw = new StringWriter();
try {
 feed.generateRss(new XmlWriter(sw), new ExtensionProfile());
} catch (IOException e) {
 //省略
}
String rss = sw.getBuffer().toString();
ちょっとはまったのが、8 行目と 17 行目の author 項目。Feed.setAuthors(List<Person>) みたいなメソッドがないので焦ったのだけれど、上掲のように get した List(要素なし)に Person を追加するのでいいみたい。

あと 14 行目 content の項目は、アプリケーションでエスケープするので、text でいいやと思って指定してみるとフィードリーダーに本文が表示されず。タイトルは text、内容は html というのがよさそう。

2010-01-17

Quota と 10 秒ルールのはざまで

を公開してから一週間。現在、順調にユーザーが増えているところ。だけど、このままユーザーが増えても、無料の Quota 内でいけるのだろうかと少し心配に。 このアプリでは
  • Update サーブレットが cron で 5 分間隔に呼ばれ、登録されているブログ情報を確認して、TaskQueue で Check サーブレットを呼び出す。
  • Check サーブレットでは Update サーブレットから渡されたパラメータ(Key String)をもとにブログ情報を確認し、その投稿フィードへアクセス。更新があればその内容をブログ情報へ格納。
  • 登録ブログ分の Check サーブレットが全て動き終わると(MemcacheService.increment(Object, long) で確認)、Prepare サーブレットが起動。登録されているブログから更新時刻をもとに 50 個 Query を使って呼び出してリストを作成し、Memcache に格納。
  • トップページの Top サーブレットで、Memcache からリストを表示。なければ Prepare サーブレットのリスト作成メソッドから取得。
なんてことをしているんだけれど、今までは贅沢に、ブログの更新をチェックする Check サーブレットを、ブログの数だけ TaskQueue に積んで起動させていた。無料 Quota の Task Queue 呼び出しリミットは 100,000。このままの仕様だと、348 ブログ登録されただけで、一日の最後の方には「Over Quota」となってアプリが止まってしまう危険性が…。 そこで Check サーブレットの仕組みをいくらか変えて、複数ブログの更新チェックを一度にできるようにしてみた。とりあえず 10 件で。DatastoreService とか GoogleService とか、その他色んなリソースを使いまわせるので、パフォーマンス的にもいいかも。とか思って動かしてみると…

Request was aborted after waiting too long to attempt to service your request. Most likely, this indicates that you have reached your simultaneous dynamic request limit. This is almost always due to excessively high latency in your app. Please see http://code.google.com/appengine/docs/quotas.html for more details.

というログで、ションボリ。なんだこりゃと調べてみて、一番分かりやすかったのがこのページ。
 あのwarningは、リクエスト処理中に後続のリクエストが来てqueueにつまれ10秒以内に処理されなかったときに発生します。

 つまり、AppEgnineは30秒ルール以外にも、負荷が集中するときには、10秒以内に処理しないければいけないという10秒ルールも存在するのです。
ということなので、とりあえず 5 件ごとの更新チェックにしたところ、ようやく何も言われなくなった。登録ブログが次の上限 1736 件に迫ったら、また別の方法を考えないと…。

2010-01-11

GAE/J アプリ「Japanese Bloggers Info」完成

Google App Engine で作った初めてのアプリ、完成。日本語 Blogger ブログの更新情報を紹介するもの。 簡単な説明がこちら。 実際にアプリを公開して、色んな人に使ってもらって初めて気づくこともあったり。
  • まだ記事のないブログを登録する人がいる。
     →エントリにアクセスしないよう条件分岐。
  • 50 件も表示すると、最後の方のブログは右上のプレビューが見えない。
     →下半分は下半分用のプレビューエリアで表示。
  • ブログ名や投稿タイトルによっては、プレビューの JavaScript が止まってしまう。
     →JavaScript の配列に入れるメッセージのエスケープ。
  • ブログの Blogger 側の設定によっては、full フィードへのアクセス不能に。
     → default へのアクセスに切り替え。
  • Blogger の設定によっては、プロフィールへの URL が取れない。
     → プロフィール URL がなければプロフィールアイコン非表示に。
こういう新しいアプリは、不完全なままでもどんどん公開して、実際に使ってもらった方が、問題点が見えやすくなっていいんだなと実感。動かしながら修正して行くというのは、それはそれで大変なんだけれど。

2010-01-09

GAE/J、Blogger フィードのコンテントが取れない

Perl で昔に書いた Japanese Blogger Update Info の後継として、Blogger ブログの更新情報を掲載する GAE/J アプリを製作中。

Google アカウントと結び付けられた Blogger ブログを登録してもらって、トップページで時系列の更新情報を紹介。cron で一定時間(今のところ 5 分間隔)で Update というサーブレットが起動されるようにしていて、そこからまた TaskQueue で、各ブログのフィードへアクセスして更新を確認する Check サーブレットを複数起動する。全部チェックし終わったら更新されたブログのリスト(HTML)を作って、Memcache へ格納。トップページでそのキャッシュを表示、のようなことをしているのだけれど…。

ブログの投稿フィードのコンテント取得で一晩はまる。
GoogleService myService = new GoogleService("blogger", "kuribo-example-1");
Feed resultFeed = myService.getFeed("http://kuribo-programming.blogspot.com/feeds/posts/summary?max-results=1&redirect=false", Feed.class);//フィード取得。実際のフィード URL は Datastore から取得。
Entry entry = resultFeed.getEntries().get(0);//フィード記載の第 1 エントリ
String title = entry.getTitle().getPlainText();//タイトル
String url = entry.getLink("alternate", "text/html").getHref();//URL
String content = entry.getTextContent().getContent().getPlainText();//コンテント
のようにしていたところ、タイトルや URL は取れるが、コンテント(ブログ投稿の本文)が取れない。結局なんだったかというと、アクセスするフィード URL が悪かった。

Blogger のブログ投稿フィードは full(全文)、summary(冒頭)、default(ブログオーナーが設定している全文・冒頭のどちらか)の 3 種類でアクセスできるが、summary でアクセスした場合本文は <content type='html'> に入らずに <summary type='text'> に入るということが判明。そりゃ取れないや。

通信量を減らそうと summary にしていたのがアダとなったみたい。
http://kuribo-programming.blogspot.com/feeds/posts/full?max-results=1&redirect=false
にアクセスすれば、問題なしにコンテントが取れた。ううむ。まだまだ Blogger 研究が足りない。

追記(2010-01-11):

と思っていたんだけれど…、full ではエラーが出るフィードがあることに、アプリを公開してから気づく。

Blogger でフィードの公開設定を「完全」でなく「先頭のみ」にしていると、full では取れない…。「完全」の場合は、full でも summary でもフィードを取れるので、勘違いしていた。

…ということで、default でタイトルや URL などの情報をまず取得しておいて、コンテントが null だった場合にはまた別の処理(代替の方法が今思いつかないけど)を加えるのがよさそう。

追記(2010-01-11):

…って、
String summary = entry.getSummary().getPlainText();
で、サマリー取れるじゃないか、と今さら気づく。今までなんだったんだと思いつつ、おかげで知らなかった Blogger の Atom フィードの仕様が少し分かったので、まあよしとしよう。

2010-01-05

GAE/J のログ

あ、全然ログの取り方分かってなかった。
java.util.logging.Logger を使って、
log.info("なんとかかんとか");
とかしてるだけじゃだめなんだ。
今さらながら。

2010-01-02

GAE/J、GoogleService とか BloggerService とか

GoogleService myService = new GoogleService("blogger", "kuribo-example-1");
を使っていたのを、
BloggerService myService = new BloggerService("kuribo-example-1");
と書き換え、実行してみた途端にエラーが発生。

java.lang.NoClassDefFoundError: com/google/gdata/data/media/mediarss/MediaThumbnail

BloggerService を使うには前に書いたの に加えて、gdata-src.java-*.**.*/gdata/java/lib ディレクトリの
  • gdata-media-*.*.jar
も war/WEB-INF/lib に入れて、ビルドパスに通す必要があるみたい。

解決策は分かったものの、デプロイに 1 分近くかかるようになってしまったのと、単にフィードの基本的な項目を取得したかっただけというのもあって、結局 GoogleService に戻しちゃいました。