2010-09-14

Blogger で作ったブログの記事一覧を作成する

これは「アプリのかけら」。いつか、実用的なアプリになるかもしれないコードの断片。

今日のお題は、Blogger で作ったブログの記事一覧を作成すること。まずは Ruby でさらっと書いてみよう。Ruby で書けたら Python で書き直してみる。Python で書けたら GAE に載せてみる。そうしたら「ウェブアプリのかけら」になる。

Blogger APIs

Blogger も Google の提供するサービスなので、データをやったり取ったりするための API が用意されている。公式サイト(Google Code 内)には以下のように書かれている。

(What is the Blogger Data API? より)
Here are some of the things you can do with the Blogger Data API:

  • Add a running list of blog posts and comments to a site.
  • Create a desktop application or plugin that allows users to create and post entries from the desktop.
  • Create a blog aggregator application.

今回のような使い方を、(無理矢理)あてはめるとすれば 3 つ目の「a blog aggregator application」になるか。ただし、実際には公開されている RSS フィードを取得するだけなので、API を使っているという感じは薄い。

フィードの取得に関する説明は「Developer's Guide: Protocol」の「Retrieving posts」に書かれている。

要は、以下のように HTTP 経由でリクエストを投げれば良い。

GET http://www.blogger.com/feeds/blogID/posts/default

ただし、これだと最近の 25 件分のデータしか取れない。それ以前のものを取るには start-index をクエリーに追加する。こんな風になる。

GET http://www.blogger.com/feeds/blogID/posts/default?start-index=26

start-index は 1 から始まるので、これでデフォルトの 25 件の次(というか、次に古い)データが 25 件分取れる。さらに前のデータなら 1 + 25 + 25 = 51 を指定する。

全記事のデータを取得するにはもう少し工夫が必要だ。Blogger から返される RSS フィードには、記事のトータル件数がふくまれている。それを取り出して、トータル件数分だけ start-index を調整しながらリクエストを繰り返す。

max-results というクエリーを使う方法もある。これで全記事数よりも大きな数値を指定すれば一度のリクエストですむ。スマートとは言えないけどね。

Ruby で実装

Ruby で HTTP リクエストを投げる

標準添付ライブラリの net/http を使う。実際に HTTP リクエストを投げる部分は繰り返し実行するので関数にしておく。

Blogger のフィードはデフォルトでは Atom 形式のデータになっている。Ruby の標準添付ライブラリには Atom を扱うものはないが、RSS の方ならある。今回は、Blogger に RSS をリクエストする。これには alt=rss をリクエストのクエリーに追加すれば良い。

(get_titles.rb より)
12: def get_rss(start_index = 1)
13:   url = URI.parse('http://www.blogger.com/')
14:   res = Net::HTTP.start(url.host, url.port) { |http|
15:     http.get("/feeds/#{BLOGID}/posts/default?alt=rss&start-index=#{start_index}")
16:   }
17:   res.body
18: end

定数 BLOGID には Blogger のブログIDを設定しておく。自分のブログなら Blogger にログインしてダッシュボードあたりで見ることができるが、他のユーザのブログについては、ID を知ることができるのかな?

記事数を取り出す

まずは、全記事数を取り出す。openSearch:totalResults というタグに囲まれているので、正規表現を使ってさらっと抽出。

(get_titles.rb より)
29: raw_data = get_rss(1)
30: /<openSearch:totalResults>(\d+)<\/openSearch:totalResults>/ =~ raw_data
31: total_posts = Regexp.last_match[1].to_i
Ruby で RSS データを読み込む

標準添付ライブラリの rss を使う。RSS::Parserparse してやれば、返ってくるオブジェクトが RSS フィードの構造をそのまま写し取ったものになっている。具体的には、rss が返り値のとき、rss.channel.items がフィードにふくまれる記事データの配列になっている。さらに個々の記事データを item とすれば、その属性は item.titleitem.link で取り出せる。

以下のコードでは、先に取り出した全記事数をもとにリクエストを繰り返してすべての記事のデータを取得している。33 〜 39 行目が while ループの外にあるのは、全記事数を取り出すのに使ったリクエストのデータも最初のページとして読み込むため。記事数だけ別のリクエストで取れれば良いんだが、公開されたフィードにアクセスする方法では、そういう特殊なリクエストは受け付けてくれないようだ。

(get_titles.rb より)
33: posts[current_page] = Array.new
34: rss = RSS::Parser.parse(raw_data, false)
35: rss.channel.items.each { |item|
36:   posts[current_page].push item
37: }
38: current_page += 1
39: current_post += rss.channel.items.length
40: 
41: # get more pages
42: while current_post < total_posts
43:   posts[current_page] = Array.new
44:   rss = RSS::Parser.parse(get_rss(current_post + 1), false)
45:   rss.channel.items.each { |item|
46:     posts[current_page].push item
47:   }
48:   current_page += 1
49:   current_post += rss.channel.items.length
50: end
51: 
52: total_pages = current_page

posts は 2 重の配列で、リクエストごとの記事データを収めた配列を要素とする配列になっている。2 重の配列にしたことに特別な意味はない。リクエストが複数回に分かれていることをデータ構造にも反映したかっただけ。

まとめてみる

コード全体は以下のようになった。肝心のリストは HTML として生成している。

こうして眺めてみるとあれこれリファクタリングしたくなってくるが、とりあえず動くんだからこれで良しとする。そんなことよりも Python で書き直したり、GAE に載せてみたりする方が先だな。

関連リンク

0 件のコメント:

コメントを投稿